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
+18 -8
View File
@@ -4,6 +4,7 @@
import Field from '$ui/components/primitives/Field.svelte';
import { countLabel } from '$ui/plural.js';
import { page } from '$app/stores';
import { canOperate } from '$lib/auth.js';
import { contextHref, safeReturnHref } from '$lib/navigation-context.js';
import Breadcrumbs from '$ui/components/primitives/Breadcrumbs.svelte';
import { timeFormat } from '$ui/stores/time-zone.js';
@@ -91,6 +92,7 @@
availability = $state(''),
now = $state(Date.now());
const steps = ['Files', 'Servers', 'Test plan', 'Review'];
const editable = $derived(canOperate($page.data?.user?.role));
const fleetServers = $derived($sharedFleet);
const eligibleFiles = $derived(
scripts.filter((file) => file.runningBetaId === null && file.latestVersion !== null)
@@ -307,6 +309,7 @@
}
}
async function initialize(ignoreDraft = false) {
if (!editable) return;
loading = true;
flash = '';
draftReady = false;
@@ -413,7 +416,7 @@
if (useLinked) void initialize(true);
}
$effect(() => {
if (!draftReady || loading || openingBeta) return;
if (!editable || !draftReady || loading || openingBeta) return;
const draft: BetaDraft = {
version: 1,
savedAt: new Date().toISOString(),
@@ -467,6 +470,7 @@
}
});
onMount(() => {
if (!editable) return;
void initialize();
const timer = setInterval(() => (now = serverTimeNow()), 10000);
const unload = (event: BeforeUnloadEvent) => {
@@ -547,7 +551,7 @@
}
async function openBeta(event: Event) {
event.preventDefault();
if (step !== 3 || forwardReason || openingBeta) return;
if (!editable || step !== 3 || forwardReason || openingBeta) return;
if (betaPlanIssues(testPlan, serverCount).length) {
await move(2);
await planBuilder?.validate();
@@ -606,15 +610,21 @@
title="New beta"
subtitle="Test files across games in one beta. Choose one instance count for every game."
/>
{#if flash}<div class="ui-error" role="alert">
{#if editable && flash}<div class="ui-error" role="alert">
{flash}{#if !scripts.length}<Button size="sm" onclick={() => initialize()}>Retry</Button
>{/if}
</div>{/if}
{#if $fleetError}<div class="ui-error" role="alert">
{#if editable && $fleetError}<div class="ui-error" role="alert">
{$fleetError}<Button size="sm" onclick={() => refreshFleet(true)}>Retry servers</Button>
</div>{/if}
{#if notice}<p role="status" class="notice">{notice}</p>{/if}
{#if loading}<EmptyState title="Loading beta setup…" />
{#if editable && notice}<p role="status" class="notice">{notice}</p>{/if}
{#if !editable}<EmptyState
title="Beta creation requires operator access"
description="You can review existing betas, test results, and saved evidence."
>
{#snippet actions()}<Button href="/betas">View betas</Button>{/snippet}
</EmptyState>
{:else if loading}<EmptyState title="Loading beta setup…" />
{:else if availableDraft}<section class="ui-panel draft">
<h2>Resume unfinished beta?</h2>
<p>
@@ -776,8 +786,8 @@
</p>
</div>
{:else}<p>
<strong>{pickedServers.size} selected</strong> · {candidateServers.length} compatible
servers. Restarts wait for confirmed empty lobbies.
<strong>{pickedServers.size} selected</strong> · {candidateServers.length}
compatible servers. Restarts wait for confirmed empty lobbies.
</p>{/if}
<details>
<summary>Selected file scopes</summary>