depository-deploy 1.0.23 → 1.0.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/install/install-unix.sh +28 -44
- package/install/install-windows.ps1 +21 -40
- package/package.json +4 -4
- package/wizard.html +204 -19
package/install/install-unix.sh
CHANGED
|
@@ -191,7 +191,7 @@ fi
|
|
|
191
191
|
|
|
192
192
|
# ---- Create directories ----
|
|
193
193
|
info "Creating directories..."
|
|
194
|
-
mkdir -p "$INSTALL_DIR"/{api,auth,worker,gatherer,digestor,ui,logs}
|
|
194
|
+
mkdir -p "$INSTALL_DIR"/{api,auth,worker,gatherer,digestor,setup,ui,logs}
|
|
195
195
|
mkdir -p "$DATA_DIR"
|
|
196
196
|
chown -R depository:depository "$INSTALL_DIR" "$DATA_DIR"
|
|
197
197
|
|
|
@@ -223,6 +223,9 @@ cp -r "$RELEASE_DIR/auth/"* "$INSTALL_DIR/auth/"
|
|
|
223
223
|
cp -r "$RELEASE_DIR/worker/"* "$INSTALL_DIR/worker/"
|
|
224
224
|
cp -r "$RELEASE_DIR/gatherer/"* "$INSTALL_DIR/gatherer/"
|
|
225
225
|
cp -r "$RELEASE_DIR/digestor/"* "$INSTALL_DIR/digestor/"
|
|
226
|
+
if [ -d "$RELEASE_DIR/setup" ]; then
|
|
227
|
+
cp -r "$RELEASE_DIR/setup/"* "$INSTALL_DIR/setup/"
|
|
228
|
+
fi
|
|
226
229
|
# UI is platform-independent — may be at $RELEASE_DIR/ui or one level up (release/ui)
|
|
227
230
|
UI_SRC="$RELEASE_DIR/ui"
|
|
228
231
|
if [ ! -d "$UI_SRC" ]; then
|
|
@@ -238,52 +241,33 @@ chmod +x "$INSTALL_DIR/auth/DEPOSITORY.API.OATH"
|
|
|
238
241
|
chmod +x "$INSTALL_DIR/worker/DEPOSITORY.CORE.WORKER"
|
|
239
242
|
chmod +x "$INSTALL_DIR/gatherer/DEPOSITORY.CORE.GATHERER"
|
|
240
243
|
chmod +x "$INSTALL_DIR/digestor/DEPOSITORY.CORE.DIGESTOR"
|
|
244
|
+
if [ -f "$INSTALL_DIR/setup/DEPOSITORY.TOOLS.SETUP" ]; then
|
|
245
|
+
chmod +x "$INSTALL_DIR/setup/DEPOSITORY.TOOLS.SETUP"
|
|
246
|
+
fi
|
|
241
247
|
chown -R depository:depository "$INSTALL_DIR"
|
|
242
248
|
|
|
243
|
-
# ----
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
# ---- Clean database (if requested) ----
|
|
252
|
-
if [ "${CLEAN_DB:-false}" = "true" ]; then
|
|
253
|
-
warn "CLEAN_DB=true — database will be wiped on first service start."
|
|
254
|
-
if [ "$DB_PROVIDER" = "Oracle" ]; then
|
|
255
|
-
# Set OracleForceRecreate=true in api appsettings so the REST service drops all tables
|
|
256
|
-
if command -v python3 &>/dev/null; then
|
|
257
|
-
python3 -c "
|
|
258
|
-
import json, sys
|
|
259
|
-
p='$INSTALL_DIR/api/appsettings.json'
|
|
260
|
-
with open(p) as f: d=json.load(f)
|
|
261
|
-
d.setdefault('DatabaseConfiguration',{})['OracleForceRecreate']=True
|
|
262
|
-
with open(p,'w') as f: json.dump(d,f,indent=2)
|
|
263
|
-
print(' OracleForceRecreate set to true in api/appsettings.json')
|
|
264
|
-
"
|
|
265
|
-
else
|
|
266
|
-
sed -i 's/"OracleForceRecreate": false/"OracleForceRecreate": true/' "$INSTALL_DIR/api/appsettings.json"
|
|
267
|
-
info " OracleForceRecreate set to true in api/appsettings.json"
|
|
268
|
-
fi
|
|
269
|
-
elif [ "$DB_PROVIDER" = "SqlServer" ]; then
|
|
270
|
-
# Drop and recreate both SQL Server databases so EF migrations start fresh
|
|
271
|
-
info " Dropping SQL Server databases: ${SQLSERVER_DATABASE}, DEPOSITORY_Users ..."
|
|
272
|
-
SQLCMD=""
|
|
273
|
-
if command -v sqlcmd &>/dev/null; then SQLCMD="sqlcmd"; fi
|
|
274
|
-
if [ -z "$SQLCMD" ] && [ -f /opt/mssql-tools18/bin/sqlcmd ]; then SQLCMD="/opt/mssql-tools18/bin/sqlcmd"; fi
|
|
275
|
-
if [ -z "$SQLCMD" ] && [ -f /opt/mssql-tools/bin/sqlcmd ]; then SQLCMD="/opt/mssql-tools/bin/sqlcmd"; fi
|
|
276
|
-
if [ -n "$SQLCMD" ]; then
|
|
277
|
-
for DBNAME in "$SQLSERVER_DATABASE" "DEPOSITORY_Users"; do
|
|
278
|
-
"$SQLCMD" -S "${SQLSERVER_HOST},${SQLSERVER_PORT}" -U "$SQLSERVER_USER" -P "$SQLSERVER_PASSWORD" \
|
|
279
|
-
-C -Q "IF DB_ID('${DBNAME}') IS NOT NULL BEGIN ALTER DATABASE [${DBNAME}] SET SINGLE_USER WITH ROLLBACK IMMEDIATE; DROP DATABASE [${DBNAME}]; END" \
|
|
280
|
-
2>/dev/null && info " Dropped database: ${DBNAME}" || warn " Could not drop ${DBNAME} (may not exist yet)"
|
|
281
|
-
done
|
|
282
|
-
else
|
|
283
|
-
warn " sqlcmd not found — cannot drop SQL Server databases automatically."
|
|
284
|
-
warn " The services will create fresh databases via EF migrations on first start."
|
|
285
|
-
fi
|
|
249
|
+
# ---- Run centralised database setup & config generation ----
|
|
250
|
+
SETUP_EXE="$INSTALL_DIR/setup/DEPOSITORY.TOOLS.SETUP"
|
|
251
|
+
if [ -f "$SETUP_EXE" ]; then
|
|
252
|
+
info "Running database setup & configuration generation..."
|
|
253
|
+
SETUP_ARGS="--config $CONF_FILE --install-dir $INSTALL_DIR --verbose"
|
|
254
|
+
if [ "${CLEAN_DB:-false}" = "true" ]; then
|
|
255
|
+
SETUP_ARGS="$SETUP_ARGS --force-recreate"
|
|
256
|
+
warn "CLEAN_DB=true — database will be wiped and recreated."
|
|
286
257
|
fi
|
|
258
|
+
"$SETUP_EXE" $SETUP_ARGS
|
|
259
|
+
if [ $? -ne 0 ]; then
|
|
260
|
+
error "Database setup failed. Check the output above."
|
|
261
|
+
fi
|
|
262
|
+
info "Database setup completed successfully."
|
|
263
|
+
else
|
|
264
|
+
warn "Setup tool not found at $SETUP_EXE — falling back to template-based configuration."
|
|
265
|
+
info "Generating configuration files from templates..."
|
|
266
|
+
apply_template "$TEMPLATE_DIR/appsettings-api.json" "$INSTALL_DIR/api/appsettings.json"
|
|
267
|
+
apply_template "$TEMPLATE_DIR/appsettings-auth.json" "$INSTALL_DIR/auth/appsettings.json"
|
|
268
|
+
apply_template "$TEMPLATE_DIR/appsettings-worker.json" "$INSTALL_DIR/worker/appsettings.json"
|
|
269
|
+
apply_template "$TEMPLATE_DIR/appsettings-gatherer.json" "$INSTALL_DIR/gatherer/appsettings.json"
|
|
270
|
+
apply_template "$TEMPLATE_DIR/appsettings-digestor.json" "$INSTALL_DIR/digestor/appsettings.json"
|
|
287
271
|
fi
|
|
288
272
|
|
|
289
273
|
# ---- Install systemd services ----
|
|
@@ -195,7 +195,7 @@ Start-Sleep -Seconds 2
|
|
|
195
195
|
|
|
196
196
|
# ---- Create directories ----
|
|
197
197
|
Write-Info "Creating directories..."
|
|
198
|
-
foreach ($sub in @("api","auth","worker","gatherer","digestor","ui","logs")) {
|
|
198
|
+
foreach ($sub in @("api","auth","worker","gatherer","digestor","setup","ui","logs")) {
|
|
199
199
|
$p = Join-Path $INSTALL_DIR $sub
|
|
200
200
|
if (-not (Test-Path $p)) { New-Item -ItemType Directory -Path $p -Force | Out-Null }
|
|
201
201
|
}
|
|
@@ -223,47 +223,28 @@ if (-not (Test-Path $uiSrc)) {
|
|
|
223
223
|
if (-not (Test-Path $uiSrc)) { Write-Fail "UI folder not found at $ReleaseDir\ui or $(Split-Path -Parent $ReleaseDir)\ui" }
|
|
224
224
|
Copy-Item "$uiSrc\*" (Join-Path $INSTALL_DIR "ui") -Recurse -Force
|
|
225
225
|
|
|
226
|
-
# ----
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
# ---- Clean database (if requested) ----
|
|
235
|
-
if ($conf.CLEAN_DB -eq "true") {
|
|
236
|
-
Write-Warn "CLEAN_DB=true -- database will be wiped."
|
|
237
|
-
if ($conf.DB_PROVIDER -eq "Oracle") {
|
|
238
|
-
# Set OracleForceRecreate=true in api appsettings
|
|
239
|
-
$apiSettings = Join-Path $INSTALL_DIR "api\appsettings.json"
|
|
240
|
-
$json = Get-Content $apiSettings -Raw | ConvertFrom-Json
|
|
241
|
-
$json.DatabaseConfiguration.OracleForceRecreate = $true
|
|
242
|
-
$json | ConvertTo-Json -Depth 10 | Set-Content $apiSettings -Encoding UTF8
|
|
243
|
-
Write-Info " OracleForceRecreate set to true in api/appsettings.json"
|
|
226
|
+
# ---- Run centralised database setup & config generation ----
|
|
227
|
+
$SetupExe = Join-Path $INSTALL_DIR "setup\DEPOSITORY.TOOLS.SETUP.exe"
|
|
228
|
+
if (Test-Path $SetupExe) {
|
|
229
|
+
Write-Info "Running database setup & configuration generation..."
|
|
230
|
+
$setupArgs = @("--config", $ConfFile, "--install-dir", $INSTALL_DIR, "--verbose")
|
|
231
|
+
if ($conf.CLEAN_DB -eq "true") {
|
|
232
|
+
$setupArgs += "--force-recreate"
|
|
233
|
+
Write-Warn "CLEAN_DB=true -- database will be wiped and recreated."
|
|
244
234
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
Write-
|
|
248
|
-
$sqlHost = "$($conf.SQLSERVER_HOST),$($conf.SQLSERVER_PORT)"
|
|
249
|
-
$sqlUser = $conf.SQLSERVER_USER
|
|
250
|
-
$sqlPass = $conf.SQLSERVER_PASSWORD
|
|
251
|
-
foreach ($dbName in @($conf.SQLSERVER_DATABASE, "DEPOSITORY_Users")) {
|
|
252
|
-
$dropSql = "IF DB_ID('$dbName') IS NOT NULL BEGIN ALTER DATABASE [$dbName] SET SINGLE_USER WITH ROLLBACK IMMEDIATE; DROP DATABASE [$dbName]; END"
|
|
253
|
-
try {
|
|
254
|
-
$connStr = "Server=$sqlHost;User ID=$sqlUser;Password=$sqlPass;TrustServerCertificate=true;"
|
|
255
|
-
$conn = New-Object System.Data.SqlClient.SqlConnection($connStr)
|
|
256
|
-
$conn.Open()
|
|
257
|
-
$cmd = $conn.CreateCommand()
|
|
258
|
-
$cmd.CommandText = $dropSql
|
|
259
|
-
$cmd.ExecuteNonQuery() | Out-Null
|
|
260
|
-
$conn.Close()
|
|
261
|
-
Write-Info " Dropped database: $dbName"
|
|
262
|
-
} catch {
|
|
263
|
-
Write-Warn " Could not drop ${dbName}: $($_.Exception.Message)"
|
|
264
|
-
}
|
|
265
|
-
}
|
|
235
|
+
& $SetupExe @setupArgs
|
|
236
|
+
if ($LASTEXITCODE -ne 0) {
|
|
237
|
+
Write-Fail "Database setup failed. Check the output above."
|
|
266
238
|
}
|
|
239
|
+
Write-OK "Database setup completed successfully."
|
|
240
|
+
} else {
|
|
241
|
+
Write-Warn "Setup tool not found at $SetupExe -- falling back to template-based configuration."
|
|
242
|
+
Write-Info "Generating configuration files from templates..."
|
|
243
|
+
Apply-Template "$TemplateDir\appsettings-api.json" "$INSTALL_DIR\api\appsettings.json"
|
|
244
|
+
Apply-Template "$TemplateDir\appsettings-auth.json" "$INSTALL_DIR\auth\appsettings.json"
|
|
245
|
+
Apply-Template "$TemplateDir\appsettings-worker.json" "$INSTALL_DIR\worker\appsettings.json"
|
|
246
|
+
Apply-Template "$TemplateDir\appsettings-gatherer.json" "$INSTALL_DIR\gatherer\appsettings.json"
|
|
247
|
+
Apply-Template "$TemplateDir\appsettings-digestor.json" "$INSTALL_DIR\digestor\appsettings.json"
|
|
267
248
|
}
|
|
268
249
|
|
|
269
250
|
# ---- Register Windows Services ----
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "depository-deploy",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.24",
|
|
4
4
|
"description": "Depository document management system – deployment wizard and installers",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"publishConfig": {
|
|
@@ -25,9 +25,9 @@
|
|
|
25
25
|
"scripts/publish.mjs"
|
|
26
26
|
],
|
|
27
27
|
"optionalDependencies": {
|
|
28
|
-
"depository-deploy-linux": "1.0.
|
|
29
|
-
"depository-deploy-macos": "1.0.
|
|
30
|
-
"depository-deploy-windows": "1.0.
|
|
28
|
+
"depository-deploy-linux": "1.0.24",
|
|
29
|
+
"depository-deploy-macos": "1.0.24",
|
|
30
|
+
"depository-deploy-windows": "1.0.24"
|
|
31
31
|
},
|
|
32
32
|
"scripts": {
|
|
33
33
|
"start": "node wizard-server.mjs"
|
package/wizard.html
CHANGED
|
@@ -435,20 +435,103 @@ input.err { border-color: var(--err) !important; box-shadow: 0 0 0 3px rgba(220,
|
|
|
435
435
|
@keyframes blink {
|
|
436
436
|
0%, 100% { opacity: 1; } 50% { opacity: .3; }
|
|
437
437
|
}
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
438
|
+
/* ── Terminal popup ─────────────────────────────────────── */
|
|
439
|
+
.terminal-overlay {
|
|
440
|
+
display: none; position: fixed; inset: 0;
|
|
441
|
+
background: rgba(0,0,0,.55); z-index: 1000;
|
|
442
|
+
align-items: center; justify-content: center; padding: 16px;
|
|
443
|
+
}
|
|
444
|
+
.terminal-overlay.open { display: flex; }
|
|
445
|
+
.terminal-modal {
|
|
446
|
+
width: min(800px, 100%); height: min(520px, 90vh);
|
|
447
|
+
background: #0f172a; border-radius: 10px;
|
|
448
|
+
display: flex; flex-direction: column;
|
|
449
|
+
border: 1px solid rgba(255,255,255,.12);
|
|
450
|
+
box-shadow: 0 20px 60px rgba(0,0,0,.6);
|
|
451
|
+
overflow: hidden; flex-shrink: 0;
|
|
452
|
+
}
|
|
453
|
+
.terminal-titlebar {
|
|
454
|
+
display: flex; align-items: center;
|
|
455
|
+
padding: 9px 12px; background: #1e293b;
|
|
456
|
+
border-bottom: 1px solid rgba(255,255,255,.07);
|
|
457
|
+
gap: 8px; flex-shrink: 0;
|
|
458
|
+
}
|
|
459
|
+
.terminal-dots { display: flex; gap: 6px; align-items: center; width: 54px; }
|
|
460
|
+
.terminal-dot {
|
|
461
|
+
width: 12px; height: 12px; border-radius: 50%; cursor: pointer;
|
|
462
|
+
transition: filter .12s;
|
|
463
|
+
}
|
|
464
|
+
.terminal-dot:hover { filter: brightness(1.3); }
|
|
465
|
+
.td-red { background: #ef4444; }
|
|
466
|
+
.td-yellow { background: #f59e0b; }
|
|
467
|
+
.td-green { background: #22c55e; }
|
|
468
|
+
.terminal-title {
|
|
469
|
+
flex: 1; font-size: 11.5px;
|
|
470
|
+
font-family: ui-monospace, "Consolas", monospace;
|
|
471
|
+
color: rgba(255,255,255,.45); text-align: center; letter-spacing: .3px;
|
|
472
|
+
}
|
|
473
|
+
.terminal-body {
|
|
474
|
+
flex: 1; overflow-y: auto; padding: 12px 14px;
|
|
475
|
+
font-family: ui-monospace, "Consolas", monospace;
|
|
476
|
+
font-size: 11.5px; line-height: 1.7;
|
|
444
477
|
}
|
|
445
|
-
.log-pane.visible { display: block; }
|
|
446
478
|
.log-line { color: #94a3b8; white-space: pre-wrap; word-break: break-all; }
|
|
447
479
|
.log-line.ok { color: #86efac; }
|
|
448
480
|
.log-line.err { color: #fca5a5; }
|
|
449
481
|
.log-line.warn { color: #fcd34d; }
|
|
450
482
|
.log-line.sect { color: #93c5fd; font-weight: 600; }
|
|
451
483
|
.log-line.dim { color: rgba(148,163,184,.35); }
|
|
484
|
+
/* ── Terminal filter tabs ───────────────────────────────── */
|
|
485
|
+
.terminal-tabs {
|
|
486
|
+
display: flex; border-top: 1px solid rgba(255,255,255,.07);
|
|
487
|
+
background: #1e293b; flex-shrink: 0;
|
|
488
|
+
}
|
|
489
|
+
.terminal-tab {
|
|
490
|
+
flex: 1; padding: 8px 6px; border: none; background: none;
|
|
491
|
+
font-family: ui-monospace, "Consolas", monospace; font-size: 11.5px;
|
|
492
|
+
color: rgba(255,255,255,.38); cursor: pointer;
|
|
493
|
+
border-top: 2px solid transparent;
|
|
494
|
+
transition: color .12s, border-color .12s, background .12s;
|
|
495
|
+
display: flex; align-items: center; justify-content: center; gap: 6px;
|
|
496
|
+
white-space: nowrap;
|
|
497
|
+
}
|
|
498
|
+
.terminal-tab:hover { color: rgba(255,255,255,.7); background: rgba(255,255,255,.04); }
|
|
499
|
+
.terminal-tab.active { color: #fff; }
|
|
500
|
+
.terminal-tab.active.tab-all { border-top-color: var(--accent); }
|
|
501
|
+
.terminal-tab.active.tab-info { border-top-color: #94a3b8; }
|
|
502
|
+
.terminal-tab.active.tab-warn { border-top-color: #fcd34d; }
|
|
503
|
+
.terminal-tab.active.tab-err { border-top-color: #fca5a5; }
|
|
504
|
+
.tab-badge {
|
|
505
|
+
font-size: 10.5px; padding: 1px 6px; border-radius: var(--r-pill);
|
|
506
|
+
background: rgba(255,255,255,.1); min-width: 20px; text-align: center;
|
|
507
|
+
line-height: 1.6;
|
|
508
|
+
}
|
|
509
|
+
.terminal-tab.tab-warn.has-items .tab-badge { background: rgba(252,211,77,.18); color: #fcd34d; }
|
|
510
|
+
.terminal-tab.tab-err.has-items .tab-badge { background: rgba(252,165,165,.18); color: #fca5a5; }
|
|
511
|
+
/* ── Inline log pane (download progress only) ──────────────── */
|
|
512
|
+
.log-pane {
|
|
513
|
+
background: #0f172a; border-radius: 8px;
|
|
514
|
+
padding: 10px 12px; font-family: ui-monospace, "Consolas", monospace;
|
|
515
|
+
font-size: 11px; line-height: 1.65; max-height: 140px; overflow-y: auto;
|
|
516
|
+
margin-top: 10px; display: none;
|
|
517
|
+
border: 1px solid rgba(255,255,255,.06);
|
|
518
|
+
}
|
|
519
|
+
.log-pane.visible { display: block; }
|
|
520
|
+
/* ── View-logs button ───────────────────────────────────── */
|
|
521
|
+
.btn-view-logs {
|
|
522
|
+
display: none; align-items: center; gap: 7px;
|
|
523
|
+
margin-top: 12px; padding: 6px 14px; border-radius: var(--r-pill);
|
|
524
|
+
border: 1px solid rgba(37,99,235,.4); background: rgba(37,99,235,.08);
|
|
525
|
+
color: #93c5fd; font-size: 12px; font-weight: 600;
|
|
526
|
+
cursor: pointer; font-family: inherit;
|
|
527
|
+
transition: background .12s, border-color .12s;
|
|
528
|
+
}
|
|
529
|
+
.btn-view-logs.visible { display: inline-flex; }
|
|
530
|
+
.btn-view-logs:hover { background: rgba(37,99,235,.16); border-color: rgba(37,99,235,.7); }
|
|
531
|
+
.term-err-pill {
|
|
532
|
+
display: none; background: rgba(252,165,165,.2); color: #fca5a5;
|
|
533
|
+
border-radius: var(--r-pill); padding: 1px 7px; font-size: 10.5px;
|
|
534
|
+
}
|
|
452
535
|
.run-result {
|
|
453
536
|
display: none; margin-top: 12px; padding: 10px 14px; border-radius: 8px;
|
|
454
537
|
font-size: 12.5px; font-weight: 600; border-left: 3px solid transparent;
|
|
@@ -860,7 +943,10 @@ input.err { border-color: var(--err) !important; box-shadow: 0 0 0 3px rgba(220,
|
|
|
860
943
|
<div class="run-status-dot" id="runDot"></div>
|
|
861
944
|
<span id="runStatusText"></span>
|
|
862
945
|
</div>
|
|
863
|
-
<
|
|
946
|
+
<button class="btn-view-logs" id="btnViewLogs" onclick="openTerminal()">
|
|
947
|
+
⬛ View Logs
|
|
948
|
+
<span class="term-err-pill" id="terminalErrPill"></span>
|
|
949
|
+
</button>
|
|
864
950
|
<div class="run-result" id="runResult"></div>
|
|
865
951
|
</div>
|
|
866
952
|
</div>
|
|
@@ -2160,23 +2246,83 @@ async function downloadBinaries() {
|
|
|
2160
2246
|
function classifyLine(raw) {
|
|
2161
2247
|
const s = raw.replace(/\x1b\[[0-9;]*m/g, '').trim();
|
|
2162
2248
|
if (!s) return null;
|
|
2163
|
-
if (/✓|OK\b|\[OK\]|
|
|
2164
|
-
|
|
2165
|
-
if (
|
|
2166
|
-
if (
|
|
2167
|
-
if (
|
|
2249
|
+
if (/✓|OK\b|\[OK\]|SETUP COMPLETED|success/i.test(s)) return 'ok';
|
|
2250
|
+
// Errors must be checked BEFORE warnings so they are never misclassified
|
|
2251
|
+
if (/✗|\[ERROR\]|error:|^crit:|^fail:|\bERR:|\bfailed\b|\bexception\b|\bunhandled\b|SETUP FAILED/i.test(s)) return 'err';
|
|
2252
|
+
if (/⚠|\[WARN\]|^warn:|warning/i.test(s)) return 'warn';
|
|
2253
|
+
if (/^[─═]{5}|^\s*[┌└│]|^={5}/.test(s)) return 'sect';
|
|
2254
|
+
if (/^\s*#/.test(s)) return 'dim';
|
|
2168
2255
|
return 'info';
|
|
2169
2256
|
}
|
|
2170
2257
|
|
|
2258
|
+
// ── Log counts & filter state ─────────────────────────────────
|
|
2259
|
+
let _logCounts = { all: 0, info: 0, warn: 0, err: 0 };
|
|
2260
|
+
let _activeFilter = 'all';
|
|
2261
|
+
|
|
2262
|
+
function _updateTabCounts() {
|
|
2263
|
+
document.getElementById('cntAll').textContent = _logCounts.all;
|
|
2264
|
+
document.getElementById('cntInfo').textContent = _logCounts.info;
|
|
2265
|
+
document.getElementById('cntWarn').textContent = _logCounts.warn;
|
|
2266
|
+
document.getElementById('cntErr').textContent = _logCounts.err;
|
|
2267
|
+
document.getElementById('tabWarn').classList.toggle('has-items', _logCounts.warn > 0);
|
|
2268
|
+
document.getElementById('tabErr').classList.toggle('has-items', _logCounts.err > 0);
|
|
2269
|
+
const pill = document.getElementById('terminalErrPill');
|
|
2270
|
+
if (_logCounts.err > 0) {
|
|
2271
|
+
pill.style.display = '';
|
|
2272
|
+
pill.textContent = _logCounts.err + ' error' + (_logCounts.err !== 1 ? 's' : '');
|
|
2273
|
+
} else {
|
|
2274
|
+
pill.style.display = 'none';
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2277
|
+
|
|
2171
2278
|
function appendLog(raw) {
|
|
2172
2279
|
const pane = document.getElementById('logPane');
|
|
2173
2280
|
const cls = classifyLine(raw);
|
|
2174
2281
|
if (cls === null) return;
|
|
2175
2282
|
const div = document.createElement('div');
|
|
2176
|
-
div.className
|
|
2177
|
-
div.
|
|
2283
|
+
div.className = 'log-line' + (cls !== 'info' ? ' ' + cls : '');
|
|
2284
|
+
div.dataset.logCls = cls;
|
|
2285
|
+
div.textContent = raw.replace(/\x1b\[[0-9;]*m/g, '');
|
|
2286
|
+
// hide line when active filter doesn't match
|
|
2287
|
+
if (_activeFilter !== 'all') {
|
|
2288
|
+
const show = (_activeFilter === 'warn' && cls === 'warn')
|
|
2289
|
+
|| (_activeFilter === 'err' && cls === 'err')
|
|
2290
|
+
|| (_activeFilter === 'info' && cls !== 'warn' && cls !== 'err');
|
|
2291
|
+
if (!show) div.style.display = 'none';
|
|
2292
|
+
}
|
|
2178
2293
|
pane.appendChild(div);
|
|
2179
2294
|
pane.scrollTop = pane.scrollHeight;
|
|
2295
|
+
_logCounts.all++;
|
|
2296
|
+
if (cls === 'err') _logCounts.err++;
|
|
2297
|
+
else if (cls === 'warn') _logCounts.warn++;
|
|
2298
|
+
else _logCounts.info++;
|
|
2299
|
+
_updateTabCounts();
|
|
2300
|
+
}
|
|
2301
|
+
|
|
2302
|
+
// ── Terminal open / close / filter ───────────────────────────
|
|
2303
|
+
function openTerminal() {
|
|
2304
|
+
document.getElementById('terminalOverlay').classList.add('open');
|
|
2305
|
+
document.getElementById('logPane').scrollTop = document.getElementById('logPane').scrollHeight;
|
|
2306
|
+
}
|
|
2307
|
+
function closeTerminal() {
|
|
2308
|
+
document.getElementById('terminalOverlay').classList.remove('open');
|
|
2309
|
+
}
|
|
2310
|
+
function terminalOverlayClick(e) {
|
|
2311
|
+
if (e.target === document.getElementById('terminalOverlay')) closeTerminal();
|
|
2312
|
+
}
|
|
2313
|
+
function filterLog(type) {
|
|
2314
|
+
_activeFilter = type;
|
|
2315
|
+
document.querySelectorAll('.terminal-tab').forEach(tab => tab.classList.remove('active'));
|
|
2316
|
+
document.getElementById('tab' + type.charAt(0).toUpperCase() + type.slice(1)).classList.add('active');
|
|
2317
|
+
document.getElementById('logPane').querySelectorAll('.log-line').forEach(line => {
|
|
2318
|
+
const cls = line.dataset.logCls || 'info';
|
|
2319
|
+
let show;
|
|
2320
|
+
if (type === 'all') show = true;
|
|
2321
|
+
else if (type === 'warn') show = cls === 'warn';
|
|
2322
|
+
else if (type === 'err') show = cls === 'err';
|
|
2323
|
+
else show = cls !== 'warn' && cls !== 'err';
|
|
2324
|
+
line.style.display = show ? '' : 'none';
|
|
2325
|
+
});
|
|
2180
2326
|
}
|
|
2181
2327
|
|
|
2182
2328
|
function setRunStatus(dotClass, textKey) {
|
|
@@ -2196,7 +2342,14 @@ async function runInstall() {
|
|
|
2196
2342
|
|
|
2197
2343
|
btn.disabled = true;
|
|
2198
2344
|
pane.innerHTML = '';
|
|
2199
|
-
|
|
2345
|
+
_logCounts = { all: 0, info: 0, warn: 0, err: 0 };
|
|
2346
|
+
_activeFilter = 'all';
|
|
2347
|
+
document.querySelectorAll('.terminal-tab').forEach(t => t.classList.remove('active','has-items'));
|
|
2348
|
+
document.getElementById('tabAll').classList.add('active');
|
|
2349
|
+
_updateTabCounts();
|
|
2350
|
+
document.getElementById('terminalTitle').textContent = 'Installation Log — Running…';
|
|
2351
|
+
document.getElementById('btnViewLogs').classList.add('visible');
|
|
2352
|
+
openTerminal();
|
|
2200
2353
|
result.style.display = 'none';
|
|
2201
2354
|
document.getElementById('retryBtn').style.display = 'none';
|
|
2202
2355
|
setRunStatus('spinning', 'run_running');
|
|
@@ -2224,10 +2377,12 @@ async function runInstall() {
|
|
|
2224
2377
|
es.close(); _evtSource = null;
|
|
2225
2378
|
const { ok } = JSON.parse(e.data);
|
|
2226
2379
|
if (ok) {
|
|
2380
|
+
document.getElementById('terminalTitle').textContent = 'Installation Log — Completed';
|
|
2227
2381
|
setRunStatus('ok', 'run_success');
|
|
2228
2382
|
result.className = 'run-result ok';
|
|
2229
2383
|
result.textContent = t('run_success');
|
|
2230
2384
|
} else {
|
|
2385
|
+
document.getElementById('terminalTitle').textContent = 'Installation Log — Failed';
|
|
2231
2386
|
setRunStatus('err', 'run_failed');
|
|
2232
2387
|
result.className = 'run-result err';
|
|
2233
2388
|
result.textContent = t('run_failed');
|
|
@@ -2239,6 +2394,7 @@ async function runInstall() {
|
|
|
2239
2394
|
|
|
2240
2395
|
es.onerror = () => {
|
|
2241
2396
|
es.close(); _evtSource = null;
|
|
2397
|
+
document.getElementById('terminalTitle').textContent = 'Installation Log — Failed';
|
|
2242
2398
|
setRunStatus('err', 'run_failed');
|
|
2243
2399
|
result.className = 'run-result err';
|
|
2244
2400
|
result.textContent = t('run_failed');
|
|
@@ -2249,6 +2405,7 @@ async function runInstall() {
|
|
|
2249
2405
|
|
|
2250
2406
|
} catch (e) {
|
|
2251
2407
|
appendLog('Error: ' + e.message);
|
|
2408
|
+
document.getElementById('terminalTitle').textContent = 'Installation Log — Failed';
|
|
2252
2409
|
setRunStatus('err', 'run_failed');
|
|
2253
2410
|
result.className = 'run-result err';
|
|
2254
2411
|
result.textContent = t('run_failed');
|
|
@@ -2260,9 +2417,15 @@ async function runInstall() {
|
|
|
2260
2417
|
|
|
2261
2418
|
function resetRun() {
|
|
2262
2419
|
if (_evtSource) { _evtSource.close(); _evtSource = null; }
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2420
|
+
document.getElementById('logPane').innerHTML = '';
|
|
2421
|
+
_logCounts = { all: 0, info: 0, warn: 0, err: 0 };
|
|
2422
|
+
_activeFilter = 'all';
|
|
2423
|
+
document.querySelectorAll('.terminal-tab').forEach(t => t.classList.remove('active','has-items'));
|
|
2424
|
+
document.getElementById('tabAll').classList.add('active');
|
|
2425
|
+
_updateTabCounts();
|
|
2426
|
+
document.getElementById('terminalTitle').textContent = 'Installation Log';
|
|
2427
|
+
document.getElementById('btnViewLogs').classList.remove('visible');
|
|
2428
|
+
closeTerminal();
|
|
2266
2429
|
document.getElementById('runResult').style.display = 'none';
|
|
2267
2430
|
document.getElementById('runStatus').style.display = 'none';
|
|
2268
2431
|
document.getElementById('retryBtn').style.display = 'none';
|
|
@@ -2362,5 +2525,27 @@ checkServerMode();
|
|
|
2362
2525
|
const restored = restoreWizardState();
|
|
2363
2526
|
if (!restored) applyOSDefaults();
|
|
2364
2527
|
</script>
|
|
2528
|
+
|
|
2529
|
+
<!-- ── Terminal popup ─────────────────────────────────────── -->
|
|
2530
|
+
<div class="terminal-overlay" id="terminalOverlay" onclick="terminalOverlayClick(event)">
|
|
2531
|
+
<div class="terminal-modal" id="terminalModal">
|
|
2532
|
+
<div class="terminal-titlebar">
|
|
2533
|
+
<div class="terminal-dots">
|
|
2534
|
+
<div class="terminal-dot td-red" onclick="closeTerminal()" title="Close"></div>
|
|
2535
|
+
<div class="terminal-dot td-yellow" title="Minimise" onclick="closeTerminal()"></div>
|
|
2536
|
+
<div class="terminal-dot td-green" title="Expand"></div>
|
|
2537
|
+
</div>
|
|
2538
|
+
<span class="terminal-title" id="terminalTitle">Installation Log</span>
|
|
2539
|
+
<div style="width:54px"></div>
|
|
2540
|
+
</div>
|
|
2541
|
+
<div class="terminal-body" id="logPane"></div>
|
|
2542
|
+
<div class="terminal-tabs">
|
|
2543
|
+
<button class="terminal-tab tab-all active" id="tabAll" onclick="filterLog('all')">All <span class="tab-badge" id="cntAll">0</span></button>
|
|
2544
|
+
<button class="terminal-tab tab-info" id="tabInfo" onclick="filterLog('info')">Info <span class="tab-badge" id="cntInfo">0</span></button>
|
|
2545
|
+
<button class="terminal-tab tab-warn" id="tabWarn" onclick="filterLog('warn')">Warnings <span class="tab-badge" id="cntWarn">0</span></button>
|
|
2546
|
+
<button class="terminal-tab tab-err" id="tabErr" onclick="filterLog('err')">Errors <span class="tab-badge" id="cntErr">0</span></button>
|
|
2547
|
+
</div>
|
|
2548
|
+
</div>
|
|
2549
|
+
</div>
|
|
2365
2550
|
</body>
|
|
2366
2551
|
</html>
|