style(panel): дашборд с рамкой, цветной статус-маркер, сгруппированное меню

UI консольной панели:
- Закрытая рамка из box-символов (╔═╗║╠╣╚╝) вместо голых ===; правый бордюр выровнен
  на всех строках (хелперы B-Top/B-Mid/B-Bot/B-Line + B-Status с цветным маркером ●).
- Статус-блок: ● зелёный РАБОТАЕТ / серый ОСТАНОВЛЕН, health цветом по состоянию,
  строка БД (размер · юзеры · миграция-номер · бэкапов), Node/JWT/LLM/время обновления, URL.
- Меню в две выровненные колонки СЕРВЕР | ОБСЛУЖИВАНИЕ (ключи голубые, подписи серые),
  отдельная строка ДИАГНОСТИКА; промпт с ▶.
Чистый рефактор отрисовки — логика switch/функции не тронуты. UTF-8 BOM, парсинг OK,
рендер-смоук показал ровное выравнивание.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-06-19 23:00:02 +03:00
parent 6eefb70ce7
commit 27f51f1a61
+65 -20
View File
@@ -68,7 +68,8 @@ $script:Stat = $null
function Refresh-Status {
$proc = Server-Proc
$s = @{ running = ($null -ne $proc); procId = '-'; uptime = '-'; health = '—'; healthMs = $null;
dbSize = '—'; users = '?'; migr = '-' }
dbSize = '—'; users = '?'; migr = '-'; at = (Get-Date -Format 'HH:mm:ss'); backups = 0 }
try { $s.backups = @(Get-ChildItem $script:BackupDir -Filter 'learnspace-*.db' -ErrorAction SilentlyContinue).Count } catch {}
if ($proc) {
$s.procId = $proc.Id
try { $u = (Get-Date) - $proc.StartTime; $s.uptime = ('{0}ч {1}м' -f [int]$u.TotalHours, $u.Minutes) } catch {}
@@ -205,6 +206,42 @@ function Run-Cmd($title, $block) {
[void](Read-Host ' [Enter] вернуться в меню')
}
# ── Рисование рамки/дашборда ──
$script:W = 62 # внутренняя ширина рамки (символов)
function B-Top { Write-Host (' ╔' + ('═' * ($script:W + 2)) + '╗') -ForegroundColor DarkCyan }
function B-Mid { Write-Host (' ╠' + ('═' * ($script:W + 2)) + '╣') -ForegroundColor DarkCyan }
function B-Bot { Write-Host (' ╚' + ('═' * ($script:W + 2)) + '╝') -ForegroundColor DarkCyan }
function B-Line($text, $color) {
$t = [string]$text
if ($t.Length -gt $script:W) { $t = $t.Substring(0, $script:W) }
Write-Host ' ║ ' -ForegroundColor DarkCyan -NoNewline
Write-Host $t.PadRight($script:W) -ForegroundColor $color -NoNewline
Write-Host ' ║' -ForegroundColor DarkCyan
}
# Строка с цветным маркером ● слева (статус). Маркер + пробел = 2 столбца.
function B-Status($markerColor, $text, $textColor) {
$avail = $script:W - 2
$t = [string]$text
if ($t.Length -gt $avail) { $t = $t.Substring(0, $avail) }
Write-Host ' ║ ' -ForegroundColor DarkCyan -NoNewline
Write-Host '●' -ForegroundColor $markerColor -NoNewline
Write-Host ' ' -NoNewline
Write-Host $t.PadRight($avail) -ForegroundColor $textColor -NoNewline
Write-Host ' ║' -ForegroundColor DarkCyan
}
# Заголовок секции меню (две колонки выравниваются под пунктами).
function Menu-Head($left, $right) { Write-Host (' ' + $left.PadRight(34) + $right) -ForegroundColor DarkCyan }
# Пункт меню в две колонки: ключ голубой, подпись серая.
function Menu-Row($lk, $ll, $rk, $rl) {
Write-Host ' ' -NoNewline
Write-Host $lk -ForegroundColor Cyan -NoNewline
Write-Host ((' ' + $ll).PadRight(31)) -ForegroundColor Gray -NoNewline
if ($rk) {
Write-Host $rk -ForegroundColor Cyan -NoNewline
Write-Host (' ' + $rl) -ForegroundColor Gray
} else { Write-Host '' }
}
# ── Неинтерактивные режимы (для start/stop-server.bat) ──
if ($Stop) { Stop-Server; Start-Sleep -Milliseconds 800; exit }
if ($Start) { Start-Server; Start-Sleep -Milliseconds 800; exit }
@@ -215,34 +252,41 @@ $run = $true
while ($run) {
Clear-Host
$s = $script:Stat
$bar = ' ' + ('=' * 58)
$migrShort = $s.migr; if ($s.migr -match '^(\d+)') { $migrShort = $matches[1] }
Write-Host ''
Write-Host $bar -ForegroundColor DarkCyan
Write-Host ' LearnSpace Панель управления' -ForegroundColor Cyan
Write-Host $bar -ForegroundColor DarkCyan
B-Top
B-Line 'LearnSpace · Панель управления сервером' Cyan
B-Mid
if ($s.running) {
Write-Host (' Статус: РАБОТАЕТ PID ' + $s.procId + ', порт ' + $script:Port + ', аптайм ' + $s.uptime) -ForegroundColor Green
B-Status Green ('РАБОТАЕТ PID ' + $s.procId + ' · порт ' + $script:Port + ' · аптайм ' + $s.uptime) Green
$hc = if ($s.health -eq 'healthy') { 'Green' } else { 'Yellow' }
$hm = if ($s.healthMs -ne $null) { " ($($s.healthMs) ms)" } else { '' }
Write-Host (' Health: ' + $s.health + $hm) -ForegroundColor $hc
B-Line ('Health: ' + $s.health + $hm) $hc
} else {
Write-Host ' Статус: остановлен' -ForegroundColor Yellow
B-Status DarkGray 'ОСТАНОВЛЕН' Yellow
}
Write-Host (' БД: ' + $s.dbSize + ', пользователей: ' + $s.users + ', миграций до: ' + $s.migr) -ForegroundColor Gray
Write-Host (' Node: ' + $script:NodeVer + ' | .env: CLIENT_ORIGIN=' + $script:EnvSum.origin + ', JWT_SECRET=' + $script:EnvSum.jwt + ', LLM=' + $script:EnvSum.llm) -ForegroundColor DarkGray
Write-Host (' URL: http://localhost:' + $script:Port) -ForegroundColor DarkGray
Write-Host $bar -ForegroundColor DarkCyan
B-Line ('БД ' + $s.dbSize + ' · пользователей: ' + $s.users + ' · миграция: ' + $migrShort + ' · бэкапов: ' + $s.backups) Gray
B-Line ('Node ' + $script:NodeVer + ' · JWT: ' + $script:EnvSum.jwt + ' · LLM: ' + $script:EnvSum.llm + ' · обновлено ' + $s.at) DarkGray
B-Line ('URL ' + $script:EnvSum.origin) DarkGray
B-Bot
Write-Host ''
Write-Host ' [1] Запустить сервер [6] Тесты (npm test)'
Write-Host ' [2] Остановить сервер [7] Проверка роутов (lint)'
Write-Host ' [3] Перезапустить [8] Открыть сайт в браузере'
Write-Host ' [4] Живые логи (окно) [9] Обновить статус'
Write-Host ' [5] Применить миграции [0] Выход'
Menu-Head 'СЕРВЕР' 'ОБСЛУЖИВАНИЕ'
Menu-Row '[1]' 'Запустить' '[B]' 'Бэкап БД'
Menu-Row '[2]' 'Остановить' '[R]' 'Восстановить БД'
Menu-Row '[3]' 'Перезапустить' '[A]' 'Создать админа'
Menu-Row '[4]' 'Живые логи (окно)' '[W]' 'Сторож (авто-рестарт)'
Menu-Row '[5]' 'Применить миграции' '[E]' 'Ошибки в логах'
Write-Host ''
Write-Host ' Данные и обслуживание:' -ForegroundColor DarkCyan
Write-Host ' [B] Бэкап БД [R] Восстановить БД [A] Создать админа [W] Сторож [E] Ошибки'
Menu-Head 'ДИАГНОСТИКА И ПРОЧЕЕ' ''
Write-Host ' ' -NoNewline
Write-Host '[6]' -ForegroundColor Cyan -NoNewline; Write-Host ' Тесты ' -ForegroundColor Gray -NoNewline
Write-Host '[7]' -ForegroundColor Cyan -NoNewline; Write-Host ' Lint роутов ' -ForegroundColor Gray -NoNewline
Write-Host '[8]' -ForegroundColor Cyan -NoNewline; Write-Host ' Сайт ' -ForegroundColor Gray -NoNewline
Write-Host '[9]' -ForegroundColor Cyan -NoNewline; Write-Host ' Обновить ' -ForegroundColor Gray -NoNewline
Write-Host '[0]' -ForegroundColor Cyan -NoNewline; Write-Host ' Выход' -ForegroundColor Gray
Write-Host ''
$c = (Read-Host ' Выбор').Trim().ToUpper()
Write-Host ' ▶ ' -ForegroundColor Cyan -NoNewline
$c = (Read-Host 'Выбор').Trim().ToUpper()
switch -Regex ($c) {
'^1$' { Start-Server; Refresh-Status; Start-Sleep 1 }
'^2$' { Stop-Server; Refresh-Status; Start-Sleep 1 }
@@ -267,3 +311,4 @@ Write-Host ' Панель закрыта. Сервер, если запуще
Write-Host ' Остановить позже: stop-server.bat или пункт [2] панели.' -ForegroundColor DarkGray