Add native host management and role-based operator authentication
Introduce an outbound Python host agent, Debian installer, durable remote operations, immutable game-file libraries, host-aware deployments and ports, and occupancy-safe campaigns and draining. Native hosts do not require Komodo. Add administrator, operator and viewer accounts, protected management routes, secure sessions, password recovery, audit history and separate telemetry tokens. Document first-admin setup, reporter migration and live-host acceptance checks. Validation: 1,343 TypeScript tests and 39 agent tests passed, along with type checking, the production build, compiled SSR/API smoke tests and administrator recovery. Real Debian/Docker T5/T6 acceptance remains unrun.
This commit is contained in:
@@ -29,6 +29,7 @@
|
||||
let tab = $state('collections');
|
||||
let limit = $state(25);
|
||||
let launchLimit = $state(10);
|
||||
const canOperate = $derived($page.data.user?.role === 'admin' || $page.data.user?.role === 'operator');
|
||||
const activeLaunches = $derived(launches.filter(launch => launch.active > 0));
|
||||
const launchHistory = $derived(launches.filter(launch => launch.active === 0));
|
||||
const visibleLaunches = $derived(tab === 'active' ? activeLaunches : launchHistory);
|
||||
@@ -92,10 +93,10 @@
|
||||
<svelte:head><title>Collections | Electric Cherry</title></svelte:head>
|
||||
<div class="page">
|
||||
<SectionHeader title="Collections" subtitle="Save your lineup, preview fleet changes, and follow launch progress and history.">
|
||||
{#snippet actions()}<Button variant="primary" onclick={() => { editingBlueprint = null; builderOpen = true; }}>Create collection</Button>{/snippet}
|
||||
{#snippet actions()}{#if canOperate}<Button variant="primary" onclick={() => { editingBlueprint = null; builderOpen = true; }}>Create collection</Button>{/if}{/snippet}
|
||||
</SectionHeader>
|
||||
<BlueprintBuilder bind:open={builderOpen} blueprint={editingBlueprint} onsaved={blueprintSaved} />
|
||||
<DeployCollectionDialog bind:open={collectionOpen} {blueprintId} />
|
||||
{#if canOperate}<BlueprintBuilder bind:open={builderOpen} blueprint={editingBlueprint} onsaved={blueprintSaved} />
|
||||
<DeployCollectionDialog bind:open={collectionOpen} {blueprintId} />{/if}
|
||||
<Tabs {tabs} bind:value={tab} onchange={() => { launchLimit = 10; saveContext(); }} ariaLabel="Collection views" idPrefix="collection-tabs" panelId="collection-panel" />
|
||||
<div id="collection-panel" role="tabpanel" aria-labelledby={"collection-tabs-" + tab} class="panel">
|
||||
{#if tab === 'collections'}
|
||||
@@ -103,7 +104,7 @@
|
||||
<h2>Your collections <span>{blueprints.length}</span></h2>
|
||||
{#if blueprintError}<p role="alert" class="error">{blueprintError} <button onclick={loadBlueprints}>Retry</button></p>{/if}
|
||||
{#if blueprintsLoading}<p>Loading collections…</p>
|
||||
{:else if !blueprints.length}<div class="blueprint-empty"><strong>Plan the whole lineup.</strong><p>Add maps from either game, choose the total lobbies each needs, and fill any gaps from one collection.</p><button onclick={() => { editingBlueprint = null; builderOpen = true; }}>Create your first collection →</button></div>
|
||||
{:else if !blueprints.length}<div class="blueprint-empty"><strong>No collections yet.</strong><p>Collections define the maps and lobby counts for a fleet. Operators can create and launch them.</p>{#if canOperate}<button onclick={() => { editingBlueprint = null; builderOpen = true; }}>Create your first collection →</button>{/if}</div>
|
||||
{:else}
|
||||
<div class="blueprints">
|
||||
{#each blueprints.slice(0,limit) as blueprint (blueprint.id)}
|
||||
@@ -115,11 +116,11 @@
|
||||
<h3>{blueprint.name}</h3>
|
||||
<p>{blueprint.description || 'Fill missing lobbies or match your fleet to this lineup exactly.'}</p>
|
||||
<ul class="blueprint-lineup">{#each blueprint.entries as entry}<li><strong>{entry.count}×</strong> {entry.mapName} <small>{entry.game.toUpperCase()}</small></li>{/each}</ul>
|
||||
<div class="blueprint-footer">
|
||||
{#if canOperate}<div class="blueprint-footer">
|
||||
<Button onclick={() => { blueprintId = blueprint.id; collectionOpen = true; }}>Preview fleet changes →</Button>
|
||||
<button class="edit" onclick={() => { editingBlueprint = blueprint; builderOpen = true; }}>Edit</button>
|
||||
<button class="remove" disabled={!!removing} onclick={() => removeBlueprint(blueprint.id)}>Remove</button>
|
||||
</div>
|
||||
</div>{/if}
|
||||
</article>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user