feat(panel): бэкап/восстановление БД, умный статус, создать админа, watchdog
control-panel.ps1 расширена: - Бэкап БД [B] (копия learnspace.db+wal/shm с датой в data/backups) и восстановление [R] (выбор из списка, страховочная копия .pre-restore, авто-стоп/старт сервера). - Умный статус: health-пинг /api/health (+ms), размер БД, кол-во пользователей, последняя миграция (db-status.js), версия Node, сводка .env (CLIENT_ORIGIN/JWT/LLM). Кэш в . - Создать админа [A] → scripts/create-admin.js (bcrypt, upsert role=admin, busy_timeout). - Сторож [W]: авто-перезапуск при падении (выход по клавише). Логи в backend/logs (не %TEMP%), [E] ошибки из логов. Фиксы PS 5.1: порт/путь БД читаются из .env (inline node -e с кавычками 5.1 ломает); db-status вынесен в файл-скрипт; миграция по filename DESC; UTF-8 BOM, парсинг OK. Меню — лат.+рус. клавиши. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,42 @@
|
|||||||
|
'use strict';
|
||||||
|
/* create-admin.js — создать или повысить пользователя до администратора.
|
||||||
|
*
|
||||||
|
* Запуск (значения через переменные окружения, чтобы пароль не светился в argv):
|
||||||
|
* ADMIN_EMAIL=a@b.c ADMIN_PASSWORD=secret12 ADMIN_NAME="Имя" node scripts/create-admin.js
|
||||||
|
* Используется панелью управления (control-panel.ps1, пункт «Создать админа»).
|
||||||
|
*
|
||||||
|
* Если пользователь с таким email уже есть — обновляет пароль/имя, ставит role='admin'
|
||||||
|
* и инкрементит token_version (старые токены становятся недействительны). Иначе создаёт.
|
||||||
|
*/
|
||||||
|
const path = require('path');
|
||||||
|
const bcrypt = require('bcryptjs');
|
||||||
|
const { DatabaseSync } = require('node:sqlite');
|
||||||
|
|
||||||
|
const email = String(process.env.ADMIN_EMAIL || '').trim().toLowerCase();
|
||||||
|
const password = String(process.env.ADMIN_PASSWORD || '');
|
||||||
|
const name = String(process.env.ADMIN_NAME || '').trim() || 'Администратор';
|
||||||
|
|
||||||
|
function fail(msg) { console.error('✗ ' + msg); process.exit(1); }
|
||||||
|
|
||||||
|
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) fail('Некорректный email (ADMIN_EMAIL).');
|
||||||
|
if (password.length < 8) fail('Пароль минимум 8 символов (ADMIN_PASSWORD).');
|
||||||
|
|
||||||
|
const DB = process.env.DB_PATH || path.join(__dirname, '..', 'data', 'learnspace.db');
|
||||||
|
const db = new DatabaseSync(DB);
|
||||||
|
try { db.exec('PRAGMA busy_timeout=4000'); } catch (_) {} // подождать, если сервер пишет
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const hash = await bcrypt.hash(password, 12);
|
||||||
|
const existing = db.prepare('SELECT id, role FROM users WHERE email = ?').get(email);
|
||||||
|
if (existing) {
|
||||||
|
db.prepare(`UPDATE users SET password_hash = ?, name = ?, role = 'admin',
|
||||||
|
token_version = COALESCE(token_version,0) + 1 WHERE id = ?`)
|
||||||
|
.run(hash, name, existing.id);
|
||||||
|
console.log(`✓ Пользователь ${email} обновлён: роль admin, новый пароль (id=${existing.id}).`);
|
||||||
|
} else {
|
||||||
|
const r = db.prepare(`INSERT INTO users (email, password_hash, name, role) VALUES (?,?,?, 'admin')`)
|
||||||
|
.run(email, hash, name);
|
||||||
|
console.log(`✓ Создан администратор ${email} (id=${r.lastInsertRowid}).`);
|
||||||
|
}
|
||||||
|
db.close();
|
||||||
|
})().catch(e => fail(e.message));
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
'use strict';
|
||||||
|
/* db-status.js — краткая сводка БД для панели управления: "<users>|<lastMigration>".
|
||||||
|
Путь к БД: env DB_PATH, либо argv[2], либо стандартный. Никогда не бросает. */
|
||||||
|
const path = require('path');
|
||||||
|
const { DatabaseSync } = require('node:sqlite');
|
||||||
|
|
||||||
|
const DB = process.env.DB_PATH || process.argv[2] || path.join(__dirname, '..', 'data', 'learnspace.db');
|
||||||
|
let users = '?', migr = '-';
|
||||||
|
try {
|
||||||
|
const db = new DatabaseSync(DB);
|
||||||
|
try { users = db.prepare('SELECT COUNT(*) AS n FROM users').get().n; } catch (_) {}
|
||||||
|
try { const r = db.prepare('SELECT filename FROM _migrations ORDER BY filename DESC LIMIT 1').get(); if (r) migr = r.filename; } catch (_) {}
|
||||||
|
db.close();
|
||||||
|
} catch (_) {}
|
||||||
|
process.stdout.write(String(users) + '|' + String(migr));
|
||||||
+173
-38
@@ -12,19 +12,49 @@ try { [Console]::OutputEncoding = [System.Text.UTF8Encoding]::new() } catch {}
|
|||||||
|
|
||||||
$root = Split-Path -Parent $PSScriptRoot
|
$root = Split-Path -Parent $PSScriptRoot
|
||||||
$backend = Join-Path $root 'backend'
|
$backend = Join-Path $root 'backend'
|
||||||
$log = Join-Path $env:TEMP 'learnspace-server.log'
|
$logsDir = Join-Path $backend 'logs'
|
||||||
$errlog = Join-Path $env:TEMP 'learnspace-server.err.log'
|
$log = Join-Path $logsDir 'server.log'
|
||||||
|
$errlog = Join-Path $logsDir 'server.err.log'
|
||||||
Set-Location $backend
|
Set-Location $backend
|
||||||
|
|
||||||
|
# Порт читаем из .env (PORT=...), иначе 3000. (node -e с кавычками PS 5.1 ломает — не используем.)
|
||||||
function Get-Port {
|
function Get-Port {
|
||||||
try {
|
$envf = Join-Path $backend '.env'
|
||||||
$p = & node -e 'try{process.stdout.write(String(require("./src/config").PORT||3000))}catch(e){process.stdout.write("3000")}' 2>$null
|
if (Test-Path $envf) {
|
||||||
if ("$p" -match '^\d+$') { return [int]"$p" }
|
$m = Select-String -Path $envf -Pattern '^\s*PORT\s*=\s*(\d+)' | Select-Object -First 1
|
||||||
} catch {}
|
if ($m) { return [int]$m.Matches[0].Groups[1].Value }
|
||||||
|
}
|
||||||
return 3000
|
return 3000
|
||||||
}
|
}
|
||||||
$script:Port = Get-Port
|
$script:Port = Get-Port
|
||||||
|
|
||||||
|
# Путь к БД (из .env DB_PATH, иначе стандартный) и папка бэкапов.
|
||||||
|
function Get-DbPath {
|
||||||
|
$envf = Join-Path $backend '.env'
|
||||||
|
if (Test-Path $envf) {
|
||||||
|
$m = Select-String -Path $envf -Pattern '^\s*DB_PATH\s*=\s*(.+)$' | Select-Object -First 1
|
||||||
|
if ($m) { $v = $m.Matches[0].Groups[1].Value.Trim(); if ($v) { return $v } }
|
||||||
|
}
|
||||||
|
return (Join-Path $backend 'data\learnspace.db')
|
||||||
|
}
|
||||||
|
$script:DbPath = Get-DbPath
|
||||||
|
$script:BackupDir = Join-Path $backend 'data\backups'
|
||||||
|
$script:NodeVer = '?'
|
||||||
|
try { $script:NodeVer = (& node -v) } catch {}
|
||||||
|
|
||||||
|
# Сводка .env (кэш).
|
||||||
|
$script:EnvSum = @{ origin = '—'; jwt = 'нет'; llm = 'нет' }
|
||||||
|
function Load-EnvSummary {
|
||||||
|
$f = Join-Path $backend '.env'
|
||||||
|
if (-not (Test-Path $f)) { return }
|
||||||
|
foreach ($line in (Get-Content $f)) {
|
||||||
|
if ($line -match '^\s*CLIENT_ORIGIN\s*=\s*(.+)$') { $script:EnvSum.origin = $matches[1].Trim() }
|
||||||
|
if ($line -match '^\s*JWT_SECRET\s*=\s*(\S.*)$') { $script:EnvSum.jwt = 'задан' }
|
||||||
|
if ($line -match '^\s*ASSISTANT_LLM_KEY\s*=\s*(\S.*)$') { $script:EnvSum.llm = 'задан' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Load-EnvSummary
|
||||||
|
|
||||||
function Server-Proc {
|
function Server-Proc {
|
||||||
try {
|
try {
|
||||||
$c = Get-NetTCPConnection -LocalPort $script:Port -State Listen -ErrorAction Stop | Select-Object -First 1
|
$c = Get-NetTCPConnection -LocalPort $script:Port -State Listen -ErrorAction Stop | Select-Object -First 1
|
||||||
@@ -33,18 +63,34 @@ function Server-Proc {
|
|||||||
return $null
|
return $null
|
||||||
}
|
}
|
||||||
|
|
||||||
function Status-Line {
|
# Полный статус (процесс + health + БД), кэшируется в $script:Stat.
|
||||||
$p = Server-Proc
|
$script:Stat = $null
|
||||||
if ($p) {
|
function Refresh-Status {
|
||||||
$ups = '?'
|
$proc = Server-Proc
|
||||||
try { $u = (Get-Date) - $p.StartTime; $ups = ('{0}ч {1}м' -f [int]$u.TotalHours, $u.Minutes) } catch {}
|
$s = @{ running = ($null -ne $proc); procId = '-'; uptime = '-'; health = '—'; healthMs = $null;
|
||||||
return ('РАБОТАЕТ — PID ' + $p.Id + ', порт ' + $script:Port + ', аптайм ' + $ups)
|
dbSize = '—'; users = '?'; migr = '-' }
|
||||||
|
if ($proc) {
|
||||||
|
$s.procId = $proc.Id
|
||||||
|
try { $u = (Get-Date) - $proc.StartTime; $s.uptime = ('{0}ч {1}м' -f [int]$u.TotalHours, $u.Minutes) } catch {}
|
||||||
|
$sw = [System.Diagnostics.Stopwatch]::StartNew()
|
||||||
|
try {
|
||||||
|
Invoke-WebRequest ("http://localhost:{0}/api/health" -f $script:Port) -UseBasicParsing -TimeoutSec 2 | Out-Null
|
||||||
|
$sw.Stop(); $s.health = 'healthy'; $s.healthMs = [int]$sw.ElapsedMilliseconds
|
||||||
|
} catch { $sw.Stop(); $s.health = 'не отвечает' }
|
||||||
}
|
}
|
||||||
return 'остановлен'
|
if (Test-Path $script:DbPath) {
|
||||||
|
$s.dbSize = ('{0:N1} МБ' -f ((Get-Item $script:DbPath).Length / 1MB))
|
||||||
|
try {
|
||||||
|
$out = & node scripts/db-status.js "$($script:DbPath)" 2>$null
|
||||||
|
$parts = "$out".Split('|'); if ($parts.Count -ge 2) { $s.users = $parts[0]; $s.migr = $parts[1] }
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
$script:Stat = $s
|
||||||
}
|
}
|
||||||
|
|
||||||
function Start-Server {
|
function Start-Server {
|
||||||
if (Server-Proc) { Write-Host ' Сервер уже работает.' -ForegroundColor Yellow; return }
|
if (Server-Proc) { Write-Host ' Сервер уже работает.' -ForegroundColor Yellow; return }
|
||||||
|
New-Item -ItemType Directory -Force -Path $logsDir | Out-Null
|
||||||
Write-Host ' Применяю миграции (идемпотентно)...' -ForegroundColor DarkGray
|
Write-Host ' Применяю миграции (идемпотентно)...' -ForegroundColor DarkGray
|
||||||
try { & node src/db/migrations-runner.js | Out-Host } catch { Write-Host (' Миграции: ' + $_.Exception.Message) -ForegroundColor Red }
|
try { & node src/db/migrations-runner.js | Out-Host } catch { Write-Host (' Миграции: ' + $_.Exception.Message) -ForegroundColor Red }
|
||||||
Remove-Item $log, $errlog -ErrorAction SilentlyContinue
|
Remove-Item $log, $errlog -ErrorAction SilentlyContinue
|
||||||
@@ -62,7 +108,7 @@ function Stop-Server {
|
|||||||
$p = Server-Proc
|
$p = Server-Proc
|
||||||
if (-not $p) { Write-Host ' Сервер не запущен.' -ForegroundColor Yellow; return }
|
if (-not $p) { Write-Host ' Сервер не запущен.' -ForegroundColor Yellow; return }
|
||||||
Stop-Process -Id $p.Id -Force -ErrorAction SilentlyContinue
|
Stop-Process -Id $p.Id -Force -ErrorAction SilentlyContinue
|
||||||
Start-Sleep -Milliseconds 500
|
Start-Sleep -Milliseconds 600
|
||||||
Write-Host (' Сервер остановлен (PID ' + $p.Id + ').') -ForegroundColor Green
|
Write-Host (' Сервер остановлен (PID ' + $p.Id + ').') -ForegroundColor Green
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,11 +117,85 @@ function Open-Logs {
|
|||||||
Write-Host ' Логов нет: сервер не запускался через панель. Запустите его пунктом [1] или [3].' -ForegroundColor Yellow
|
Write-Host ' Логов нет: сервер не запускался через панель. Запустите его пунктом [1] или [3].' -ForegroundColor Yellow
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
$cmd = "`$Host.UI.RawUI.WindowTitle='LearnSpace — живые логи'; Write-Host 'Живые логи сервера (закройте окно, чтобы прекратить просмотр):' -ForegroundColor Cyan; Get-Content -Path '$log' -Wait -Tail 50"
|
$cmd = "`$Host.UI.RawUI.WindowTitle='LearnSpace — живые логи'; Write-Host 'Живые логи (закройте окно, чтобы прекратить просмотр):' -ForegroundColor Cyan; Get-Content -Path '$log' -Wait -Tail 50"
|
||||||
Start-Process powershell -ArgumentList '-NoProfile', '-NoExit', '-Command', $cmd
|
Start-Process powershell -ArgumentList '-NoProfile', '-NoExit', '-Command', $cmd
|
||||||
Write-Host ' Логи открыты в отдельном окне.' -ForegroundColor Green
|
Write-Host ' Логи открыты в отдельном окне.' -ForegroundColor Green
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Show-Errors {
|
||||||
|
$lines = @()
|
||||||
|
if (Test-Path $errlog) { $lines += (Get-Content $errlog -Tail 20 | Where-Object { $_ -and ($_ -notmatch 'ExperimentalWarning|trace-warnings') }) }
|
||||||
|
if (Test-Path $log) { $lines += (Get-Content $log -Tail 300 | Select-String -Pattern 'ERROR|FATAL|✗|Unhandled|Error:' | Select-Object -Last 15 | ForEach-Object { $_.Line }) }
|
||||||
|
Write-Host ''
|
||||||
|
if (-not $lines -or $lines.Count -eq 0) { Write-Host ' Ошибок в логах не найдено.' -ForegroundColor Green; return }
|
||||||
|
$lines | ForEach-Object { Write-Host (' ' + $_) -ForegroundColor Red }
|
||||||
|
}
|
||||||
|
|
||||||
|
function Backup-Db {
|
||||||
|
if (-not (Test-Path $script:DbPath)) { Write-Host ' БД не найдена.' -ForegroundColor Yellow; return }
|
||||||
|
New-Item -ItemType Directory -Force -Path $script:BackupDir | Out-Null
|
||||||
|
$ts = Get-Date -Format 'yyyyMMdd-HHmmss'
|
||||||
|
$dest = Join-Path $script:BackupDir ("learnspace-$ts.db")
|
||||||
|
Copy-Item $script:DbPath $dest -Force
|
||||||
|
foreach ($ext in '-wal', '-shm') { $sp = $script:DbPath + $ext; if (Test-Path $sp) { Copy-Item $sp ($dest + $ext) -Force } }
|
||||||
|
$sz = '{0:N1} МБ' -f ((Get-Item $dest).Length / 1MB)
|
||||||
|
Write-Host (' Бэкап создан: ' + (Split-Path $dest -Leaf) + " ($sz)") -ForegroundColor Green
|
||||||
|
Write-Host (' Папка: ' + $script:BackupDir) -ForegroundColor DarkGray
|
||||||
|
if (Server-Proc) { Write-Host ' (сервер работает — для 100% консистентности лучше делать на остановленном)' -ForegroundColor DarkGray }
|
||||||
|
}
|
||||||
|
|
||||||
|
function Restore-Db {
|
||||||
|
if (-not (Test-Path $script:BackupDir)) { Write-Host ' Папки бэкапов нет — сначала сделайте бэкап ([B]).' -ForegroundColor Yellow; return }
|
||||||
|
$files = @(Get-ChildItem $script:BackupDir -Filter 'learnspace-*.db' -ErrorAction SilentlyContinue | Sort-Object LastWriteTime -Descending)
|
||||||
|
if ($files.Count -eq 0) { Write-Host ' Бэкапов нет.' -ForegroundColor Yellow; return }
|
||||||
|
Write-Host ''
|
||||||
|
for ($i = 0; $i -lt $files.Count; $i++) {
|
||||||
|
Write-Host (' [{0}] {1} ({2:N1} МБ, {3:yyyy-MM-dd HH:mm})' -f ($i + 1), $files[$i].Name, ($files[$i].Length / 1MB), $files[$i].LastWriteTime)
|
||||||
|
}
|
||||||
|
$sel = Read-Host ' Номер бэкапа (Enter = отмена)'
|
||||||
|
if (-not ($sel -match '^\d+$')) { Write-Host ' Отменено.'; return }
|
||||||
|
$idx = [int]$sel - 1
|
||||||
|
if ($idx -lt 0 -or $idx -ge $files.Count) { Write-Host ' Неверный номер.' -ForegroundColor Yellow; return }
|
||||||
|
if ((Read-Host (' Перезаписать ТЕКУЩУЮ БД из ' + $files[$idx].Name + '? Текущая будет утеряна [y/N]')) -notmatch '^[YyНн]') { Write-Host ' Отменено.'; return }
|
||||||
|
$wasRunning = [bool](Server-Proc)
|
||||||
|
if ($wasRunning) { Stop-Server }
|
||||||
|
if (Test-Path $script:DbPath) { Copy-Item $script:DbPath ($script:DbPath + '.pre-restore') -Force } # страховка
|
||||||
|
Copy-Item $files[$idx].FullName $script:DbPath -Force
|
||||||
|
foreach ($ext in '-wal', '-shm') {
|
||||||
|
$sp = $script:DbPath + $ext; if (Test-Path $sp) { Remove-Item $sp -Force }
|
||||||
|
$bsrc = $files[$idx].FullName + $ext; if (Test-Path $bsrc) { Copy-Item $bsrc $sp -Force }
|
||||||
|
}
|
||||||
|
Write-Host ' БД восстановлена (прежняя сохранена рядом как *.pre-restore).' -ForegroundColor Green
|
||||||
|
if ($wasRunning) { Start-Server }
|
||||||
|
}
|
||||||
|
|
||||||
|
function Create-Admin {
|
||||||
|
$em = Read-Host ' Email админа'
|
||||||
|
if (-not $em.Trim()) { Write-Host ' Отменено.'; return }
|
||||||
|
$nm = Read-Host ' Имя (Enter = «Администратор»)'
|
||||||
|
$sec = Read-Host ' Пароль (мин. 8 символов)' -AsSecureString
|
||||||
|
$pw = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($sec))
|
||||||
|
if ($pw.Length -lt 8) { Write-Host ' Пароль слишком короткий (мин. 8).' -ForegroundColor Yellow; return }
|
||||||
|
$env:ADMIN_EMAIL = $em.Trim(); $env:ADMIN_PASSWORD = $pw; $env:ADMIN_NAME = $nm.Trim(); $env:DB_PATH = $script:DbPath
|
||||||
|
try { & node scripts/create-admin.js } catch { Write-Host (' Ошибка: ' + $_.Exception.Message) -ForegroundColor Red }
|
||||||
|
finally { Remove-Item Env:ADMIN_EMAIL, Env:ADMIN_PASSWORD, Env:ADMIN_NAME -ErrorAction SilentlyContinue }
|
||||||
|
}
|
||||||
|
|
||||||
|
function Watchdog {
|
||||||
|
Clear-Host
|
||||||
|
Write-Host ' Режим «Сторож»: проверка каждые ~8 с, авто-перезапуск при падении.' -ForegroundColor Cyan
|
||||||
|
Write-Host ' Нажмите любую клавишу для выхода в меню.' -ForegroundColor DarkGray
|
||||||
|
Write-Host ''
|
||||||
|
while (-not [Console]::KeyAvailable) {
|
||||||
|
$t = Get-Date -Format 'HH:mm:ss'
|
||||||
|
$p = Server-Proc
|
||||||
|
if ($p) { Write-Host (" [$t] работает (PID $($p.Id))") -ForegroundColor DarkGray }
|
||||||
|
else { Write-Host (" [$t] СЕРВЕР УПАЛ — поднимаю...") -ForegroundColor Yellow; Start-Server }
|
||||||
|
for ($i = 0; $i -lt 16; $i++) { if ([Console]::KeyAvailable) { break }; Start-Sleep -Milliseconds 500 }
|
||||||
|
}
|
||||||
|
[void][Console]::ReadKey($true)
|
||||||
|
}
|
||||||
|
|
||||||
function Run-Cmd($title, $block) {
|
function Run-Cmd($title, $block) {
|
||||||
Write-Host ''
|
Write-Host ''
|
||||||
Write-Host (' >>> ' + $title) -ForegroundColor Cyan
|
Write-Host (' >>> ' + $title) -ForegroundColor Cyan
|
||||||
@@ -90,41 +210,56 @@ if ($Stop) { Stop-Server; Start-Sleep -Milliseconds 800; exit }
|
|||||||
if ($Start) { Start-Server; Start-Sleep -Milliseconds 800; exit }
|
if ($Start) { Start-Server; Start-Sleep -Milliseconds 800; exit }
|
||||||
|
|
||||||
# ── Меню ──
|
# ── Меню ──
|
||||||
|
Refresh-Status
|
||||||
$run = $true
|
$run = $true
|
||||||
while ($run) {
|
while ($run) {
|
||||||
Clear-Host
|
Clear-Host
|
||||||
$bar = ' ' + ('=' * 56)
|
$s = $script:Stat
|
||||||
|
$bar = ' ' + ('=' * 58)
|
||||||
Write-Host ''
|
Write-Host ''
|
||||||
Write-Host $bar -ForegroundColor DarkCyan
|
Write-Host $bar -ForegroundColor DarkCyan
|
||||||
Write-Host ' LearnSpace — Панель управления' -ForegroundColor Cyan
|
Write-Host ' LearnSpace — Панель управления' -ForegroundColor Cyan
|
||||||
Write-Host $bar -ForegroundColor DarkCyan
|
Write-Host $bar -ForegroundColor DarkCyan
|
||||||
$st = Status-Line
|
if ($s.running) {
|
||||||
if ($st -like 'РАБОТАЕТ*') { Write-Host (' Статус: ' + $st) -ForegroundColor Green }
|
Write-Host (' Статус: РАБОТАЕТ — PID ' + $s.procId + ', порт ' + $script:Port + ', аптайм ' + $s.uptime) -ForegroundColor Green
|
||||||
else { Write-Host (' Статус: ' + $st) -ForegroundColor Yellow }
|
$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
|
||||||
|
} else {
|
||||||
|
Write-Host ' Статус: остановлен' -ForegroundColor 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 (' URL: http://localhost:' + $script:Port) -ForegroundColor DarkGray
|
||||||
Write-Host (' Папка: ' + $backend) -ForegroundColor DarkGray
|
|
||||||
Write-Host $bar -ForegroundColor DarkCyan
|
Write-Host $bar -ForegroundColor DarkCyan
|
||||||
Write-Host ''
|
Write-Host ''
|
||||||
Write-Host ' [1] Запустить сервер [5] Применить миграции'
|
Write-Host ' [1] Запустить сервер [6] Тесты (npm test)'
|
||||||
Write-Host ' [2] Остановить сервер [6] Тесты (npm test)'
|
Write-Host ' [2] Остановить сервер [7] Проверка роутов (lint)'
|
||||||
Write-Host ' [3] Перезапустить [7] Проверка роутов (lint)'
|
Write-Host ' [3] Перезапустить [8] Открыть сайт в браузере'
|
||||||
Write-Host ' [4] Живые логи (окно) [8] Открыть сайт в браузере'
|
Write-Host ' [4] Живые логи (окно) [9] Обновить статус'
|
||||||
|
Write-Host ' [5] Применить миграции [0] Выход'
|
||||||
Write-Host ''
|
Write-Host ''
|
||||||
Write-Host ' [9] Обновить статус [0] Выход'
|
Write-Host ' Данные и обслуживание:' -ForegroundColor DarkCyan
|
||||||
|
Write-Host ' [B] Бэкап БД [R] Восстановить БД [A] Создать админа [W] Сторож [E] Ошибки'
|
||||||
Write-Host ''
|
Write-Host ''
|
||||||
$c = Read-Host ' Выбор'
|
$c = (Read-Host ' Выбор').Trim().ToUpper()
|
||||||
switch ($c.Trim()) {
|
switch -Regex ($c) {
|
||||||
'1' { Start-Server; Start-Sleep 1 }
|
'^1$' { Start-Server; Refresh-Status; Start-Sleep 1 }
|
||||||
'2' { Stop-Server; Start-Sleep 1 }
|
'^2$' { Stop-Server; Refresh-Status; Start-Sleep 1 }
|
||||||
'3' { Stop-Server; Start-Sleep -Milliseconds 800; Start-Server; Start-Sleep 1 }
|
'^3$' { Stop-Server; Start-Sleep -Milliseconds 800; Start-Server; Refresh-Status; Start-Sleep 1 }
|
||||||
'4' { Open-Logs; Start-Sleep 1 }
|
'^4$' { Open-Logs; Start-Sleep 1 }
|
||||||
'5' { Run-Cmd 'Применение миграций' { & node src/db/migrations-runner.js | Out-Host } }
|
'^5$' { Run-Cmd 'Применение миграций' { & node src/db/migrations-runner.js | Out-Host }; Refresh-Status }
|
||||||
'6' { Run-Cmd 'Тесты (npm test)' { & npm test } }
|
'^6$' { Run-Cmd 'Тесты (npm test)' { & npm test } }
|
||||||
'7' { Run-Cmd 'Проверка авторизации роутов' { & npm run lint:routes } }
|
'^7$' { Run-Cmd 'Проверка авторизации роутов' { & npm run lint:routes } }
|
||||||
'8' { Start-Process ('http://localhost:' + $script:Port) }
|
'^8$' { Start-Process ('http://localhost:' + $script:Port) }
|
||||||
'9' { }
|
'^9$' { Refresh-Status }
|
||||||
'0' { $run = $false }
|
'^(B|И)$' { Run-Cmd 'Бэкап БД' { Backup-Db } }
|
||||||
default { }
|
'^(R|К)$' { Run-Cmd 'Восстановление БД' { Restore-Db }; Refresh-Status }
|
||||||
|
'^(A|Ф)$' { Run-Cmd 'Создать администратора' { Create-Admin } }
|
||||||
|
'^(W|Ц)$' { Watchdog; Refresh-Status }
|
||||||
|
'^(E|У)$' { Run-Cmd 'Ошибки в логах' { Show-Errors } }
|
||||||
|
'^0$' { $run = $false }
|
||||||
|
default { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Write-Host ''
|
Write-Host ''
|
||||||
|
|||||||
Reference in New Issue
Block a user