Files
proxy/internal/tui/dashboard.go
T

141 lines
3.3 KiB
Go

package tui
import (
"proxyctl/internal/app"
"proxyctl/internal/target"
)
func targetActionLabel(kind app.TargetActionKind) string {
switch kind {
case app.EnableTargetAction:
return "Enable"
case app.DisableTargetAction:
return "Disable"
case app.ApplyProfileTargetAction:
return "Apply Profile"
case app.ResolveDriftTargetAction:
return "Resolve"
case app.ReapplyActiveProfileAction:
return "Reapply Active Profile"
case app.RestoreOriginalAction:
return "Restore Original"
default:
return "Unavailable"
}
}
type dashboardPane uint8
const (
targetListPane dashboardPane = iota
targetDetailsPane
)
// targetCatalog owns the dashboard's product-ordered Target projection. It is
// the only place where availability filtering and identity-based selection are
// combined, so rendering and input handling cannot accidentally select by a
// transient row number.
type targetCatalog struct {
items []app.TargetSnapshot
}
func newTargetCatalog(snapshot app.Snapshot, includeUnavailable bool, selectedID string) targetCatalog {
items := make([]app.TargetSnapshot, 0, len(snapshot.Targets))
for _, item := range snapshot.Targets {
// Available Targets are the routine default. If progressive detection
// proves the selected identity unavailable, keep that one row visible
// until the user selects another Target rather than silently moving the
// selection to a different product.
if item.Available || includeUnavailable || item.ID == selectedID || item.DetectionPhase != app.TargetReady {
items = append(items, item)
}
}
return targetCatalog{items: items}
}
func (c targetCatalog) firstID() string {
if len(c.items) == 0 {
return ""
}
return c.items[0].ID
}
func (c targetCatalog) index(id string) int {
for i := range c.items {
if c.items[i].ID == id {
return i
}
}
return -1
}
func (c targetCatalog) selected(id string) (app.TargetSnapshot, bool) {
index := c.index(id)
if index < 0 {
return app.TargetSnapshot{}, false
}
return c.items[index], true
}
func (c targetCatalog) move(id string, delta int) string {
if len(c.items) == 0 {
return ""
}
index := c.index(id)
if index < 0 {
index = 0
}
index = max(0, min(len(c.items)-1, index+delta))
return c.items[index].ID
}
// retain keeps an identity when possible and otherwise chooses the product row
// nearest its former position. This is used after refresh and visibility
// changes; terminal resize never needs to touch selection at all.
func (c targetCatalog) retain(id string, formerIndex int) string {
if c.index(id) >= 0 {
return id
}
if len(c.items) == 0 {
return ""
}
return c.items[max(0, min(len(c.items)-1, formerIndex))].ID
}
type targetStateCounts struct {
enabled int
disabled int
external int
drifted int
checking int
failed int
}
func countAvailableTargetStates(snapshot app.Snapshot) targetStateCounts {
var counts targetStateCounts
for _, item := range snapshot.Targets {
switch item.DetectionPhase {
case app.TargetChecking:
counts.checking++
continue
case app.TargetDetectionFailed:
counts.failed++
continue
}
if !item.Available {
continue
}
switch item.State {
case target.Enabled:
counts.enabled++
case target.Disabled:
counts.disabled++
case target.External:
counts.external++
case target.Drifted:
counts.drifted++
}
}
return counts
}