@@ -448,6 +448,8 @@ func NewAdminPanel(hub *Hub, db *sql.DB, pluginManager *manager.PluginManager, l
448448 selectedPlugin : - 1 ,
449449 }
450450
451+ panel .applyLayout (120 , 40 )
452+
451453 // Load initial data
452454 panel .refreshData ()
453455
@@ -788,16 +790,7 @@ func (ap *AdminPanel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
788790
789791 switch msg := msg .(type ) {
790792 case tea.WindowSizeMsg :
791- ap .width = msg .Width
792- ap .height = msg .Height
793-
794- availableWidth := msg .Width - 12
795- if availableWidth < 30 {
796- availableWidth = 30
797- }
798-
799- ap .help .Width = availableWidth
800- ap .userTable .SetWidth (availableWidth )
793+ ap .applyLayout (msg .Width , msg .Height )
801794
802795 case tea.KeyMsg :
803796 switch {
@@ -1055,15 +1048,40 @@ func (ap *AdminPanel) renderScrollableContent(content string, scrollOffset int)
10551048 return strings .Join (visibleLines , "\n " )
10561049}
10571050
1051+ func (ap * AdminPanel ) contentWidth () int {
1052+ width := ap .width - 12
1053+ if width < 30 {
1054+ return 30
1055+ }
1056+ return width
1057+ }
1058+
1059+ func (ap * AdminPanel ) applyLayout (width , height int ) {
1060+ ap .width = width
1061+ ap .height = height
1062+
1063+ contentWidth := ap .contentWidth ()
1064+ ap .help .Width = contentWidth
1065+ ap .userTable .SetWidth (contentWidth )
1066+ ap .pluginTable .SetWidth (contentWidth )
1067+
1068+ usableHeight := height - 16
1069+ if usableHeight < 6 {
1070+ usableHeight = 6
1071+ }
1072+ if usableHeight > 18 {
1073+ usableHeight = 18
1074+ }
1075+ ap .userTable .SetHeight (usableHeight )
1076+ ap .pluginTable .SetHeight (usableHeight )
1077+ }
1078+
10581079func (ap * AdminPanel ) View () string {
10591080 if ap .quitting {
10601081 return "Admin panel closed. Server continues running.\n "
10611082 }
10621083
1063- availableWidth := ap .width - 12
1064- if availableWidth < 30 {
1065- availableWidth = 30
1066- }
1084+ availableWidth := ap .contentWidth ()
10671085
10681086 doc := strings.Builder {}
10691087
@@ -1094,15 +1112,6 @@ func (ap *AdminPanel) View() string {
10941112func (ap * AdminPanel ) renderTabs () string {
10951113 var renderedTabs []string
10961114
1097- availableWidth := ap .width - 12
1098- if availableWidth < 30 {
1099- availableWidth = 30
1100- }
1101- tabWidth := availableWidth / len (ap .tabs )
1102- if tabWidth < 8 {
1103- tabWidth = 8
1104- }
1105-
11061115 for i , tab := range ap .tabs {
11071116 var style lipgloss.Style
11081117 if i == int (ap .activeTab ) {
@@ -1111,11 +1120,11 @@ func (ap *AdminPanel) renderTabs() string {
11111120 style = tabStyle
11121121 }
11131122
1114- renderedTab := style .Width ( tabWidth ). Align ( lipgloss . Center ). Render (tab )
1123+ renderedTab := style .Render (tab )
11151124 renderedTabs = append (renderedTabs , renderedTab )
11161125 }
11171126
1118- return lipgloss . JoinHorizontal ( lipgloss . Top , renderedTabs ... )
1127+ return strings . Join ( renderedTabs , " " )
11191128}
11201129
11211130func (ap * AdminPanel ) renderContent () string {
@@ -1140,13 +1149,11 @@ func (ap *AdminPanel) renderContent() string {
11401149func (ap * AdminPanel ) renderOverview () string {
11411150 doc := strings.Builder {}
11421151
1143- contentWidth := ap .width - 12
1144- if contentWidth < 30 {
1145- contentWidth = 30
1146- }
1152+ contentWidth := ap .contentWidth ()
11471153
11481154 // System status
1149- doc .WriteString (subtitleStyle .Width (contentWidth ).Render ("System Status\n " ))
1155+ doc .WriteString (subtitleStyle .Width (contentWidth ).Render ("System Status" ))
1156+ doc .WriteString ("\n " )
11501157 doc .WriteString (strings .Repeat ("─" , min (20 , contentWidth - 2 )) + "\n " )
11511158
11521159 statusText := "🟢 " + ap .systemInfo .ServerStatus
@@ -1162,7 +1169,8 @@ func (ap *AdminPanel) renderOverview() string {
11621169 doc .WriteString ("\n " )
11631170
11641171 // Live Configuration Summary
1165- doc .WriteString (subtitleStyle .Width (contentWidth ).Render ("Live Configuration\n " ))
1172+ doc .WriteString (subtitleStyle .Width (contentWidth ).Render ("Live Configuration" ))
1173+ doc .WriteString ("\n " )
11661174 doc .WriteString (strings .Repeat ("─" , min (20 , contentWidth - 2 )) + "\n " )
11671175 doc .WriteString (fmt .Sprintf ("Port: %d\n " , ap .config .Port ))
11681176
@@ -1181,7 +1189,8 @@ func (ap *AdminPanel) renderOverview() string {
11811189 doc .WriteString ("\n " )
11821190
11831191 // Database info
1184- doc .WriteString (subtitleStyle .Width (contentWidth ).Render ("Database Information\n " ))
1192+ doc .WriteString (subtitleStyle .Width (contentWidth ).Render ("Database Information" ))
1193+ doc .WriteString ("\n " )
11851194 doc .WriteString (strings .Repeat ("─" , min (20 , contentWidth - 2 )) + "\n " )
11861195 doc .WriteString (fmt .Sprintf ("Database Path: %s\n " , ap .config .DBPath ))
11871196 doc .WriteString (fmt .Sprintf ("Config Directory: %s\n " , ap .config .ConfigDir ))
@@ -1192,12 +1201,10 @@ func (ap *AdminPanel) renderOverview() string {
11921201func (ap * AdminPanel ) renderUsers () string {
11931202 doc := strings.Builder {}
11941203
1195- contentWidth := ap .width - 12
1196- if contentWidth < 30 {
1197- contentWidth = 30
1198- }
1204+ contentWidth := ap .contentWidth ()
11991205
1200- doc .WriteString (subtitleStyle .Width (contentWidth ).Render ("User Management\n " ))
1206+ doc .WriteString (subtitleStyle .Width (contentWidth ).Render ("User Management" ))
1207+ doc .WriteString ("\n " )
12011208 doc .WriteString (strings .Repeat ("─" , min (20 , contentWidth - 2 )) + "\n " )
12021209
12031210 // Show selected user info
@@ -1237,18 +1244,18 @@ func (ap *AdminPanel) renderUsers() string {
12371244func (ap * AdminPanel ) renderSystem () string {
12381245 doc := strings.Builder {}
12391246
1240- contentWidth := ap .width - 12
1241- if contentWidth < 30 {
1242- contentWidth = 30
1243- }
1247+ contentWidth := ap .contentWidth ()
12441248
1245- doc .WriteString (subtitleStyle .Width (contentWidth ).Render ("System Management\n " ))
1249+ doc .WriteString (subtitleStyle .Width (contentWidth ).Render ("System Management" ))
1250+ doc .WriteString ("\n " )
12461251 doc .WriteString (strings .Repeat ("─" , min (20 , contentWidth - 2 )) + "\n " )
12471252
1248- doc .WriteString (infoStylePanel .Render ("Use [c] Clear Database, [b] Backup Database, [s] Show Stats\n \n " ))
1253+ doc .WriteString (infoStylePanel .Render ("Use [c] Clear Database, [b] Backup Database, [s] Show Stats" ))
1254+ doc .WriteString ("\n \n " )
12491255
12501256 // Live Configuration Details
1251- doc .WriteString (subtitleStyle .Render ("Live Configuration:\n " ))
1257+ doc .WriteString (subtitleStyle .Render ("Live Configuration:" ))
1258+ doc .WriteString ("\n " )
12521259 doc .WriteString (fmt .Sprintf (" Server Port: %d\n " , ap .config .Port ))
12531260 doc .WriteString (fmt .Sprintf (" Database: %s\n " , ap .config .DBPath ))
12541261 doc .WriteString (fmt .Sprintf (" Config Directory: %s\n " , ap .config .ConfigDir ))
@@ -1276,7 +1283,8 @@ func (ap *AdminPanel) renderSystem() string {
12761283 doc .WriteString (fmt .Sprintf (" Plugin Registry: %s\n " , ap .config .PluginRegistryURL ))
12771284
12781285 doc .WriteString ("\n " )
1279- doc .WriteString (subtitleStyle .Render ("Database Statistics:\n " ))
1286+ doc .WriteString (subtitleStyle .Render ("Database Statistics:" ))
1287+ doc .WriteString ("\n " )
12801288 doc .WriteString (fmt .Sprintf (" Total Messages: %d\n " , ap .systemInfo .MessagesSent ))
12811289 doc .WriteString (fmt .Sprintf (" Total Users: %d\n " , ap .systemInfo .TotalUsers ))
12821290 doc .WriteString (fmt .Sprintf (" Active Connections: %d\n " , ap .systemInfo .ActiveUsers ))
@@ -1288,16 +1296,15 @@ func (ap *AdminPanel) renderSystem() string {
12881296func (ap * AdminPanel ) renderLogs () string {
12891297 doc := strings.Builder {}
12901298
1291- contentWidth := ap .width - 12
1292- if contentWidth < 30 {
1293- contentWidth = 30
1294- }
1299+ contentWidth := ap .contentWidth ()
12951300
1296- doc .WriteString (subtitleStyle .Width (contentWidth ).Render ("System Logs\n " ))
1301+ doc .WriteString (subtitleStyle .Width (contentWidth ).Render ("System Logs" ))
1302+ doc .WriteString ("\n " )
12971303 doc .WriteString (strings .Repeat ("─" , min (20 , contentWidth - 2 )) + "\n " )
12981304
1299- // Add logs content
1300- for _ , logEntry := range ap .logs {
1305+ // Add logs content (oldest first so newest appear at the bottom)
1306+ for i := len (ap .logs ) - 1 ; i >= 0 ; i -- {
1307+ logEntry := ap .logs [i ]
13011308 var levelStyle lipgloss.Style
13021309 switch logEntry .Level {
13031310 case "ERROR" :
@@ -1313,19 +1320,30 @@ func (ap *AdminPanel) renderLogs() string {
13131320 logEntry .Component ,
13141321 logEntry .Message ))
13151322 }
1323+ content := doc .String ()
1324+ lines := strings .Split (content , "\n " )
1325+ availableHeight := ap .height - 8
1326+ if availableHeight < 10 {
1327+ availableHeight = 10
1328+ }
1329+ maxLines := availableHeight - 2
1330+ if maxLines < 1 {
1331+ maxLines = 1
1332+ }
1333+ if ap .logsScroll == 0 && len (lines ) > maxLines {
1334+ ap .logsScroll = len (lines ) - maxLines
1335+ }
13161336
1317- return ap .renderScrollableContent (doc . String () , ap .logsScroll )
1337+ return ap .renderScrollableContent (content , ap .logsScroll )
13181338}
13191339
13201340func (ap * AdminPanel ) renderPlugins () string {
13211341 doc := strings.Builder {}
13221342
1323- contentWidth := ap .width - 12
1324- if contentWidth < 30 {
1325- contentWidth = 30
1326- }
1343+ contentWidth := ap .contentWidth ()
13271344
1328- doc .WriteString (subtitleStyle .Width (contentWidth ).Render ("Plugin Management\n " ))
1345+ doc .WriteString (subtitleStyle .Width (contentWidth ).Render ("Plugin Management" ))
1346+ doc .WriteString ("\n " )
13291347 doc .WriteString (strings .Repeat ("─" , min (20 , contentWidth - 2 )) + "\n " )
13301348
13311349 // Show selected plugin info
@@ -1400,18 +1418,18 @@ func (ap *AdminPanel) renderPlugins() string {
14001418func (ap * AdminPanel ) renderMetrics () string {
14011419 doc := strings.Builder {}
14021420
1403- contentWidth := ap .width - 12
1404- if contentWidth < 30 {
1405- contentWidth = 30
1406- }
1421+ contentWidth := ap .contentWidth ()
14071422
1408- doc .WriteString (subtitleStyle .Width (contentWidth ).Render ("Performance Metrics\n " ))
1423+ doc .WriteString (subtitleStyle .Width (contentWidth ).Render ("Performance Metrics" ))
1424+ doc .WriteString ("\n " )
14091425 doc .WriteString (strings .Repeat ("─" , min (20 , contentWidth - 2 )) + "\n " )
14101426
1411- doc .WriteString (infoStylePanel .Render ("Use [G] Force GC, [R] Reset Metrics, [E] Export Logs\n \n " ))
1427+ doc .WriteString (infoStylePanel .Render ("Use [G] Force GC, [R] Reset Metrics, [E] Export Logs" ))
1428+ doc .WriteString ("\n \n " )
14121429
14131430 // System Performance - more compact layout
1414- doc .WriteString (metricLabelStyle .Render ("System Performance:\n " ))
1431+ doc .WriteString (metricLabelStyle .Render ("System Performance:" ))
1432+ doc .WriteString ("\n " )
14151433 doc .WriteString (fmt .Sprintf ("Memory: %s | Goroutines: %s | Heap: %s\n " ,
14161434 metricValueStyle .Render (fmt .Sprintf ("%.1f MB" , ap .systemInfo .MemoryUsage )),
14171435 metricValueStyle .Render (fmt .Sprintf ("%d" , ap .systemInfo .GoroutineCount )),
@@ -1423,7 +1441,8 @@ func (ap *AdminPanel) renderMetrics() string {
14231441 doc .WriteString ("\n " )
14241442
14251443 // Connection Metrics - more compact layout
1426- doc .WriteString (metricLabelStyle .Render ("Connection Metrics:\n " ))
1444+ doc .WriteString (metricLabelStyle .Render ("Connection Metrics:" ))
1445+ doc .WriteString ("\n " )
14271446 doc .WriteString (fmt .Sprintf ("Active: %s | Peak: %s | Total: %s | Disconnects: %s\n " ,
14281447 metricValueStyle .Render (fmt .Sprintf ("%d" , ap .systemInfo .ActiveUsers )),
14291448 metricValueStyle .Render (fmt .Sprintf ("%d" , ap .metrics .PeakUsers )),
@@ -1433,7 +1452,8 @@ func (ap *AdminPanel) renderMetrics() string {
14331452 doc .WriteString ("\n " )
14341453
14351454 // Message Metrics - more compact layout
1436- doc .WriteString (metricLabelStyle .Render ("Message Metrics:\n " ))
1455+ doc .WriteString (metricLabelStyle .Render ("Message Metrics:" ))
1456+ doc .WriteString ("\n " )
14371457 doc .WriteString (fmt .Sprintf ("Total: %s | Rate: %s | Conn Rate: %s\n " ,
14381458 metricValueStyle .Render (fmt .Sprintf ("%d" , ap .systemInfo .MessagesSent )),
14391459 metricValueStyle .Render (fmt .Sprintf ("%.2f msg/s" , ap .messageRate )),
@@ -1443,7 +1463,8 @@ func (ap *AdminPanel) renderMetrics() string {
14431463
14441464 // Memory History Chart (simplified)
14451465 if len (ap .metrics .MemoryHistory ) > 0 {
1446- doc .WriteString (metricLabelStyle .Render ("Memory History (last 5):\n " ))
1466+ doc .WriteString (metricLabelStyle .Render ("Memory History (last 5):" ))
1467+ doc .WriteString ("\n " )
14471468 recent := ap .metrics .MemoryHistory
14481469 if len (recent ) > 5 {
14491470 recent = recent [len (recent )- 5 :]
@@ -1459,7 +1480,8 @@ func (ap *AdminPanel) renderMetrics() string {
14591480
14601481 // Connection History Chart (simplified)
14611482 if len (ap .metrics .ConnectionHistory ) > 0 {
1462- doc .WriteString (metricLabelStyle .Render ("Connection History (last 5):\n " ))
1483+ doc .WriteString (metricLabelStyle .Render ("Connection History (last 5):" ))
1484+ doc .WriteString ("\n " )
14631485 recent := ap .metrics .ConnectionHistory
14641486 if len (recent ) > 5 {
14651487 recent = recent [len (recent )- 5 :]
0 commit comments