This commit is contained in:
Kelorastus
2026-09-16 11:42:24 +02:00
parent b7f3061be3
commit 3fdfde7043
18 changed files with 1485 additions and 21 deletions
+45
View File
@@ -16,6 +16,7 @@ type Phase =
| "signing_in" // browser sign-in in progress
| "installing" // downloading + verifying a version
| "restoring" // loading the database snapshot into local Postgres
| "syncing" // exchanging operator data with the DHP (ODIAN is not running)
| "launching" // starting Postgres → API → tile server → Odian.exe
| "running" // Odian.exe is open
| "error"; // the last long operation failed; see `error`
@@ -34,6 +35,28 @@ interface Status {
phase: Phase;
error: string | null; // set with phase "error"; cleared by the next command or `refresh`
install_dir: string; // e.g. "D:\\ODIAN"
sync: SyncStatus | null; // counts from the last sync this session; null before one
live: boolean | null; // while ODIAN runs: true = its data comes from the DHP,
// false = from this PC. null when it is not running, or
// the DHP offers no Engine API to forward to
}
interface SyncStatus {
pending: number; // local edits the DHP has not taken yet
conflicts: number; // records both sides changed: the operator decides
rejected: number; // edits the DHP refused (another operator's record, ...)
waiting: number; // DHP changes this PC could not apply yet
}
interface SyncConflict {
tbl: string; // database table, e.g. "db_ground_poi"
key: Record<string, unknown>; // the record's key columns
kind: "conflict" | "rejected";
fields: string[]; // clashing fields; "*exists": one side deleted it; "*value": a whole-value clash
base: unknown; // the DHP value before this PC's edit
mine: unknown; // this PC's value (null = deleted here)
theirs: unknown; // the DHP's value (null = deleted there)
reason: string | null; // why the DHP refused it (rejected only)
}
interface Settings {
@@ -83,12 +106,34 @@ interface LogEvent { level: "info" | "warn" | "error"; message: string; at: numb
| `get_settings` | — | `Settings` | |
| `set_settings` | `{ settings: Settings }` | `Status` | |
| `pick_folder` | — | `string \| null` | native folder picker |
| `sync_now` | — | `void` | pushes and pulls operator data now; only while ODIAN is stopped (phase `syncing` meanwhile) |
| `get_sync_conflicts` | — | `SyncConflict[]` | what waits for a decision, oldest first; starts the local Postgres just for the read |
| `resolve_sync_conflict` | `{ tbl, key, choice: "theirs" \| "mine" \| "value" \| "dismiss", value?: unknown }` | `SyncConflict[]` | records one decision and returns what is left. `theirs` takes the DHP's version; `mine` keeps this PC's (the next sync sends it); `value` keeps a field-by-field pick; `dismiss` keeps a refused edit on this PC only. Links and folder placements held with a record follow its decision |
Errors reject the promise with a string message.
Planned, not yet available: the size of `available` for the Install button, and
inspecting a drive (its version and size) before installing from it.
## Live mode
When the DHP advertises an Engine API (`engine_api_url` in its `/v1/health`), the
launcher starts the local API pointed at it and keeps a token file fresh for it. ODIAN
always talks to the local API; while the DHP answers, that API forwards the shared-data
routes there, so operators see each other's work and tracks are live. The moment the DHP
stops answering, the same API serves this PC's own database instead — no restart, and the
request that discovers the outage is already served locally. Coming back, the launcher
first sends what was edited offline, then hands the token back and ODIAN is live again.
`status.live` says which of the two is happening.
## Operator-data sync
When the installed version ships the sync schema (`versions\<v>\sync\*.sql`) and the
DHP is reachable, `launch` first syncs (push, then pull), then starts the stack. When
ODIAN closes, the launcher pushes that session's work. Offline, edits stay on this PC.
A sync that cannot finish is logged and never blocks the launch. Conflicts and refused
edits wait in the local database until `resolve_sync_conflict`.
## Events (`listen`)
| Event | Payload | When |