fix
This commit is contained in:
@@ -25,6 +25,21 @@
|
||||
/** The short version of a row's state, for the column that has room for three words.
|
||||
NOT called `state`: that shadows the `$state` rune in this file, and everything
|
||||
declared with it silently stops being reactive. */
|
||||
// The refresh is its own press, with its own password: it dumps a database operators are
|
||||
// using, so it must not be reachable by the same click that ships a code change.
|
||||
let refreshOpen = $state(false);
|
||||
let pgPassword = $state("");
|
||||
let tileRoot = $state("");
|
||||
const canRefresh = $derived(
|
||||
password.trim().length > 0 && (pgPassword.trim().length > 0 || tileRoot.trim().length > 0),
|
||||
);
|
||||
|
||||
async function refresh() {
|
||||
if (!canRefresh || busy) return;
|
||||
await launcher.refreshRun(password, pgPassword, tileRoot);
|
||||
pgPassword = "";
|
||||
}
|
||||
|
||||
/** `20260921T125618Z` -> `21 Sep 2026`; anything else is shown as it came. */
|
||||
function stamped(snapshot: string | null): string {
|
||||
if (!snapshot || snapshot.length < 8) return snapshot ?? "unknown";
|
||||
@@ -36,6 +51,13 @@
|
||||
return `${d} ${MONTHS[m - 1] ?? "?"} ${y}`;
|
||||
}
|
||||
|
||||
/** `20260423` -> `Apr 2026`. The vintage of the extract, not an age. */
|
||||
function monthOf(stamp: string): string {
|
||||
const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
||||
const m = Number(stamp.slice(4, 6));
|
||||
return `${MONTHS[m - 1] ?? "?"} ${stamp.slice(0, 4)}`;
|
||||
}
|
||||
|
||||
function aged(days: number | null): string {
|
||||
if (days === null) return "";
|
||||
if (days <= 0) return "today";
|
||||
@@ -97,10 +119,55 @@
|
||||
<div class="payload-row">
|
||||
<span class="payload-name">Map tiles</span>
|
||||
<span class="payload-val mono">{d.tile_sets} sets</span>
|
||||
<span class="payload-age" class:stale={(d.tiles_age_days ?? 0) > 90}>{aged(d.tiles_age_days)}</span>
|
||||
<!-- Deliberately NOT an age. Nothing records when tiles were baked, and the only
|
||||
date available is the upstream extract's, which said "161 days old" about
|
||||
work that is three weeks old. -->
|
||||
<span class="payload-age" class:stale={(d.basemap_vintage_days ?? 0) > 365}>
|
||||
{d.basemap_vintage ? `map data ${monthOf(d.basemap_vintage)}` : ""}
|
||||
</span>
|
||||
<span class="payload-size mono">{formatBytes(d.tile_bytes)}</span>
|
||||
</div>
|
||||
<span class="payload-foot mono">{d.tile_datasets.join(" · ")}</span>
|
||||
<span class="payload-foot">
|
||||
Tiles record no bake date, so only the source extract's vintage is known.
|
||||
</span>
|
||||
<div class="payload-act">
|
||||
<button class="btn-link" onclick={() => (refreshOpen = !refreshOpen)}>
|
||||
{refreshOpen ? "Never mind" : "Bring up to date…"}
|
||||
</button>
|
||||
</div>
|
||||
{#if refreshOpen}
|
||||
<div class="refresh">
|
||||
<p class="refresh-why">
|
||||
This rebuilds the seed from the live database and re-indexes the tiles. It
|
||||
<strong>publishes nothing</strong> — it prepares the inputs, and the console says how to cut a
|
||||
version from them.
|
||||
</p>
|
||||
<input
|
||||
class="input"
|
||||
type="password"
|
||||
autocomplete="off"
|
||||
placeholder="database password (Data PC) — leave empty to skip the seed"
|
||||
bind:value={pgPassword}
|
||||
disabled={busy}
|
||||
/>
|
||||
<input
|
||||
class="input"
|
||||
type="text"
|
||||
autocomplete="off"
|
||||
spellcheck="false"
|
||||
placeholder="tile folder laid out as style\dataset — leave empty to skip the tiles"
|
||||
bind:value={tileRoot}
|
||||
disabled={busy}
|
||||
/>
|
||||
<div class="row">
|
||||
<span class="refresh-note">
|
||||
The seed is minutes of load on a database people are using. Uses the node password above.
|
||||
</span>
|
||||
<button class="btn" disabled={!canRefresh || busy} onclick={refresh}>Refresh</button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if !d.is_live}
|
||||
<span class="payload-foot">Read from this PC's {d.version}, which is not what the DHP serves.</span>
|
||||
{/if}
|
||||
@@ -189,6 +256,33 @@
|
||||
.payload-age.stale {
|
||||
color: var(--warn);
|
||||
}
|
||||
.payload-act {
|
||||
margin-top: 6px;
|
||||
}
|
||||
.refresh {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
margin-top: 8px;
|
||||
padding-top: 8px;
|
||||
border-top: 1px solid var(--line);
|
||||
}
|
||||
.refresh-why,
|
||||
.refresh-note {
|
||||
margin: 0;
|
||||
color: var(--muted);
|
||||
font-size: var(--text-xs);
|
||||
line-height: 1.5;
|
||||
}
|
||||
.refresh .row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
.refresh .row .refresh-note {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.payload-foot {
|
||||
margin-top: 4px;
|
||||
color: var(--muted);
|
||||
|
||||
@@ -75,6 +75,7 @@ function createTauriBackend(): Backend {
|
||||
releasePlan: () => invoke<ReleasePlan>("release_plan"),
|
||||
releaseRun: (password, withApi, withMcp) => invoke<void>("release_run", { password, withApi, withMcp }),
|
||||
releaseConsole: (since) => invoke<ReleaseConsoleTail>("release_console", { since }),
|
||||
refreshRun: (password, pgPassword, tileRoot) => invoke<void>("refresh_run", { password, pgPassword, tileRoot }),
|
||||
onTileSyncProgress: (handler) => subscribe<TileSyncProgress>("tile-sync-progress", handler),
|
||||
onRemoveProgress: (handler) => subscribe<RemoveProgress>("remove-progress", handler),
|
||||
onStack: (handler) => subscribe<StackEvent>("stack", handler),
|
||||
|
||||
@@ -469,6 +469,20 @@ class Launcher {
|
||||
}
|
||||
}
|
||||
|
||||
/** Rebuild the pack's data payload. Streams into the same console as a release. */
|
||||
refreshRun(password: string, pgPassword: string, tileRoot: string) {
|
||||
return this.run("release", async () => {
|
||||
await backend.refreshRun(password, pgPassword, tileRoot);
|
||||
this.releaseOpen = false;
|
||||
this.consoleLines = [];
|
||||
this.consoleTotal = 0;
|
||||
this.consoleExit = null;
|
||||
this.consoleRunning = true;
|
||||
this.consoleOpen = true;
|
||||
this.pollConsole();
|
||||
});
|
||||
}
|
||||
|
||||
/** Reopen it after it was closed; a finished release keeps its output to be read. */
|
||||
showConsole() {
|
||||
this.consoleOpen = true;
|
||||
|
||||
+21
-1
@@ -825,7 +825,8 @@ class MockBackend implements Backend {
|
||||
tile_sets: 8,
|
||||
tile_bytes: 102_650_000_000,
|
||||
tile_datasets: ["protomaps_20260423", "planet_heightmap"],
|
||||
tiles_age_days: 161,
|
||||
basemap_vintage: "20260423",
|
||||
basemap_vintage_days: 161,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -854,6 +855,25 @@ class MockBackend implements Backend {
|
||||
})();
|
||||
}
|
||||
|
||||
async refreshRun(password: string, pgPassword: string, tileRoot: string): Promise<void> {
|
||||
await this.wait(300);
|
||||
if (!password.trim()) throw "The node's sudo password is needed: the dump runs there.";
|
||||
if (!pgPassword.trim() && !tileRoot.trim()) throw "Nothing to refresh: give the database password, a tile folder, or both.";
|
||||
this.consoleLines = ["=== Database seed ==="];
|
||||
this.consoleDone = null;
|
||||
const rest = pgPassword.trim()
|
||||
? ["live version : 0.75.0", "dumping odian from 192.168.1.13 ...", "snapshot : 20261001T090000Z (was 20260921T125618Z)",
|
||||
"tables : 302 bytes: 15.40 GB", "Refresh finished. Nothing is published yet."]
|
||||
: ["=== Tile indexes ===", "re-indexing 8 sets...", "Refresh finished. Nothing is published yet."];
|
||||
void (async () => {
|
||||
for (const line of rest) {
|
||||
await this.wait(600);
|
||||
this.consoleLines.push(line);
|
||||
}
|
||||
this.consoleDone = 0;
|
||||
})();
|
||||
}
|
||||
|
||||
private consoleLines: string[] = [];
|
||||
private consoleDone: number | null = 0;
|
||||
|
||||
|
||||
+6
-2
@@ -314,8 +314,10 @@ export interface ReleaseData {
|
||||
tile_sets: number;
|
||||
tile_bytes: number;
|
||||
tile_datasets: string[];
|
||||
/** Age of the oldest dated dataset; undated sets (heightmap) are not counted. */
|
||||
tiles_age_days: number | null;
|
||||
/** The upstream extract the basemap was drawn from, `YYYYMMDD`. NOT a bake time: tiles
|
||||
* record none anywhere, so there is no "how old are our tiles" to show. */
|
||||
basemap_vintage: string | null;
|
||||
basemap_vintage_days: number | null;
|
||||
}
|
||||
|
||||
export interface ReleasePlan {
|
||||
@@ -412,6 +414,8 @@ export interface Backend {
|
||||
releasePlan(): Promise<ReleasePlan>;
|
||||
releaseRun(password: string, withApi: boolean, withMcp: boolean): Promise<void>;
|
||||
releaseConsole(since: number): Promise<ReleaseConsoleTail>;
|
||||
/** Rebuild the pack's data payload. Prepares inputs only; it publishes nothing. */
|
||||
refreshRun(password: string, pgPassword: string, tileRoot: string): Promise<void>;
|
||||
/** What the operator has to decide, oldest first. */
|
||||
getSyncConflicts(): Promise<SyncConflict[]>;
|
||||
/** Records one decision; resolves with what is left to decide. */
|
||||
|
||||
Reference in New Issue
Block a user