mirror of
https://github.com/activecm/rita
synced 2026-06-08 13:02:45 +00:00
Update viewer
Co-Authored-By: Liza Tsibur <liza@activecountermeasures.com>
This commit is contained in:
+27
-20
@@ -34,7 +34,7 @@ linters-settings:
|
||||
sizeThreshold: 512 # Default: 512
|
||||
skipTestFuncs: true
|
||||
rangeValCopy:
|
||||
sizeThreshold: 200 # Default: 128
|
||||
sizeThreshold: 200 # Default: 128
|
||||
skipTestFuncs: true
|
||||
hugeParam:
|
||||
sizeThreshold: 100 # Default: 80
|
||||
@@ -74,23 +74,23 @@ linters-settings:
|
||||
# Enable to require nolint directives to mention the specific linter being suppressed.
|
||||
# Default: false
|
||||
require-specific: true
|
||||
|
||||
|
||||
revive:
|
||||
enable-all-rules: true
|
||||
enable-all-rules: true
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md
|
||||
rules:
|
||||
- name: add-constant
|
||||
disabled: true
|
||||
- name: argument-limit
|
||||
arguments: [10]
|
||||
- name: cognitive-complexity # enable in future cleanup
|
||||
- name: cognitive-complexity
|
||||
disabled: true
|
||||
- name: confusing-results
|
||||
disabled: true
|
||||
- name: cyclomatic # enable in future cleanup
|
||||
- name: cyclomatic
|
||||
disabled: true
|
||||
- name: deep-exit
|
||||
disabled: true # enable in future cleanup
|
||||
disabled: true
|
||||
- name: empty-lines
|
||||
disabled: true
|
||||
- name: exported
|
||||
@@ -113,16 +113,23 @@ linters-settings:
|
||||
arguments: [8]
|
||||
- name: var-naming
|
||||
arguments:
|
||||
- [ ] # allowlist
|
||||
- [ ] # blocklist
|
||||
- [ { "upperCaseConst": true, "skipPackageNameChecks": true } ]
|
||||
- [] # allowlist
|
||||
- [] # blocklist
|
||||
- [{ "upperCaseConst": true, "skipPackageNameChecks": true }]
|
||||
- name: unexported-return
|
||||
disabled: true
|
||||
- name: unhandled-error
|
||||
arguments: [ "fmt.Printf", "fmt.Fprintf", "fmt.Println", "io.Closer.Close", "os.File.Close" ]
|
||||
arguments:
|
||||
[
|
||||
"fmt.Printf",
|
||||
"fmt.Fprintf",
|
||||
"fmt.Println",
|
||||
"io.Closer.Close",
|
||||
"os.File.Close",
|
||||
]
|
||||
- name: unused-receiver
|
||||
disabled: true
|
||||
|
||||
|
||||
goconst:
|
||||
# Minimum occurrences of constant string count to trigger issue.
|
||||
# Default: 3
|
||||
@@ -130,7 +137,7 @@ linters-settings:
|
||||
# Ignore test files.
|
||||
# Default: false
|
||||
ignore-tests: true
|
||||
|
||||
|
||||
stylecheck:
|
||||
checks: ["all", "-ST1003"]
|
||||
|
||||
@@ -155,7 +162,7 @@ linters:
|
||||
- durationcheck
|
||||
|
||||
# Forces to not skip error check.
|
||||
- errcheck
|
||||
- errcheck
|
||||
|
||||
# Checks `Err-` prefix for var and `-Error` suffix for error type.
|
||||
- errname
|
||||
@@ -178,7 +185,7 @@ linters:
|
||||
# Checks standard formatting (whitespace, indentation, etc.)
|
||||
- gofmt
|
||||
|
||||
# Checks
|
||||
# Checks
|
||||
- goimports
|
||||
|
||||
# Allow or ban replace directives in go.mod or force explanation for retract directives.
|
||||
@@ -264,25 +271,25 @@ issues:
|
||||
# - "var-naming: don't use an underscore in package name"
|
||||
exclude-rules:
|
||||
# ignore shadowing of err and ctx
|
||||
- linters: [ govet ]
|
||||
- linters: [govet]
|
||||
text: 'shadow: declaration of "(err|ctx)" shadows declaration at'
|
||||
|
||||
# tolerate long functions in tests
|
||||
- linters: [ revive ]
|
||||
- linters: [revive]
|
||||
path: "(.+)_test.go"
|
||||
text: "function-length: .*"
|
||||
|
||||
# tolerate long lines in tests
|
||||
- linters: [ revive ]
|
||||
- linters: [revive]
|
||||
path: "(.+)_test.go"
|
||||
text: "line-length-limit: .*"
|
||||
|
||||
# tolerate deep-exit in tests
|
||||
- linters: [ revive ]
|
||||
- linters: [revive]
|
||||
path: "(.+)_test.go"
|
||||
text: "deep-exit: .*"
|
||||
|
||||
# tolerate import shadowing in tests
|
||||
- linters: [ revive ]
|
||||
- linters: [revive]
|
||||
path: "(.+)_test.go"
|
||||
text: "import-shadowing: The name ('require') shadows an import name"
|
||||
text: "import-shadowing: The name ('require') shadows an import name"
|
||||
|
||||
+6
-4
@@ -51,10 +51,11 @@ const (
|
||||
)
|
||||
|
||||
type listModel struct {
|
||||
Rows list.Model
|
||||
width int
|
||||
totalHeight int
|
||||
columns []column
|
||||
Rows list.Model
|
||||
width int
|
||||
totalHeight int
|
||||
headerHeight int
|
||||
columns []column
|
||||
}
|
||||
|
||||
func MakeList(items []list.Item, columns []column, width int, height int) listModel {
|
||||
@@ -95,6 +96,7 @@ func (m *listModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
func (m *listModel) SetHeight(height int) {
|
||||
_, v := listStyle.GetFrameSize()
|
||||
header := lipgloss.Height(renderColumnHeader(m.columns, m.width))
|
||||
m.headerHeight = header
|
||||
h := (height - header - v)
|
||||
m.totalHeight = header + v + h
|
||||
m.Rows.SetSize(m.width, h)
|
||||
|
||||
+76
-63
@@ -64,7 +64,6 @@ func (i *Item) GetDst() string {
|
||||
return i.Dst.String()
|
||||
}
|
||||
|
||||
// func (i *Item) FQDN() string { return i.fqdn }
|
||||
func (i *Item) GetBeacon() string {
|
||||
// if connection is a strobe, set beacon score to 100%
|
||||
if i.StrobeScore > 0 {
|
||||
@@ -72,6 +71,7 @@ func (i *Item) GetBeacon() string {
|
||||
}
|
||||
return renderIndicator(i.BeaconThreatScore, fmt.Sprintf("%1.2f%%", i.BeaconScore*100))
|
||||
}
|
||||
|
||||
func (i *Item) GetFirstSeen(relativeTimestamp time.Time) string {
|
||||
timeAgo := relativeTimestamp.Sub(i.FirstSeen)
|
||||
switch {
|
||||
@@ -130,8 +130,11 @@ func (i *Item) GetThreatIntel() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// no-op
|
||||
func (i Item) FilterValue() string { return i.GetSrc() } //nolint:gocritic // filtervalue cannot be a pointer method
|
||||
//nolint:gocritic // filtervalue cannot be a pointer method
|
||||
func (i Item) FilterValue() string { return i.GetSrc() } // no-op, bubble tea requires this method to be implemented but it is unused
|
||||
|
||||
// GetSeverity returns the severity of the mixtape result based on the final score and
|
||||
// adds a color based on the severity level if color is set to true
|
||||
func (i *Item) GetSeverity(color bool) string {
|
||||
caser := cases.Title(language.English)
|
||||
|
||||
@@ -157,6 +160,7 @@ func (i *Item) GetSeverity(color bool) string {
|
||||
return caser.String(string(severity))
|
||||
}
|
||||
|
||||
// GetResults queries the database for mixtape results based on the filter and pagination parameters
|
||||
func GetResults(db *database.DB, filter *Filter, currentPage, pageSize int, minTimestamp time.Time) ([]list.Item, bool, error) {
|
||||
// build query
|
||||
query, params, appliedFilter := BuildResultsQuery(filter, currentPage, pageSize, minTimestamp)
|
||||
@@ -184,6 +188,7 @@ func GetResults(db *database.DB, filter *Filter, currentPage, pageSize int, minT
|
||||
return items, appliedFilter, nil
|
||||
}
|
||||
|
||||
// BuildResultsQuery builds a query for fetching mixtape results based on the filter and pagination parameters
|
||||
func BuildResultsQuery(filter *Filter, currentPage, pageSize int, minTimestamp time.Time) (string, clickhouse.Parameters, bool) {
|
||||
params := clickhouse.Parameters{}
|
||||
query := `--sql
|
||||
@@ -254,25 +259,27 @@ func BuildResultsQuery(filter *Filter, currentPage, pageSize int, minTimestamp t
|
||||
|
||||
// set where conditions for src and dst filters
|
||||
whereConditions := []string{}
|
||||
if filter.Src != "" {
|
||||
whereConditions = append(whereConditions, "src={src:String}")
|
||||
params["src"] = filter.Src
|
||||
}
|
||||
if filter.Dst != "" {
|
||||
whereConditions = append(whereConditions, "dst={dst:String}")
|
||||
params["dst"] = filter.Dst
|
||||
}
|
||||
if filter.Fqdn != "" {
|
||||
whereConditions = append(whereConditions, "fqdn={fqdn:String}")
|
||||
params["fqdn"] = filter.Fqdn
|
||||
}
|
||||
if filter.ThreatIntel != "" {
|
||||
whereConditions = append(whereConditions, "threat_intel={threat_intel:Bool}")
|
||||
params["threat_intel"] = filter.ThreatIntel
|
||||
}
|
||||
if !filter.LastSeen.IsZero() {
|
||||
whereConditions = append(whereConditions, "toStartOfHour(last_seen) >= {last_seen:Int64}")
|
||||
params["last_seen"] = fmt.Sprintf("%d", filter.LastSeen.UTC().Unix())
|
||||
if filter != nil {
|
||||
if filter.Src != "" {
|
||||
whereConditions = append(whereConditions, "src={src:String}")
|
||||
params["src"] = filter.Src
|
||||
}
|
||||
if filter.Dst != "" {
|
||||
whereConditions = append(whereConditions, "dst={dst:String}")
|
||||
params["dst"] = filter.Dst
|
||||
}
|
||||
if filter.Fqdn != "" {
|
||||
whereConditions = append(whereConditions, "fqdn={fqdn:String}")
|
||||
params["fqdn"] = filter.Fqdn
|
||||
}
|
||||
if filter.ThreatIntel != "" {
|
||||
whereConditions = append(whereConditions, "threat_intel={threat_intel:Bool}")
|
||||
params["threat_intel"] = filter.ThreatIntel
|
||||
}
|
||||
if !filter.LastSeen.IsZero() {
|
||||
whereConditions = append(whereConditions, "toStartOfHour(last_seen) >= {last_seen:Int64}")
|
||||
params["last_seen"] = fmt.Sprintf("%d", filter.LastSeen.UTC().Unix())
|
||||
}
|
||||
}
|
||||
|
||||
// set where conditions for src and dst filters to query if any were specified
|
||||
@@ -287,29 +294,31 @@ func BuildResultsQuery(filter *Filter, currentPage, pageSize int, minTimestamp t
|
||||
|
||||
// set having conditions for numerical filters
|
||||
havingConditions := []string{}
|
||||
if filter.Count.Value != "" && filter.Count.Operator != "" {
|
||||
havingConditions = append(havingConditions, "count "+filter.Count.Operator+" {count:Int64}")
|
||||
params["count"] = filter.Count.Value
|
||||
}
|
||||
|
||||
if filter.Beacon.Value != "" && filter.Beacon.Operator != "" {
|
||||
havingConditions = append(havingConditions, "beacon_score "+filter.Beacon.Operator+" {beacon:Float32}")
|
||||
params["beacon"] = filter.Beacon.Value
|
||||
}
|
||||
|
||||
if filter.Subdomains.Value != "" && filter.Subdomains.Operator != "" {
|
||||
havingConditions = append(havingConditions, "subdomain_count "+filter.Subdomains.Operator+" {subdomains:Int64}")
|
||||
params["subdomains"] = filter.Subdomains.Value
|
||||
}
|
||||
|
||||
if filter.Duration.Value != "" && filter.Duration.Operator != "" {
|
||||
if filter.Duration.Operator == "=" {
|
||||
// round column down to the nearest integer if the operator is equ
|
||||
havingConditions = append(havingConditions, "floor(total_duration) "+filter.Duration.Operator+" {duration:Float64}")
|
||||
} else {
|
||||
havingConditions = append(havingConditions, "total_duration "+filter.Duration.Operator+" {duration:Float64}")
|
||||
if filter != nil {
|
||||
if filter.Count.Value != "" && filter.Count.Operator != "" {
|
||||
havingConditions = append(havingConditions, "count "+filter.Count.Operator+" {count:Int64}")
|
||||
params["count"] = filter.Count.Value
|
||||
}
|
||||
|
||||
if filter.Beacon.Value != "" && filter.Beacon.Operator != "" {
|
||||
havingConditions = append(havingConditions, "beacon_score "+filter.Beacon.Operator+" {beacon:Float32}")
|
||||
params["beacon"] = filter.Beacon.Value
|
||||
}
|
||||
|
||||
if filter.Subdomains.Value != "" && filter.Subdomains.Operator != "" {
|
||||
havingConditions = append(havingConditions, "subdomain_count "+filter.Subdomains.Operator+" {subdomains:Int64}")
|
||||
params["subdomains"] = filter.Subdomains.Value
|
||||
}
|
||||
|
||||
if filter.Duration.Value != "" && filter.Duration.Operator != "" {
|
||||
if filter.Duration.Operator == "=" {
|
||||
// round column down to the nearest integer if the operator is equ
|
||||
havingConditions = append(havingConditions, "floor(total_duration) "+filter.Duration.Operator+" {duration:Float64}")
|
||||
} else {
|
||||
havingConditions = append(havingConditions, "total_duration "+filter.Duration.Operator+" {duration:Float64}")
|
||||
}
|
||||
params["duration"] = filter.Duration.Value
|
||||
}
|
||||
params["duration"] = filter.Duration.Value
|
||||
}
|
||||
|
||||
// add having conditions to query if any were specified
|
||||
@@ -323,29 +332,33 @@ func BuildResultsQuery(filter *Filter, currentPage, pageSize int, minTimestamp t
|
||||
|
||||
// add where conditions to the outer part of the query if any were specified
|
||||
outerWhereConditions := []string{}
|
||||
// add conditions for severity filter to query
|
||||
if len(filter.Severity) > 0 {
|
||||
for i, op := range filter.Severity {
|
||||
paramName := fmt.Sprintf("final_score_%d", i)
|
||||
outerWhereConditions = append(outerWhereConditions, "final_score "+op.Operator+fmt.Sprintf("{%s:Float32}", paramName))
|
||||
params[paramName] = op.Value
|
||||
if filter != nil {
|
||||
// add conditions for severity filter to query
|
||||
if len(filter.Severity) > 0 {
|
||||
for i, op := range filter.Severity {
|
||||
paramName := fmt.Sprintf("final_score_%d", i)
|
||||
outerWhereConditions = append(outerWhereConditions, "final_score "+op.Operator+fmt.Sprintf("{%s:Float32}", paramName))
|
||||
params[paramName] = op.Value
|
||||
}
|
||||
query += "WHERE " + strings.Join(outerWhereConditions, " AND ")
|
||||
}
|
||||
query += "WHERE " + strings.Join(outerWhereConditions, " AND ")
|
||||
}
|
||||
|
||||
// set sorting conditions if any were specified
|
||||
sortingConditions := []string{}
|
||||
if filter.SortSeverity != "" {
|
||||
sortingConditions = append(sortingConditions, "final_score "+filter.SortSeverity)
|
||||
}
|
||||
if filter.SortBeacon != "" {
|
||||
sortingConditions = append(sortingConditions, "beacon_score "+filter.SortBeacon)
|
||||
}
|
||||
if filter.SortDuration != "" {
|
||||
sortingConditions = append(sortingConditions, "total_duration "+filter.SortDuration)
|
||||
}
|
||||
if filter.SortSubdomains != "" {
|
||||
sortingConditions = append(sortingConditions, "subdomains "+filter.SortSubdomains)
|
||||
if filter != nil {
|
||||
if filter.SortSeverity != "" {
|
||||
sortingConditions = append(sortingConditions, "final_score "+filter.SortSeverity)
|
||||
}
|
||||
if filter.SortBeacon != "" {
|
||||
sortingConditions = append(sortingConditions, "beacon_score "+filter.SortBeacon)
|
||||
}
|
||||
if filter.SortDuration != "" {
|
||||
sortingConditions = append(sortingConditions, "total_duration "+filter.SortDuration)
|
||||
}
|
||||
if filter.SortSubdomains != "" {
|
||||
sortingConditions = append(sortingConditions, "subdomains "+filter.SortSubdomains)
|
||||
}
|
||||
}
|
||||
|
||||
// add sorting conditions to query if any were specified
|
||||
@@ -357,8 +370,8 @@ func BuildResultsQuery(filter *Filter, currentPage, pageSize int, minTimestamp t
|
||||
`
|
||||
}
|
||||
|
||||
offset := currentPage * pageSize
|
||||
// set offset ; fetch if the offset is greater than 0, otherwise set limit
|
||||
offset := currentPage * pageSize
|
||||
if offset > 0 {
|
||||
query += `--sql
|
||||
OFFSET {skip:Int32} ROWS FETCH NEXT {page_size:Int32} ROWS ONLY
|
||||
|
||||
+36
-33
@@ -23,6 +23,7 @@ type modifier struct {
|
||||
type sidebarModel struct {
|
||||
Viewport viewport.Model
|
||||
Data *Item
|
||||
Height int
|
||||
maxTimestamp time.Time
|
||||
useCurrentTime bool
|
||||
ScrollEnabled bool
|
||||
@@ -41,27 +42,42 @@ func (m *sidebarModel) Init() tea.Cmd {
|
||||
m.Viewport.SetContent(m.getSidebarContents())
|
||||
return nil
|
||||
}
|
||||
func (m *sidebarModel) Update(_ tea.Msg) (tea.Model, tea.Cmd) {
|
||||
return m, nil
|
||||
func (m *sidebarModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
var cmds []tea.Cmd
|
||||
switch msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
// if k := msg.String(); k == "ctrl+c" || k == "q" || k == "esc" {
|
||||
// return m, tea.Quit
|
||||
// }
|
||||
|
||||
case tea.WindowSizeMsg:
|
||||
cmds = append(cmds, viewport.Sync(m.Viewport))
|
||||
|
||||
// return m, nil
|
||||
}
|
||||
return m, tea.Batch(cmds...)
|
||||
}
|
||||
|
||||
func (m *sidebarModel) View() string {
|
||||
m.Viewport.SetContent(m.getSidebarContents())
|
||||
m.Viewport.Height = m.Height
|
||||
borderColor := mauve
|
||||
if m.ScrollEnabled {
|
||||
borderColor = green
|
||||
}
|
||||
style := sideBarStyle.
|
||||
// .Width(m.width).Height(m.height).
|
||||
// .Width(m.width)
|
||||
// Height(m.Viewport.Height).
|
||||
Padding(0, 1).
|
||||
Border(lipgloss.RoundedBorder()).
|
||||
BorderForeground(borderColor)
|
||||
|
||||
return style.Render(m.Viewport.View())
|
||||
hi := style.Render(m.Viewport.View())
|
||||
return lipgloss.NewStyle().Render(hi)
|
||||
// Border(lipgloss.NormalBorder())
|
||||
}
|
||||
|
||||
// getSidebarContents gets and formats the data to be displayed in the sidebar
|
||||
func (m *sidebarModel) getSidebarContents() string {
|
||||
|
||||
// get header
|
||||
var target string
|
||||
headerPadding := 2
|
||||
@@ -74,8 +90,8 @@ func (m *sidebarModel) getSidebarContents() string {
|
||||
// dstStyle := lipgloss.NewStyle().Width(m.viewport.Width - (headerPadding * 2))
|
||||
fqdnLabel := "FQDN"
|
||||
dstStyle := lipgloss.NewStyle().Width(m.Viewport.Width - len(fqdnLabel) - (headerPadding * 4))
|
||||
|
||||
target = lipgloss.JoinHorizontal(lipgloss.Left, headerLabelStyle.Render(fqdnLabel), headerValueStyle.Render(Truncate(m.Data.GetDst(), &dstStyle)))
|
||||
valueStyle := headerValueStyle.Render(Truncate(m.Data.GetDst(), &dstStyle))
|
||||
target = lipgloss.JoinHorizontal(lipgloss.Left, headerLabelStyle.Render(fqdnLabel), valueStyle)
|
||||
target = lipgloss.NewStyle().MarginBottom(2).Render(target)
|
||||
} else {
|
||||
// handle connection pair, ip -> ip or ip -> fqdn
|
||||
@@ -83,15 +99,14 @@ func (m *sidebarModel) getSidebarContents() string {
|
||||
srcStyle := lipgloss.NewStyle().Width(m.Viewport.Width - len(srcLabel) - (headerPadding * 4))
|
||||
dstLabel := "DST"
|
||||
dstStyle := lipgloss.NewStyle().Width(m.Viewport.Width - len(dstLabel) - (headerPadding * 4))
|
||||
// - 6 - len(m.data.GetSrc())
|
||||
// dstStyle := lipgloss.NewStyle().Width(dstWidth)
|
||||
src := lipgloss.JoinHorizontal(lipgloss.Left, headerLabelStyle.Render(srcLabel), headerValueStyle.Render(Truncate(m.Data.GetSrc(), &srcStyle)))
|
||||
dst := lipgloss.JoinHorizontal(lipgloss.Left, headerLabelStyle.Render(dstLabel), headerValueStyle.Render(Truncate(m.Data.GetDst(), &dstStyle)))
|
||||
srcValueStyle := headerValueStyle.Render(Truncate(m.Data.GetSrc(), &srcStyle))
|
||||
dstValueStyle := headerValueStyle.Render(Truncate(m.Data.GetDst(), &dstStyle))
|
||||
|
||||
src := lipgloss.JoinHorizontal(lipgloss.Left, headerLabelStyle.Render(srcLabel), srcValueStyle)
|
||||
dst := lipgloss.JoinHorizontal(lipgloss.Left, headerLabelStyle.Render(dstLabel), dstValueStyle)
|
||||
target = lipgloss.JoinVertical(lipgloss.Top, lipgloss.NewStyle().MarginBottom(1).Render(src), dst)
|
||||
}
|
||||
heading := lipgloss.NewStyle().
|
||||
// PaddingLeft(headerPadding).
|
||||
MarginBottom(1).Render(target)
|
||||
heading := lipgloss.NewStyle().MarginBottom(1).Render(target)
|
||||
|
||||
// get modifiers
|
||||
sectionStyle := lipgloss.NewStyle().
|
||||
@@ -130,8 +145,8 @@ func (m *sidebarModel) getSidebarContents() string {
|
||||
|
||||
// render header
|
||||
portsHeader := portsHeaderStyle.Render("Port : Proto : Service")
|
||||
ports = lipgloss.JoinVertical(lipgloss.Top, portsHeader, strings.Join(portProtoService, "\n"))
|
||||
|
||||
ports = lipgloss.JoinVertical(lipgloss.Top, portsHeader, strings.Join(portProtoService, ","))
|
||||
// strings.Join(portProtoService, "\n")
|
||||
// calculate the number of lines available for port data
|
||||
// remainingLines := m.viewport.Height - (lipgloss.Height(heading) + lipgloss.Height(modifiers) + lipgloss.Height(modifierLabel) + lipgloss.Height(connInfoLabel) + lipgloss.Height(bytes) + lipgloss.Height(connCount))
|
||||
// ports = renderPorts(portProtoService, m.viewport.Width, remainingLines)
|
||||
@@ -142,61 +157,48 @@ func (m *sidebarModel) getSidebarContents() string {
|
||||
return lipgloss.JoinVertical(lipgloss.Top, heading, modifierLabel, modifiers, connInfoLabel, connCount, bytes, ports)
|
||||
}
|
||||
|
||||
// renderModifiers aggregates and formats the modifiers for the currently selected item
|
||||
// for rendering in the sidebar
|
||||
func (m *sidebarModel) renderModifiers() string {
|
||||
modifierList := m.getModifiers()
|
||||
// panic(modifierList)
|
||||
|
||||
var modifiers string
|
||||
var renderedModifiers []string
|
||||
for _, modifier := range modifierList {
|
||||
renderedModifier := renderModifier(modifier)
|
||||
// if i > 0 {
|
||||
// renderedModifier = lipgloss.NewStyle().MarginLeft(1).Render(renderedModifier)
|
||||
// }
|
||||
renderedModifiers = append(renderedModifiers, renderedModifier)
|
||||
}
|
||||
newlineStyle := lipgloss.NewStyle().PaddingRight(1).BorderForeground(overlay2).Border(lipgloss.NormalBorder(), false, true, false, false)
|
||||
linebreakStyle := lipgloss.NewStyle().MarginBottom(1)
|
||||
// if len(renderedModifiers) > 2 {
|
||||
|
||||
var modifierLines []string
|
||||
// lastUsedIndex := 0
|
||||
var currentModifiers string
|
||||
for i, mod := range renderedModifiers {
|
||||
if i == 0 {
|
||||
currentModifiers = newlineStyle.Render(mod)
|
||||
} else {
|
||||
// modifier = lipgloss.NewStyle().Border(lipgloss.NormalBorder(), false, true, false, false).Padding(0, 2, 0, 0).Render(modifier)
|
||||
|
||||
newMod := lipgloss.JoinHorizontal(lipgloss.Left, currentModifiers, lipgloss.NewStyle().Padding(0, 1).BorderForeground(overlay2).Border(lipgloss.NormalBorder(), false, true, false, false).Render(mod))
|
||||
// panic(newMod)
|
||||
|
||||
width := lipgloss.Width(newMod)
|
||||
if m.Viewport.Width <= width {
|
||||
modifierLines = append(modifierLines, lipgloss.JoinHorizontal(lipgloss.Left, linebreakStyle.Render(currentModifiers)))
|
||||
// lastUsedIndex = i
|
||||
currentModifiers = mod
|
||||
if i != len(renderedModifiers)-1 {
|
||||
currentModifiers = newlineStyle.Render(mod)
|
||||
}
|
||||
} else {
|
||||
currentModifiers = newMod
|
||||
// if i != len(renderedModifiers)-1 {
|
||||
// currentModifiers = newlineStyle.Render(newMod)
|
||||
// }
|
||||
}
|
||||
}
|
||||
// modifiers += mod
|
||||
}
|
||||
modifierLines = append(modifierLines, linebreakStyle.Render(currentModifiers))
|
||||
// panic(modifierLines)
|
||||
modifiers = lipgloss.JoinVertical(lipgloss.Top, modifierLines...)
|
||||
// modifiers = lipgloss.NewStyle().MarginBottom(1).PaddingBottom(1).Border(lipgloss.NormalBorder(), false, false, true, false).BorderForeground(surface0).Render(modifiers)
|
||||
|
||||
// }
|
||||
return modifiers
|
||||
}
|
||||
|
||||
// getModifiers gets all the modifiers for the currently selected item
|
||||
func (m *sidebarModel) getModifiers() []modifier {
|
||||
var modifiers []modifier
|
||||
|
||||
@@ -245,6 +247,7 @@ func (m *sidebarModel) getModifiers() []modifier {
|
||||
return modifiers
|
||||
}
|
||||
|
||||
// renderModifier formats and styles a single modifier for rendering
|
||||
func renderModifier(mod modifier) string {
|
||||
var color lipgloss.AdaptiveColor
|
||||
switch {
|
||||
|
||||
+24
-11
@@ -18,7 +18,7 @@ import (
|
||||
"github.com/charmbracelet/lipgloss/table"
|
||||
)
|
||||
|
||||
var DebugMode bool
|
||||
var DebugMode bool // set true by rita if debug flag is passed in
|
||||
var mainStyle = lipgloss.NewStyle().Margin(0, 0)
|
||||
|
||||
type Model struct {
|
||||
@@ -57,6 +57,9 @@ type column struct {
|
||||
width int
|
||||
}
|
||||
|
||||
type FinishedLoadingResults string
|
||||
type StillLoadingResults string
|
||||
|
||||
// CreateUI creates the terminal UI
|
||||
func CreateUI(_ *config.Config, db *database.DB, useCurrentTime bool, maxTimestamp time.Time, minTimestamp time.Time) error {
|
||||
// create model
|
||||
@@ -76,6 +79,7 @@ func CreateUI(_ *config.Config, db *database.DB, useCurrentTime bool, maxTimesta
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewModel creates a new model
|
||||
func NewModel(maxTimestamp, minTimestamp time.Time, useCurrentTime bool, db *database.DB) (*Model, error) {
|
||||
pageSize := 100
|
||||
// get results from database
|
||||
@@ -101,11 +105,14 @@ func NewModel(maxTimestamp, minTimestamp time.Time, useCurrentTime bool, db *dat
|
||||
sideBar := NewSidebarModel(maxTimestamp, useCurrentTime, &Item{})
|
||||
if len(dataList.Rows.Items()) > 0 {
|
||||
// set sidebar data to whichever item is selected in the list
|
||||
data, ok := dataList.Rows.Items()[dataList.Rows.Index()].(Item)
|
||||
tmp := dataList.Rows.Items()[dataList.Rows.Index()]
|
||||
data, ok := tmp.(*Item)
|
||||
fmt.Println(data, tmp)
|
||||
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("error setting sidebar data")
|
||||
}
|
||||
sideBar.Data = &data
|
||||
sideBar.Data = data
|
||||
|
||||
}
|
||||
|
||||
@@ -134,6 +141,7 @@ func NewModel(maxTimestamp, minTimestamp time.Time, useCurrentTime bool, db *dat
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// Init initializes the model
|
||||
func (m *Model) Init() tea.Cmd {
|
||||
|
||||
// set title
|
||||
@@ -178,6 +186,7 @@ func (m *Model) Init() tea.Cmd {
|
||||
return m.Footer.spinner.Tick
|
||||
}
|
||||
|
||||
// Update updates the model
|
||||
func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
|
||||
var cmd tea.Cmd
|
||||
@@ -185,12 +194,14 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
case tea.WindowSizeMsg:
|
||||
// make the footer the entire width of the terminal
|
||||
m.Footer.width = msg.Width
|
||||
|
||||
height := msg.Height - int(math.Max(float64(lipgloss.Height(m.SearchBar.View())), float64(lipgloss.Height(m.title)))) - lipgloss.Height(m.dbFooterBar)
|
||||
// make the list fill the extra vertical space
|
||||
m.List.SetHeight(msg.Height - int(math.Max(float64(lipgloss.Height(m.SearchBar.View())), float64(lipgloss.Height(m.title)))) - lipgloss.Height(m.dbFooterBar))
|
||||
m.List.SetHeight(height)
|
||||
|
||||
// make the sidebar the same height as the list
|
||||
m.SideBar.Viewport.Height = m.List.totalHeight
|
||||
m.SideBar.Height = m.List.totalHeight
|
||||
// m.SideBar.Viewport.Height = lipgloss.Height(m.List.View())
|
||||
|
||||
// make sidebar fill the extra horizontal space
|
||||
m.SideBar.Viewport.Width = msg.Width - lipgloss.Width(m.List.View()) - 4
|
||||
@@ -258,11 +269,13 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
m.List.Rows.Select(index)
|
||||
}
|
||||
|
||||
if data, ok := m.List.Rows.Items()[m.List.Rows.Index()].(Item); ok {
|
||||
m.SideBar.Data = &data
|
||||
// set sidebar data to the selected item
|
||||
if data, ok := m.List.Rows.Items()[m.List.Rows.Index()].(*Item); ok {
|
||||
m.SideBar.Data = data
|
||||
}
|
||||
|
||||
} else {
|
||||
// if there are no items to display, set the sidebar data to an empty item
|
||||
m.SideBar.Data = &Item{}
|
||||
}
|
||||
|
||||
@@ -280,9 +293,10 @@ func (m *Model) View() string {
|
||||
case m.ViewHelp:
|
||||
mainContent = helpPanel(m.SideBar.Viewport.Height, m.List.width, mainHelpText())
|
||||
default:
|
||||
resultList := mainStyle.Render(m.List.View())
|
||||
mainContent = lipgloss.JoinHorizontal(
|
||||
lipgloss.Left,
|
||||
mainStyle.Render(m.List.View()),
|
||||
resultList,
|
||||
mainStyle.Render(m.SideBar.View()),
|
||||
)
|
||||
}
|
||||
@@ -295,9 +309,6 @@ func (m *Model) View() string {
|
||||
)
|
||||
}
|
||||
|
||||
type FinishedLoadingResults string
|
||||
type StillLoadingResults string
|
||||
|
||||
// handleFiltering handles key presses on the search bar
|
||||
func (m *Model) handleFiltering(msg tea.KeyMsg) tea.Cmd {
|
||||
var cmd tea.Cmd
|
||||
@@ -594,12 +605,14 @@ func mainHelpText() string {
|
||||
|
||||
}
|
||||
|
||||
// helpPanel returns a help panel with the given height, width, and contents
|
||||
func helpPanel(height int, width int, contents string) string {
|
||||
return mainStyle.Height(height).Width(width).
|
||||
Border(lipgloss.RoundedBorder()).BorderForeground(surface0).
|
||||
Render(contents)
|
||||
}
|
||||
|
||||
// getTableWidth returns the total width of the table
|
||||
func getTableWidth(columns []column) int {
|
||||
width := 0
|
||||
for _, column := range columns {
|
||||
|
||||
Reference in New Issue
Block a user