Catalog seeding, blank-unique fix, slides rotation, console repair

This commit is contained in:
2026-08-05 13:19:51 -04:00
parent 9c530a70db
commit d3e7311376
13 changed files with 3721 additions and 219 deletions

View File

@@ -201,6 +201,7 @@ var
// the check takes a few seconds, runs hidden, and gives no sign of life.
CheckingPage: TOutputProgressWizardPage;
PluginPage: TInputOptionWizardPage;
CatalogPage: TInputOptionWizardPage;
PluginNames: TArrayOfString;
PluginPageReady: Boolean;
DbPageReady: Boolean;
@@ -397,7 +398,21 @@ begin
if Trim(PluginNames[I]) <> '' then
PluginPage.Add(PluginLabel(Trim(PluginNames[I])));
DbChoicePage := CreateInputOptionPage(PluginPage.ID,
// Offered, not forced. A site that machines nothing inherits 21 machine types
// it will never use, and a catalog nobody wanted is just clutter in every
// dropdown. Ticked by default because the alternative is retyping a vendor and
// model list another site already built.
CatalogPage := CreateInputOptionPage(PluginPage.ID,
'Starter data', 'Load the shared equipment catalog?',
'Vendors, model numbers and the type lists, as built up across the other ' +
'sites. It only ADDS - nothing existing is changed or removed - and it can ' +
'be loaded later with "flask seed catalog". No assets, locations or people ' +
'are included; this is catalogue information only.',
False, False);
CatalogPage.Add('Load the shared vendor and model catalog');
CatalogPage.Values[0] := True;
DbChoicePage := CreateInputOptionPage(CatalogPage.ID,
'Database', 'Where should ShopDB-Flask store its data?',
'Most sites already run MySQL for the existing shopdb application. If so, ' +
'choose the second option - installing a second server would collide on ' +
@@ -1177,6 +1192,8 @@ begin
' -OnFailure never' +
' -ClientIpSource ' + ClientIpSourceArg +
' -SitePlugins "' + SelectedPlugins + '"';
if CatalogPage.Values[0] then
Common := Common + ' -SeedCatalog';
// Subpath deployment: an IIS Application under the existing site instead of a
// site of its own. Appended here because Pascal has no conditional expression.

View File

@@ -22,7 +22,7 @@
[CmdletBinding()]
param(
[ValidateSet('menu','status','start','stop','restart','logs','open','backup',
'check','sessions','plugins','add-plugin','verify','uninstall')]
'check','repair','sessions','plugins','add-plugin','verify','uninstall')]
[string] $Command = 'menu',
[string] $Path = '',
# Machine-readable output for 'check'. The people running this are expected to
@@ -577,11 +577,101 @@ function Invoke-Check {
$env:FLASK_APP = 'shopdb'
try { & $flask db-utils preflight 2>&1 | ForEach-Object { Say " $_" } }
finally { Pop-Location }
# Surface an unfinished install HERE, where somebody is already looking,
# rather than leaving them to infer it from 500s on unrelated pages.
$state = Test-Provisioned
if ($state.SchemaCurrent -eq $false -or $state.Seeded -eq $false) {
Say ''
Say ' THIS SERVER IS NOT FULLY PROVISIONED' 'Red'
foreach ($detail in $state.Detail) { Say (" - {0}" -f $detail) 'Red' }
Say ' Most pages will answer 500 until this is fixed. Run:' 'Yellow'
Say ' shopdb-admin.ps1 repair' 'White'
}
Say ''
Say ' For help from an AI assistant, paste the output of:' 'DarkGray'
Say ' shopdb-admin.ps1 check -Json' 'White'
}
function Test-Provisioned {
"""Is the schema current and the reference data seeded?"""
# A server whose migrations or seeds never completed does not fail politely:
# it answers 500 on most pages and 404 on settings that were never created,
# which reads as a broken application rather than an unfinished install. One
# site was stood up that way and spent a morning being debugged as a bug.
$result = @{ SchemaCurrent = $true; Seeded = $true; Detail = @() }
$out = Invoke-Flask @('db','current')
$current = ($out | Out-String)
$head = (Invoke-Flask @('db','heads') | Out-String)
if ($script:LastFlaskExit -ne 0) {
$result.SchemaCurrent = $false
$result.Detail += 'could not read the schema version'
} elseif ($current -notmatch '\(head\)' -and $head.Trim()) {
# `db current` appends "(head)" when the database is at the newest
# revision. Its absence means migrations are outstanding.
$result.SchemaCurrent = $false
$result.Detail += 'database schema is behind the application'
}
# Sentinel seeds. permissions, settings and the reference data are each
# created by a `flask seed` command, and their absence is what produced the
# 404s on settings keys at a site whose install never finished.
$seedOut = (Invoke-Flask @('db-utils','seed-state') | Out-String)
if ($seedOut -match 'No such command') {
# An older build with no seed-state. Report UNKNOWN rather than healthy:
# claiming a clean bill of health from a check that did not run is how a
# broken server passes inspection.
$result.Seeded = $null
$result.Detail += 'seed state could not be checked (older build)'
} elseif ($script:LastFlaskExit -ne 0 -or $seedOut -match 'MISSING') {
$result.Seeded = $false
$result.Detail += 'seed data is missing (permissions, settings or reference data)'
}
return $result
}
function Invoke-Repair {
Head 'Repair provisioning'
Say ' Brings the database up to the application: migrations, plugin'
Say ' migrations, and the seed data. Every step is idempotent, so running'
Say ' this on a healthy server changes nothing.'
Say ''
$steps = @(
@{ Name = 'core schema'; Args = @('db','upgrade') },
@{ Name = 'plugin schemas'; Args = @('plugin','upgrade-all') },
@{ Name = 'permissions'; Args = @('seed','permissions') },
@{ Name = 'settings'; Args = @('seed','settings') },
@{ Name = 'reference data'; Args = @('seed','reference-data') }
)
$failed = @()
foreach ($step in $steps) {
Say (" {0} ..." -f $step.Name) 'White'
$out = Invoke-Flask $step.Args
if ($script:LastFlaskExit -ne 0) {
$failed += $step.Name
Say (" FAILED (exit {0})" -f $script:LastFlaskExit) 'Red'
$out | Select-Object -Last 6 | ForEach-Object { Say " $_" 'Red' }
} else {
Say ' done' 'Green'
}
}
if ($failed.Count -gt 0) {
Say ''
Say (" {0} step(s) failed: {1}" -f $failed.Count, ($failed -join ', ')) 'Red'
Say ' Nothing further was skipped - each step ran independently.' 'Red'
Say ' Send the output above, or run: shopdb-admin.ps1 check -Json' 'Red'
return
}
Say ''
Say ' Provisioning complete. Restarting the application.' 'Green'
Restart-App
}
function Show-Sessions {
Head 'Worker processes'
$w = Get-CimInstance Win32_Process -Filter "Name='w3wp.exe'" -EA SilentlyContinue
@@ -856,6 +946,7 @@ switch ($Command) {
'open' { Open-Site }
'backup' { Backup-Db $Path }
'check' { Invoke-Check }
'repair' { Invoke-Repair }
'sessions' { Show-Sessions }
'plugins' { Show-Plugins }
'add-plugin'{ Add-Plugin $Path }

View File

@@ -127,6 +127,10 @@ param(
# client reads as 127.0.0.1: the GE-Enforce IP allowlist, the dashboard
# visitor-location lookup and per-host login rate limiting all break quietly.
[ValidateSet('direct','proxy')] [string] $ClientIpSource = 'direct',
# Load the shared vendor/model catalog during stage 3. Offered by the
# wizard rather than assumed: a site that machines nothing does not want
# 21 machine types cluttering its dropdowns.
[switch] $SeedCatalog,
# Required before this installer will alter an installation it did not
# create. Everything here is built for a greenfield server: it makes its own
# Python, its own venv and its own IIS objects, and its upgrade path assumes
@@ -1747,6 +1751,14 @@ build to get a matching pair, then send the install log to support.
Invoke-Native $Flask @('seed',$seed) "seed $seed"
}
# Additive and idempotent, so a re-run adds only what is genuinely new
# and never overwrites a description somebody corrected here.
if ($SeedCatalog) {
Write-Log 'flask seed catalog (shared vendor and model catalog)'
$catalogOut = Invoke-Native $Flask @('seed','catalog') 'seed catalog' -OkExit 0,1
$catalogOut | Select-Object -Last 12 | ForEach-Object { Write-Log " $_" }
}
# Plugin registry starts empty on a fresh box. apply-profile resolves the
# declared set's hard-dependency closure and installs + enables it in
# dependency order, idempotently (ADR-013). It fails loudly if the profile