fix
This commit is contained in:
@@ -2427,11 +2427,24 @@ impl Launcher {
|
|||||||
// The commit the LIVE pack was cut from. Read from this PC's own copy of that version,
|
// The commit the LIVE pack was cut from. Read from this PC's own copy of that version,
|
||||||
// which costs nothing; when this PC is on an older version we simply do not know, and
|
// which costs nothing; when this PC is on an older version we simply do not know, and
|
||||||
// the panel says so rather than guessing.
|
// the panel says so rather than guessing.
|
||||||
let pack_commit = match (&installed, &pack_live) {
|
// One read, two answers. The commit is only trusted when this PC is ON the live version
|
||||||
(Some(have), Some(live)) if have == live => std::fs::read(Layout::new(&install_dir, have).version_dir().join("manifest.json"))
|
// (an older copy says where IT came from, not where the live pack did), but the data
|
||||||
|
// payload is worth showing either way - it is carried forward unchanged, so an older
|
||||||
|
// manifest describes the same seed and the same tiles.
|
||||||
|
let manifest = installed.as_ref().and_then(|have| {
|
||||||
|
std::fs::read(Layout::new(&install_dir, have).version_dir().join("manifest.json"))
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|raw| serde_json::from_slice::<serde_json::Value>(&raw).ok())
|
.and_then(|raw| serde_json::from_slice::<serde_json::Value>(&raw).ok())
|
||||||
.and_then(|m| m.get("source_commit").and_then(|c| c.as_str().map(str::to_string))),
|
});
|
||||||
|
let is_live = matches!((&installed, &pack_live), (Some(have), Some(live)) if have == live);
|
||||||
|
let pack_commit = match (is_live, &manifest) {
|
||||||
|
(true, Some(m)) => m.get("source_commit").and_then(|c| c.as_str().map(str::to_string)),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
let data = match (&manifest, &installed) {
|
||||||
|
(Some(m), Some(have)) => {
|
||||||
|
Some(crate::release::data_from_manifest(m, have, is_live, now_secs() as i64))
|
||||||
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
// The launcher's own feeds are public, so this needs no token.
|
// The launcher's own feeds are public, so this needs no token.
|
||||||
@@ -2477,6 +2490,7 @@ impl Launcher {
|
|||||||
launcher_live,
|
launcher_live,
|
||||||
launcher_commit,
|
launcher_commit,
|
||||||
None,
|
None,
|
||||||
|
data,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+151
-1
@@ -23,6 +23,9 @@ pub struct ReleasePlan {
|
|||||||
pub items: Vec<ReleaseItem>,
|
pub items: Vec<ReleaseItem>,
|
||||||
/// Why the whole thing cannot run, in one line; None when it can.
|
/// Why the whole thing cannot run, in one line; None when it can.
|
||||||
pub blocked: Option<String>,
|
pub blocked: Option<String>,
|
||||||
|
/// What the pack carries but a release does not rebuild. None when nothing is installed
|
||||||
|
/// here, so there is no manifest to read it from.
|
||||||
|
pub data: Option<ReleaseData>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize)]
|
#[derive(Clone, Debug, Serialize)]
|
||||||
@@ -115,6 +118,107 @@ fn item(
|
|||||||
ReleaseItem { key, title, live, commit: commit.map(|c| c[..c.len().min(7)].to_string()), behind, dirty, note }
|
ReleaseItem { key, title, live, commit: commit.map(|c| c[..c.len().min(7)].to_string()), behind, dirty, note }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// What a pack carries that a code release does NOT rebuild.
|
||||||
|
///
|
||||||
|
/// `cut_pack.py` copies the database seed, the source chunks and the tile sets forward from
|
||||||
|
/// the previous version every time, and asserts they did not change - which is what keeps a
|
||||||
|
/// code release from quietly altering data. The cost is that they age invisibly: the seed and
|
||||||
|
/// the tiles are whatever they were the day someone last built them. This puts a date on it.
|
||||||
|
#[derive(Clone, Debug, Serialize)]
|
||||||
|
pub struct ReleaseData {
|
||||||
|
/// The version whose manifest these numbers were read from.
|
||||||
|
pub version: String,
|
||||||
|
/// Whether that is also what the DHP serves, so the numbers describe the live pack.
|
||||||
|
pub is_live: bool,
|
||||||
|
/// The seed's snapshot stamp, e.g. `20260921T125618Z`.
|
||||||
|
pub db_snapshot: Option<String>,
|
||||||
|
pub db_age_days: Option<i64>,
|
||||||
|
pub db_bytes: u64,
|
||||||
|
pub tile_sets: usize,
|
||||||
|
pub tile_bytes: u64,
|
||||||
|
/// Every distinct dataset the tile sets name.
|
||||||
|
pub tile_datasets: Vec<String>,
|
||||||
|
/// The age of the OLDEST dated dataset, which is what decides how stale the maps are. A
|
||||||
|
/// name carrying no date (`planet_heightmap`) is not counted: that one does not move.
|
||||||
|
pub tiles_age_days: Option<i64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Days from 1970-01-01 to y-m-d, proleptic Gregorian. Hinnant's algorithm, which is exact
|
||||||
|
/// and needs no date crate - the alternative was a dependency to subtract two dates.
|
||||||
|
fn days_from_civil(y: i64, m: i64, d: i64) -> i64 {
|
||||||
|
let y = if m <= 2 { y - 1 } else { y };
|
||||||
|
let era = if y >= 0 { y } else { y - 399 } / 400;
|
||||||
|
let yoe = y - era * 400;
|
||||||
|
let mp = (m + 9) % 12;
|
||||||
|
let doy = (153 * mp + 2) / 5 + d - 1;
|
||||||
|
let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
|
||||||
|
era * 146_097 + doe - 719_468
|
||||||
|
}
|
||||||
|
|
||||||
|
/// How many days ago `YYYYMMDD` was, or None when those are not eight sane digits.
|
||||||
|
fn age_days(stamp: &str, now_secs: i64) -> Option<i64> {
|
||||||
|
let digits: String = stamp.chars().take_while(|c| c.is_ascii_digit()).collect();
|
||||||
|
if digits.len() < 8 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let y: i64 = digits[0..4].parse().ok()?;
|
||||||
|
let m: i64 = digits[4..6].parse().ok()?;
|
||||||
|
let d: i64 = digits[6..8].parse().ok()?;
|
||||||
|
if !(1..=12).contains(&m) || !(1..=31).contains(&d) || !(2000..=2999).contains(&y) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(now_secs / 86_400 - days_from_civil(y, m, d))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The date a dataset name carries, if any: `protomaps_20260423` -> `20260423`.
|
||||||
|
fn dataset_stamp(dataset: &str) -> Option<&str> {
|
||||||
|
let tail = dataset.rsplit('_').next()?;
|
||||||
|
(tail.len() == 8 && tail.chars().all(|c| c.is_ascii_digit())).then_some(tail)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read the data payload out of an installed version's manifest.
|
||||||
|
pub fn data_from_manifest(m: &serde_json::Value, version: &str, is_live: bool, now_secs: i64) -> ReleaseData {
|
||||||
|
let db = m.get("db");
|
||||||
|
let db_snapshot = db.and_then(|d| d.get("snapshot")).and_then(|s| s.as_str()).map(str::to_string);
|
||||||
|
let db_bytes = db
|
||||||
|
.and_then(|d| d.get("files"))
|
||||||
|
.and_then(|f| f.as_array())
|
||||||
|
.map(|files| files.iter().filter_map(|f| f.get("size").and_then(serde_json::Value::as_u64)).sum())
|
||||||
|
.unwrap_or(0);
|
||||||
|
let sets = m.get("tiles").and_then(|t| t.get("sets")).and_then(|s| s.as_array());
|
||||||
|
let tile_bytes = sets
|
||||||
|
.map(|s| {
|
||||||
|
s.iter()
|
||||||
|
.filter_map(|set| set.get("bytes").or_else(|| set.get("size")).and_then(serde_json::Value::as_u64))
|
||||||
|
.sum()
|
||||||
|
})
|
||||||
|
.unwrap_or(0);
|
||||||
|
let mut tile_datasets: Vec<String> = Vec::new();
|
||||||
|
for set in sets.map(Vec::as_slice).unwrap_or(&[]) {
|
||||||
|
if let Some(d) = set.get("dataset").and_then(|d| d.as_str()) {
|
||||||
|
if !tile_datasets.iter().any(|have| have == d) {
|
||||||
|
tile_datasets.push(d.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let tiles_age_days = tile_datasets
|
||||||
|
.iter()
|
||||||
|
.filter_map(|d| dataset_stamp(d))
|
||||||
|
.filter_map(|s| age_days(s, now_secs))
|
||||||
|
.max();
|
||||||
|
ReleaseData {
|
||||||
|
version: version.to_string(),
|
||||||
|
is_live,
|
||||||
|
db_age_days: db_snapshot.as_deref().and_then(|s| age_days(s, now_secs)),
|
||||||
|
db_snapshot,
|
||||||
|
db_bytes,
|
||||||
|
tile_sets: sets.map(|s| s.len()).unwrap_or(0),
|
||||||
|
tile_bytes,
|
||||||
|
tile_datasets,
|
||||||
|
tiles_age_days,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Everything the Release panel shows.
|
/// Everything the Release panel shows.
|
||||||
pub fn plan(
|
pub fn plan(
|
||||||
dev_root: &Path,
|
dev_root: &Path,
|
||||||
@@ -125,6 +229,7 @@ pub fn plan(
|
|||||||
launcher_live: Option<String>,
|
launcher_live: Option<String>,
|
||||||
launcher_commit: Option<String>,
|
launcher_commit: Option<String>,
|
||||||
api_live: Option<String>,
|
api_live: Option<String>,
|
||||||
|
data: Option<ReleaseData>,
|
||||||
) -> ReleasePlan {
|
) -> ReleasePlan {
|
||||||
let docker = docker_running();
|
let docker = docker_running();
|
||||||
let scripts = mcp_repo.join("scripts").join("release_pack.ps1").is_file()
|
let scripts = mcp_repo.join("scripts").join("release_pack.ps1").is_file()
|
||||||
@@ -157,7 +262,7 @@ pub fn plan(
|
|||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
ReleasePlan { docker, online, scripts, items, blocked }
|
ReleasePlan { docker, online, scripts, items, blocked, data }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The Release console's buffer: filled by the running release, read by the window.
|
/// The Release console's buffer: filled by the running release, read by the window.
|
||||||
@@ -446,6 +551,51 @@ pub fn next_launcher(live: &str) -> Option<String> {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn the_pack_says_how_old_the_data_it_carries_is() {
|
||||||
|
// 2026-10-01, the day this was written, as unix seconds.
|
||||||
|
let now = days_from_civil(2026, 10, 1) * 86_400;
|
||||||
|
assert_eq!(age_days("20261001T000000Z", now), Some(0), "today");
|
||||||
|
assert_eq!(age_days("20260921T125618Z", now), Some(10), "the real 0.72.0 seed");
|
||||||
|
assert_eq!(age_days("20260423", now), Some(161), "the real basemap extract");
|
||||||
|
// Leap day, and a year boundary: the arithmetic is the whole point of this test.
|
||||||
|
assert_eq!(age_days("20260228", now), Some(215));
|
||||||
|
assert_eq!(age_days("20251231", now), Some(274));
|
||||||
|
// Nonsense must not become a date.
|
||||||
|
assert_eq!(age_days("", now), None);
|
||||||
|
assert_eq!(age_days("2026", now), None);
|
||||||
|
assert_eq!(age_days("20261301", now), None, "month 13");
|
||||||
|
assert_eq!(age_days("latest", now), None);
|
||||||
|
|
||||||
|
assert_eq!(dataset_stamp("protomaps_20260423"), Some("20260423"));
|
||||||
|
assert_eq!(dataset_stamp("planet_heightmap"), None, "undated: it does not move");
|
||||||
|
|
||||||
|
// A manifest shaped like the real one.
|
||||||
|
let m: serde_json::Value = serde_json::json!({
|
||||||
|
"db": { "snapshot": "20260921T125618Z",
|
||||||
|
"files": [ {"size": 1000}, {"size": 2000} ] },
|
||||||
|
"tiles": { "sets": [
|
||||||
|
{"style": "dark", "dataset": "protomaps_20260423", "bytes": 10},
|
||||||
|
{"style": "dark_v2", "dataset": "protomaps_20260423", "bytes": 20},
|
||||||
|
{"style": "heightmap", "dataset": "planet_heightmap", "bytes": 70}
|
||||||
|
]}
|
||||||
|
});
|
||||||
|
let d = data_from_manifest(&m, "0.72.0", true, now);
|
||||||
|
assert_eq!(d.db_bytes, 3000);
|
||||||
|
assert_eq!(d.db_age_days, Some(10));
|
||||||
|
assert_eq!(d.tile_sets, 3);
|
||||||
|
assert_eq!(d.tile_bytes, 100);
|
||||||
|
// Deduplicated, and the undated set does not drag the age to None.
|
||||||
|
assert_eq!(d.tile_datasets, vec!["protomaps_20260423".to_string(), "planet_heightmap".to_string()]);
|
||||||
|
assert_eq!(d.tiles_age_days, Some(161));
|
||||||
|
|
||||||
|
// A manifest with no tiles at all must not panic or invent numbers.
|
||||||
|
let empty = data_from_manifest(&serde_json::json!({}), "0.1.0", false, now);
|
||||||
|
assert_eq!(empty.tile_sets, 0);
|
||||||
|
assert_eq!(empty.db_bytes, 0);
|
||||||
|
assert_eq!(empty.db_age_days, None);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn the_next_version_moves_the_right_place() {
|
fn the_next_version_moves_the_right_place() {
|
||||||
// A pack is a minor (0.71.0 -> 0.72.0); the launcher is a patch (0.1.38 -> 0.1.39).
|
// A pack is a minor (0.71.0 -> 0.72.0); the launcher is a patch (0.1.38 -> 0.1.39).
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
||||||
"productName": "ODIAN Launcher",
|
"productName": "ODIAN Launcher",
|
||||||
"version": "0.1.42",
|
"version": "0.1.43",
|
||||||
"identifier": "fr.odian.launcher",
|
"identifier": "fr.odian.launcher",
|
||||||
"build": {
|
"build": {
|
||||||
"frontendDist": "../dist",
|
"frontendDist": "../dist",
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import { prefersReducedMotion } from "svelte/motion";
|
import { prefersReducedMotion } from "svelte/motion";
|
||||||
import Icon from "./Icon.svelte";
|
import Icon from "./Icon.svelte";
|
||||||
import { launcher } from "../lib/launcher.svelte";
|
import { launcher } from "../lib/launcher.svelte";
|
||||||
|
import { formatBytes } from "../lib/format";
|
||||||
|
|
||||||
// Its own panel rather than a block inside the action card: the card is anchored to the
|
// Its own panel rather than a block inside the action card: the card is anchored to the
|
||||||
// bottom of the window and cannot grow, so three rows plus a password field pushed the
|
// bottom of the window and cannot grow, so three rows plus a password field pushed the
|
||||||
@@ -24,6 +25,24 @@
|
|||||||
/** The short version of a row's state, for the column that has room for three words.
|
/** 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
|
NOT called `state`: that shadows the `$state` rune in this file, and everything
|
||||||
declared with it silently stops being reactive. */
|
declared with it silently stops being reactive. */
|
||||||
|
/** `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";
|
||||||
|
const y = Number(snapshot.slice(0, 4));
|
||||||
|
const m = Number(snapshot.slice(4, 6));
|
||||||
|
const d = Number(snapshot.slice(6, 8));
|
||||||
|
if (!y || !m || !d) return snapshot;
|
||||||
|
const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
||||||
|
return `${d} ${MONTHS[m - 1] ?? "?"} ${y}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function aged(days: number | null): string {
|
||||||
|
if (days === null) return "";
|
||||||
|
if (days <= 0) return "today";
|
||||||
|
if (days === 1) return "1 day old";
|
||||||
|
return `${days} days old`;
|
||||||
|
}
|
||||||
|
|
||||||
function rowState(row: { behind: boolean | null; dirty: boolean }): { text: string; tone: string } {
|
function rowState(row: { behind: boolean | null; dirty: boolean }): { text: string; tone: string } {
|
||||||
if (row.dirty) return { text: "uncommitted", tone: "warn" };
|
if (row.dirty) return { text: "uncommitted", tone: "warn" };
|
||||||
if (row.behind === true) return { text: "behind", tone: "go" };
|
if (row.behind === true) return { text: "behind", tone: "go" };
|
||||||
@@ -62,6 +81,32 @@
|
|||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
<!-- The payload a release does NOT rebuild. It is copied forward from the previous
|
||||||
|
version every time, so it ages quietly: this is the only place that says by how
|
||||||
|
much, which is what turns "the tiles are old" into a decision. -->
|
||||||
|
{#if plan.data}
|
||||||
|
{@const d = plan.data}
|
||||||
|
<div class="payload">
|
||||||
|
<span class="payload-head">Carried forward — a release does not rebuild these</span>
|
||||||
|
<div class="payload-row">
|
||||||
|
<span class="payload-name">Database seed</span>
|
||||||
|
<span class="payload-val mono">{stamped(d.db_snapshot)}</span>
|
||||||
|
<span class="payload-age" class:stale={(d.db_age_days ?? 0) > 30}>{aged(d.db_age_days)}</span>
|
||||||
|
<span class="payload-size mono">{formatBytes(d.db_bytes)}</span>
|
||||||
|
</div>
|
||||||
|
<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>
|
||||||
|
<span class="payload-size mono">{formatBytes(d.tile_bytes)}</span>
|
||||||
|
</div>
|
||||||
|
<span class="payload-foot mono">{d.tile_datasets.join(" · ")}</span>
|
||||||
|
{#if !d.is_live}
|
||||||
|
<span class="payload-foot">Read from this PC's {d.version}, which is not what the DHP serves.</span>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<div class="checks mono">
|
<div class="checks mono">
|
||||||
<span class:ok={plan.online}>DHP {plan.online ? "✓" : "✗"}</span>
|
<span class:ok={plan.online}>DHP {plan.online ? "✓" : "✗"}</span>
|
||||||
<span class:ok={plan.docker}>Docker {plan.docker ? "✓" : "✗"}</span>
|
<span class:ok={plan.docker}>Docker {plan.docker ? "✓" : "✗"}</span>
|
||||||
@@ -108,6 +153,48 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
/* The data payload. Quiet by default - it is context, not a control - but the age goes
|
||||||
|
amber once it is old enough to matter, which is the whole point of showing it. */
|
||||||
|
.payload {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
margin: 10px 0 2px;
|
||||||
|
padding: 8px 10px;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
.payload-head {
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: var(--text-xs);
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
.payload-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 7.5rem 7rem minmax(0, 1fr) auto;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: baseline;
|
||||||
|
font-size: var(--text-xs);
|
||||||
|
}
|
||||||
|
.payload-name {
|
||||||
|
color: var(--ink-2);
|
||||||
|
}
|
||||||
|
.payload-val,
|
||||||
|
.payload-size {
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
.payload-age {
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
.payload-age.stale {
|
||||||
|
color: var(--warn);
|
||||||
|
}
|
||||||
|
.payload-foot {
|
||||||
|
margin-top: 4px;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: var(--text-xs);
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
.release {
|
.release {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@@ -815,6 +815,18 @@ class MockBackend implements Backend {
|
|||||||
{ key: "mcp", title: "MCP server", live: null, commit: "c7d8e9f", behind: null,
|
{ key: "mcp", title: "MCP server", live: null, commit: "c7d8e9f", behind: null,
|
||||||
dirty: false, note: "the script asks the node whether it is already running this commit" },
|
dirty: false, note: "the script asks the node whether it is already running this commit" },
|
||||||
],
|
],
|
||||||
|
// The real numbers from pack 0.72.0, so the panel can be judged on what it will show.
|
||||||
|
data: {
|
||||||
|
version: this.status.installed ?? "0.72.0",
|
||||||
|
is_live: true,
|
||||||
|
db_snapshot: "20260921T125618Z",
|
||||||
|
db_age_days: 10,
|
||||||
|
db_bytes: 15_354_000_000,
|
||||||
|
tile_sets: 8,
|
||||||
|
tile_bytes: 102_650_000_000,
|
||||||
|
tile_datasets: ["protomaps_20260423", "planet_heightmap"],
|
||||||
|
tiles_age_days: 161,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -303,6 +303,21 @@ export interface ReleaseItem {
|
|||||||
note: string;
|
note: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** What a pack carries but a code release does not rebuild: the DB seed and the tile sets. */
|
||||||
|
export interface ReleaseData {
|
||||||
|
version: string;
|
||||||
|
/** The numbers describe the live pack, not just this PC's older copy. */
|
||||||
|
is_live: boolean;
|
||||||
|
db_snapshot: string | null;
|
||||||
|
db_age_days: number | null;
|
||||||
|
db_bytes: number;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ReleasePlan {
|
export interface ReleasePlan {
|
||||||
docker: boolean;
|
docker: boolean;
|
||||||
online: boolean;
|
online: boolean;
|
||||||
@@ -310,6 +325,8 @@ export interface ReleasePlan {
|
|||||||
items: ReleaseItem[];
|
items: ReleaseItem[];
|
||||||
/** Why a release cannot run at all; null when it can. */
|
/** Why a release cannot run at all; null when it can. */
|
||||||
blocked: string | null;
|
blocked: string | null;
|
||||||
|
/** Null when nothing is installed here, so there is no manifest to read it from. */
|
||||||
|
data: ReleaseData | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A slice of the running release's output: what the window has not shown yet. */
|
/** A slice of the running release's output: what the window has not shown yet. */
|
||||||
|
|||||||
Reference in New Issue
Block a user