Add Super Viewer read-only administrator role
CI / checks (push) Successful in 5m40s

This commit is contained in:
2026-10-11 23:30:40 -04:00
parent 253d47300c
commit 4ea291d305
48 changed files with 1684 additions and 933 deletions
+57 -52
View File
@@ -24,6 +24,7 @@
updated_at: string;
}
const games: ZombieGame[] = ['t5', 't6'];
const administrator = $derived($page.data.user?.role === 'admin');
let open = $state(false);
let search = $state('');
let gameFilter = $state('');
@@ -85,6 +86,7 @@
void reload();
});
function addKey() {
if (!administrator) return;
plutoKey = '';
label = '';
error = '';
@@ -93,7 +95,7 @@
}
async function save(e: SubmitEvent) {
e.preventDefault();
if (!plutoKey.trim() || busy) return;
if (!administrator || !plutoKey.trim() || busy) return;
busy = true;
error = '';
notice = '';
@@ -117,7 +119,7 @@
}
}
async function remove(key: SavedKey) {
if (key.assigned_servers.length) return;
if (!administrator || busy || key.assigned_servers.length) return;
if (
!(await confirmAction({
title: 'Remove saved key?',
@@ -154,7 +156,9 @@
title="Keys"
subtitle="Pool Plutonium keys by game. Any available key can launch any map or lobby in that game."
>
{#snippet actions()}<Button variant="primary" onclick={addKey}>Add key</Button>{/snippet}
{#snippet actions()}{#if administrator}<Button variant="primary" onclick={addKey}
>Add key</Button
>{/if}{/snippet}
</SectionHeader>
<p class="key-help">
A key stays assigned while its server exists, including when stopped, and becomes available
@@ -209,16 +213,17 @@
{#if loading}<p>Loading keys…</p>
{:else if !filtered.length}<EmptyState
title={keys.length ? 'No matching keys' : 'No saved keys'}
description="Add keys here. Launches automatically use an available key from their game’s pool."
description={administrator
? 'Add keys here. Launches automatically use an available key from their game’s pool.'
: 'Launches automatically use an available key from their game’s pool.'}
/>
{:else}
<div class="table-wrap">
<table>
<thead
><tr
><th>Game / label</th><th>Availability</th><th>Last saved</th><th
>Actions</th
></tr
><th>Game / label</th><th>Availability</th><th>Last saved</th
>{#if administrator}<th>Actions</th>{/if}</tr
></thead
>
<tbody
@@ -237,13 +242,13 @@
>{/if}</td
>
<td><RelativeTime value={key.updated_at} /></td>
<td
><Button
ariaLabel={`Remove ${key.label || key.game.toUpperCase() + ' key'}`}
disabled={busy || !!key.assigned_servers.length}
onclick={() => remove(key)}>Remove</Button
>{#if key.assigned_servers.length}<small>In use</small>{/if}</td
>
{#if administrator}<td
><Button
ariaLabel={`Remove ${key.label || key.game.toUpperCase() + ' key'}`}
disabled={busy || !!key.assigned_servers.length}
onclick={() => remove(key)}>Remove</Button
>{#if key.assigned_servers.length}<small>In use</small>{/if}</td
>{/if}
</tr>
{/each}</tbody
>
@@ -252,44 +257,44 @@
{/if}
<ListFooter total={filtered.length} bind:limit onchange={saveSearch} label="keys" />
</div>
<Dialog bind:open title="Add key" {busy}>
{#if error}<p role="alert" class="error">{error}</p>{/if}
<form onsubmit={save}>
<Field label="Game">
<Select bind:value={game} disabled={busy}
><option value="t5">Black Ops I</option><option value="t6">Black Ops II</option
></Select
></Field
>
<Field
label="Label (optional)"
hint="Record the hostname you chose in Plutonium, or another name that helps identify this key."
>
<Input bind:value={label} placeholder="e.g. Electric Cherry 5" disabled={busy} />
</Field>
<Field
label="Plutonium server key"
hint="Available to any new lobby in this game. Existing credentials are never displayed."
>
<Input
type="password"
bind:value={plutoKey}
placeholder="Paste key"
autocomplete="off"
required
disabled={busy}
/>
</Field>
<div class="actions">
<Button
type="submit"
variant="primary"
disabled={busy || loading || !plutoKey.trim()}
loading={busy}>Save key</Button
><Button onclick={() => (open = false)} disabled={busy}>Cancel</Button>
</div>
</form>
</Dialog>
{#if administrator}<Dialog bind:open title="Add key" {busy}>
{#if error}<p role="alert" class="error">{error}</p>{/if}
<form onsubmit={save}>
<Field label="Game">
<Select bind:value={game} disabled={busy}
><option value="t5">Black Ops I</option><option value="t6">Black Ops II</option
></Select
></Field
>
<Field
label="Label (optional)"
hint="Record the hostname you chose in Plutonium, or another name that helps identify this key."
>
<Input bind:value={label} placeholder="e.g. Electric Cherry 5" disabled={busy} />
</Field>
<Field
label="Plutonium server key"
hint="Available to any new lobby in this game. Existing credentials are never displayed."
>
<Input
type="password"
bind:value={plutoKey}
placeholder="Paste key"
autocomplete="off"
required
disabled={busy}
/>
</Field>
<div class="actions">
<Button
type="submit"
variant="primary"
disabled={busy || loading || !plutoKey.trim()}
loading={busy}>Save key</Button
><Button onclick={() => (open = false)} disabled={busy}>Cancel</Button>
</div>
</form>
</Dialog>{/if}
<style>
.page {