@@ -77,12 +77,10 @@ func (m Model) View() string {
7777 }
7878
7979 contentWidth := util .Clamp (36 , width - 4 , 76 )
80-
81- borderStyle := lipgloss .NewStyle ().
82- BorderStyle (lipgloss .RoundedBorder ()).
83- BorderForeground (lipgloss .Color ("6" )).
84- Padding (0 , 1 ).
85- Width (contentWidth )
80+ sectionTitleStyle := lipgloss .NewStyle ().Foreground (lipgloss .Color ("6" )).Bold (true )
81+ bodyStyle := lipgloss .NewStyle ().Foreground (lipgloss .Color ("8" ))
82+ valueStyle := lipgloss .NewStyle ().Foreground (lipgloss .Color ("10" )).Bold (true )
83+ warnValueStyle := lipgloss .NewStyle ().Foreground (lipgloss .Color ("3" )).Bold (true )
8684
8785 if m .err != nil {
8886 errTitle := lipgloss .NewStyle ().
@@ -91,68 +89,50 @@ func (m Model) View() string {
9189 Render (i18n .T ("dashboard.error_title" ))
9290 errBody := lipgloss .NewStyle ().
9391 Foreground (lipgloss .Color ("8" )).
94- Width (contentWidth - 2 ).
92+ Width (contentWidth ).
9593 Render (m .err .Error ())
96- return borderStyle . Render ( lipgloss .JoinVertical (lipgloss .Left , errTitle , "" , errBody ) )
94+ return lipgloss .JoinVertical (lipgloss .Left , errTitle , "" , errBody )
9795 }
9896
99- titleStyle := lipgloss .NewStyle ().
100- Foreground (lipgloss .Color ("6" )).
101- Bold (true )
102- labelStyle := lipgloss .NewStyle ().Foreground (lipgloss .Color ("8" ))
103- valueStyle := lipgloss .NewStyle ().Foreground (lipgloss .Color ("10" )).Bold (true )
104- warnValueStyle := lipgloss .NewStyle ().Foreground (lipgloss .Color ("3" )).Bold (true )
97+ height := m .size .Height
98+ if height <= 0 {
99+ height = 24
100+ }
105101
106- metricsPanel := borderStyle .Render (lipgloss .JoinVertical (
107- lipgloss .Left ,
108- titleStyle .Render (i18n .T ("dashboard.system_metrics" )),
102+ lines := []string {
103+ sectionTitleStyle .Render (i18n .T ("dashboard.system_status" )),
104+ "" ,
105+ valueStyle .Render (fmt .Sprintf (i18n .T ("dashboard.accounts" ), m .data .AccountCount , m .data .ActiveAccountCount )),
106+ valueStyle .Render (fmt .Sprintf (i18n .T ("dashboard.public_keys" ), m .data .PublicKeyCount , m .data .GlobalKeyCount )),
107+ valueStyle .Render (fmt .Sprintf (i18n .T ("dashboard.system_key" ), formatSystemKeySerial (m .data .SystemKeySerial ))),
108+ "" ,
109+ sectionTitleStyle .Render (i18n .T ("dashboard.deployment_status" )),
109110 "" ,
110- formatKeyValue (i18n .T ("dashboard.label.accounts" ), fmt .Sprintf (i18n .T ("dashboard.accounts_summary" ), m .data .AccountCount , m .data .ActiveAccountCount ), labelStyle , valueStyle ),
111- formatKeyValue (i18n .T ("dashboard.label.hosts" ), formatHostStatus (m .data .HostsUpToDate , m .data .HostsOutdated ), labelStyle , chooseOutdatedStyle (m .data .HostsOutdated , valueStyle , warnValueStyle )),
112- formatKeyValue (i18n .T ("dashboard.label.system_key" ), formatSystemKeySerial (m .data .SystemKeySerial ), labelStyle , valueStyle ),
113- ))
111+ valueStyle .Render (fmt .Sprintf (i18n .T ("dashboard.hosts_current_key" ), m .data .HostsUpToDate )),
112+ valueStyle .Render (fmt .Sprintf (i18n .T ("dashboard.hosts_past_keys" ), m .data .HostsOutdated )),
113+ "" ,
114+ sectionTitleStyle .Render (i18n .T ("dashboard.security_posture" )),
115+ "" ,
116+ bodyStyle .Render (fmt .Sprintf (i18n .T ("dashboard.key_type_spread" ), formatAlgoSpread (m .data .AlgoCounts , warnValueStyle ))),
117+ "" ,
118+ sectionTitleStyle .Render (i18n .T ("dashboard.recent_activity" )),
119+ "" ,
120+ }
114121
115- // TODO use bubbles table (like everywhere else)
116- logsTitle := titleStyle .Render (i18n .T ("dashboard.recent_audit_logs" ))
117- logsBody := ""
118122 if len (m .data .RecentLogs ) == 0 {
119- logsBody = lipgloss . NewStyle (). Foreground ( lipgloss . Color ( "8" )). Italic (true ).Render (i18n .T ("dashboard.no_recent_entries" ))
123+ lines = append ( lines , bodyStyle . Italic (true ).Render (i18n .T ("dashboard.no_recent_activity" ) ))
120124 } else {
121- height := m .size .Height
122- if height <= 0 {
123- height = 24
124- }
125125 maxLogRows := util .Clamp (3 , height - 14 , 10 )
126- logLines := make ([]string , 0 , min (len (m .data .RecentLogs ), maxLogRows + 1 ))
127126 entryWidth := contentWidth - 4
128- logTimeCol := padRight (i18n .T ("dashboard.log_col_time" ), 12 )
129- logActionCol := padRight (i18n .T ("dashboard.log_col_action" ), 20 )
130- logDetailsCol := i18n .T ("dashboard.log_col_details" )
131- head := lipgloss .NewStyle ().Foreground (lipgloss .Color ("8" )).Bold (true ).Render (
132- fmt .Sprintf ("%s | %s | %s" , logTimeCol , logActionCol , logDetailsCol ),
133- )
134- logLines = append (logLines , head )
135127 for i , al := range m .data .RecentLogs {
136128 if i >= maxLogRows {
137129 break
138130 }
139- logLines = append (logLines , formatLogEntry (al , entryWidth ))
131+ lines = append (lines , bodyStyle . Render ( formatLogEntry (al , entryWidth ) ))
140132 }
141- logsBody = lipgloss .JoinVertical (lipgloss .Left , logLines ... )
142133 }
143134
144- logsPanel := borderStyle .Render (lipgloss .JoinVertical (
145- lipgloss .Left ,
146- logsTitle ,
147- "" ,
148- logsBody ,
149- ))
150-
151- return lipgloss .JoinVertical (lipgloss .Left , metricsPanel , "" , logsPanel )
152- }
153-
154- func formatKeyValue (label , value string , labelStyle , valueStyle lipgloss.Style ) string {
155- return labelStyle .Render (label + ":" ) + " " + valueStyle .Render (value )
135+ return lipgloss .JoinVertical (lipgloss .Left , lines ... )
156136}
157137
158138func formatHostStatus (upToDate , outdated int ) string {
@@ -169,13 +149,6 @@ func formatSystemKeySerial(serial int) string {
169149 return fmt .Sprintf (i18n .T ("dashboard.system_key_serial" ), serial )
170150}
171151
172- func chooseOutdatedStyle (outdated int , normal , warn lipgloss.Style ) lipgloss.Style {
173- if outdated > 0 {
174- return warn
175- }
176- return normal
177- }
178-
179152func formatLogEntry (al AuditLogEntry , width int ) string {
180153 ts := parseTimestamp (al .Timestamp )
181154 action := titleFromUnderscore (strings .TrimSpace (al .Action ))
@@ -198,6 +171,24 @@ func formatLogEntry(al AuditLogEntry, width int) string {
198171 return timestampStyle .Render (padRight (ts , 12 )) + " | " + actionStyle .Render (actionPadded ) + " | " + detailStyle .Render (details )
199172}
200173
174+ func formatAlgoSpread (algoCounts map [string ]int , style lipgloss.Style ) string {
175+ if len (algoCounts ) == 0 {
176+ return "-"
177+ }
178+
179+ algorithms := make ([]string , 0 , len (algoCounts ))
180+ for algorithm := range algoCounts {
181+ algorithms = append (algorithms , algorithm )
182+ }
183+ slices .Sort (algorithms )
184+
185+ parts := make ([]string , 0 , len (algorithms ))
186+ for _ , algorithm := range algorithms {
187+ parts = append (parts , style .Render (fmt .Sprintf ("%s: %d" , algorithm , algoCounts [algorithm ])))
188+ }
189+ return strings .Join (parts , ", " )
190+ }
191+
201192// TODO decide if this function handles date, time or datetime
202193func parseTimestamp (raw string ) string {
203194 raw = strings .TrimSpace (raw )
0 commit comments