This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
import Field from '$ui/components/primitives/Field.svelte';
|
||||
import RelativeTime from '$ui/components/RelativeTime.svelte';
|
||||
import { page } from '$app/stores';
|
||||
import { canOperate } from '$lib/auth.js';
|
||||
import { contextHref } from '$lib/navigation-context.js';
|
||||
import { timeFormat } from '$ui/stores/time-zone.js';
|
||||
import { onMount } from 'svelte';
|
||||
@@ -49,6 +50,7 @@
|
||||
}
|
||||
|
||||
let betas = $state<BetaListRow[]>([]);
|
||||
const editable = $derived(canOperate($page.data?.user?.role));
|
||||
let loading = $state(true);
|
||||
let error = $state('');
|
||||
let tab = $state('active');
|
||||
@@ -122,7 +124,7 @@
|
||||
}
|
||||
onMount(() => {
|
||||
const query = new URLSearchParams(window.location.search);
|
||||
if (query.has('artifact') || query.has('pluginId')) {
|
||||
if (editable && (query.has('artifact') || query.has('pluginId'))) {
|
||||
void goto('/betas/new' + window.location.search, { replaceState: true });
|
||||
return;
|
||||
}
|
||||
@@ -141,7 +143,7 @@
|
||||
<svelte:head><title>Betas · Electric Cherry</title></svelte:head>
|
||||
<div class="page">
|
||||
<SectionHeader title="Betas">
|
||||
{#snippet actions()}{#if loading || betas.length || error}<Button
|
||||
{#snippet actions()}{#if editable && (loading || betas.length || error)}<Button
|
||||
variant="primary"
|
||||
href="/betas/new">New beta</Button
|
||||
>{/if}{/snippet}
|
||||
@@ -230,7 +232,8 @@
|
||||
pageNumber = 1;
|
||||
saveContext();
|
||||
}}>Clear filters</Button
|
||||
>{:else}<Button variant="primary" href="/betas/new">New beta</Button
|
||||
>{:else if editable}<Button variant="primary" href="/betas/new"
|
||||
>New beta</Button
|
||||
>{/if}{/snippet}
|
||||
</EmptyState>
|
||||
{:else}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import { timeFormat } from '$ui/stores/time-zone.js';
|
||||
import { onMount } from 'svelte';
|
||||
import { page } from '$app/stores';
|
||||
import { canOperate } from '$lib/auth.js';
|
||||
import { artifactVersionLabel } from '$lib/artifact-version.js';
|
||||
import type { BetaTestView } from '$lib/beta-plan.js';
|
||||
import type { BetaPromotionPreview } from '$lib/beta-promotion.js';
|
||||
@@ -92,7 +93,9 @@
|
||||
let updateOpen = $state(false),
|
||||
updateRevision = $state(1),
|
||||
updateFiles = $state<BetaFile[]>([]);
|
||||
const editable = $derived(canOperate($page.data?.user?.role));
|
||||
function openUpdate() {
|
||||
if (!editable) return;
|
||||
updateRevision = revision.revision;
|
||||
updateFiles = [...scripts];
|
||||
updateOpen = true;
|
||||
@@ -157,6 +160,7 @@
|
||||
);
|
||||
|
||||
function testAction(action: string) {
|
||||
if (!editable) return;
|
||||
moreActionsOpen = false;
|
||||
void testProgress?.action(action);
|
||||
}
|
||||
@@ -260,7 +264,7 @@
|
||||
}
|
||||
}
|
||||
async function promote() {
|
||||
if (!preview) return;
|
||||
if (!editable || !preview) return;
|
||||
acting = 'promote';
|
||||
reviewError = '';
|
||||
flash = '';
|
||||
@@ -288,6 +292,7 @@
|
||||
}
|
||||
}
|
||||
async function rollback() {
|
||||
if (!editable) return;
|
||||
const reject = beta?.state === 'ready-promote';
|
||||
const restarts =
|
||||
test?.plan.rollbackRestart === 'manual'
|
||||
@@ -318,6 +323,7 @@
|
||||
}
|
||||
}
|
||||
async function retry(server: string) {
|
||||
if (!editable) return;
|
||||
acting = `retry:${server}`;
|
||||
flash = '';
|
||||
try {
|
||||
@@ -392,7 +398,7 @@
|
||||
$page.url
|
||||
)}>View rollback campaign</Button
|
||||
>
|
||||
{:else if closed}<Button
|
||||
{:else if closed && editable}<Button
|
||||
variant="primary"
|
||||
href={contextHref(`/betas/new?reuse=${id}`, $page.url)}
|
||||
>Reuse files and test plan</Button
|
||||
@@ -410,12 +416,12 @@
|
||||
? 'failures'
|
||||
: 'issues'}</Button
|
||||
>
|
||||
{:else if presentation.key === 'paused' && !testExpired}<Button
|
||||
{:else if editable && presentation.key === 'paused' && !testExpired}<Button
|
||||
variant="primary"
|
||||
onclick={() => testAction('resume')}
|
||||
disabled={busy}>Resume testing</Button
|
||||
>{/if}
|
||||
{#if !closed || promotion || rollbackCampaign}<details
|
||||
{#if editable && (!closed || promotion || rollbackCampaign)}<details
|
||||
class="more-actions"
|
||||
bind:open={moreActionsOpen}
|
||||
>
|
||||
@@ -583,7 +589,7 @@
|
||||
$page.url
|
||||
)}>View provisioning</a
|
||||
>{/if}
|
||||
{#if server.stage === 'failed' && !server.server_deleted_at && !closed}<Button
|
||||
{#if editable && server.stage === 'failed' && !server.server_deleted_at && !closed}<Button
|
||||
size="sm"
|
||||
onclick={() => retry(server.server)}
|
||||
disabled={busy}
|
||||
@@ -694,15 +700,15 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<BetaRevisionDialog
|
||||
bind:open={updateOpen}
|
||||
betaId={id}
|
||||
revision={updateRevision}
|
||||
files={updateFiles}
|
||||
servers={servers.map((server) => server.server)}
|
||||
timeoutHours={test?.plan.timeoutHours}
|
||||
onUpdate={load}
|
||||
/>
|
||||
{#if editable}<BetaRevisionDialog
|
||||
bind:open={updateOpen}
|
||||
betaId={id}
|
||||
revision={updateRevision}
|
||||
files={updateFiles}
|
||||
servers={servers.map((server) => server.server)}
|
||||
timeoutHours={test?.plan.timeoutHours}
|
||||
onUpdate={load}
|
||||
/>{/if}
|
||||
|
||||
<Dialog
|
||||
bind:open={reviewOpen}
|
||||
@@ -763,13 +769,13 @@
|
||||
>Cancel</Button
|
||||
>{#if preview}<Button onclick={reviewPromotion} disabled={acting !== null}
|
||||
>Refresh review</Button
|
||||
><Button
|
||||
variant="primary"
|
||||
onclick={promote}
|
||||
loading={acting === 'promote'}
|
||||
disabled={acting !== null || !!preview.blockers.length}
|
||||
>Promote to {preview.servers.length} servers</Button
|
||||
>{/if}{/snippet}
|
||||
>{#if editable}<Button
|
||||
variant="primary"
|
||||
onclick={promote}
|
||||
loading={acting === 'promote'}
|
||||
disabled={acting !== null || !!preview.blockers.length}
|
||||
>Promote to {preview.servers.length} servers</Button
|
||||
>{/if}{/if}{/snippet}
|
||||
</Dialog>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user