Files
ODIAN_Launcher/src/components/StackList.svelte
T
Kelorastus b7f3061be3 ODIAN Launcher: hub, installer, local stack and remembered sign-in (M2)
- Tauri 2 + Svelte 5 hub in ODIAN dark: patch notes and roadmap, install and
  update progress, local stack status, settings with DHP and MCP addresses.
- crates/odian-packs: Rust installer for pack format v1 (signed manifests,
  resumable verified downloads from a DHP or an ODIAN drive), byte-compatible
  with ODIAN_MCP_Server's Python odian_packs.
- Local stack on 127.0.0.1: Postgres -> Engine API -> tile server -> Odian.exe,
  one Job Object, clean shutdown; database snapshot restore on a fresh PC only.
- Keycloak sign-in through the DHP's MCP, remembered until Sign out with a
  launcher session that renews the Engine token silently.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 15:23:17 +02:00

128 lines
2.9 KiB
Svelte

<script lang="ts">
import StateDot, { type DotTone } from "./StateDot.svelte";
import { COMPONENT_DETAIL, COMPONENT_LABEL, STACK_STATE_LABEL } from "../lib/format";
import { STACK_ORDER, type StackMap } from "../lib/launcher.svelte";
import type { Phase, StackComponent, StackState } from "../lib/types";
interface Props {
stack: StackMap;
phase: Phase;
}
let { stack, phase }: Props = $props();
type Shown = StackState | "waiting";
const TONE: Record<Shown, DotTone> = {
waiting: "idle",
starting: "warn",
ready: "ok",
stopped: "idle",
failed: "danger",
};
function shownState(component: StackComponent): Shown {
const event = stack[component];
if (event) return event.state;
// No event seen yet (e.g. the UI reloaded while ODIAN was already open).
return phase === "running" ? "ready" : "waiting";
}
</script>
<ol class="stack" aria-label="Local stack">
{#each STACK_ORDER as component, index (component)}
{@const state = shownState(component)}
{@const message = stack[component]?.message}
<li class="row {state}">
<span class="index mono" aria-hidden="true">{String(index + 1).padStart(2, "0")}</span>
<span class="dot-cell"><StateDot tone={TONE[state]} pulse={state === "starting"} /></span>
<span class="name">
{COMPONENT_LABEL[component]}
<span class="detail mono">{COMPONENT_DETAIL[component]}</span>
</span>
<span class="state mono">{STACK_STATE_LABEL[state]}</span>
{#if message}
<span class="message mono selectable" title={message}>{message}</span>
{/if}
</li>
{/each}
</ol>
<style>
.stack {
display: grid;
margin: 0;
padding: 0;
list-style: none;
border-top: 1px solid var(--line);
}
.row {
position: relative;
display: grid;
grid-template-columns: 22px 20px minmax(0, 1fr) auto;
align-items: center;
column-gap: 8px;
min-height: 44px;
padding: 8px 0;
border-bottom: 1px solid var(--line);
}
.index {
color: var(--muted);
font-size: var(--text-xs);
}
.dot-cell {
display: grid;
place-items: center;
}
.name {
overflow: hidden;
color: var(--ink-2);
font-size: var(--text-sm);
font-weight: 500;
white-space: nowrap;
text-overflow: ellipsis;
}
.detail {
margin-left: 6px;
color: var(--muted);
font-size: var(--text-xs);
font-weight: 400;
}
.state {
color: var(--muted);
font-size: var(--text-xs);
letter-spacing: 0.06em;
text-transform: uppercase;
}
.message {
grid-column: 3 / 5;
overflow: hidden;
color: var(--muted);
font-size: var(--text-xs);
white-space: nowrap;
text-overflow: ellipsis;
}
.ready .state {
color: var(--ok);
}
.starting .state {
color: var(--warn);
}
.failed .state,
.failed .message {
color: var(--danger);
}
.ready .name,
.starting .name {
color: var(--ink);
}
</style>