This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
import { serverTitle } from '$ui/server-labels.js';
|
||||
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 { onMount } from 'svelte';
|
||||
import { pollVisible } from '$ui/poll.js';
|
||||
@@ -33,6 +34,7 @@
|
||||
family: Family;
|
||||
}
|
||||
let { id, family }: Props = $props();
|
||||
const editable = $derived(canOperate($page.data?.user?.role));
|
||||
|
||||
const basePath = $derived(`/${family}s`);
|
||||
const crumbLabel = $derived(family.charAt(0).toUpperCase() + family.slice(1) + 's');
|
||||
@@ -228,7 +230,7 @@
|
||||
onMount(() => pollVisible(() => load({ seedForm: false }), 10000, false));
|
||||
|
||||
function openScopeModal(): void {
|
||||
if (runningBetaId !== null) return;
|
||||
if (!editable || runningBetaId !== null) return;
|
||||
seedScopeForm();
|
||||
flash = null;
|
||||
scopeReviewOpen = true;
|
||||
@@ -240,7 +242,7 @@
|
||||
scopeDialog?.close();
|
||||
}
|
||||
function openUploadModal(): void {
|
||||
if (runningBetaId !== null) return;
|
||||
if (!editable || runningBetaId !== null) return;
|
||||
releaseVersion = '';
|
||||
versionFile = null;
|
||||
versionNotes = '';
|
||||
@@ -273,7 +275,7 @@
|
||||
|
||||
async function uploadVersion(e: Event): Promise<void> {
|
||||
e.preventDefault();
|
||||
if (!versionFile || uploadingVersion) return;
|
||||
if (!editable || !versionFile || uploadingVersion) return;
|
||||
uploadingVersion = true;
|
||||
flash = null;
|
||||
const form = new FormData();
|
||||
@@ -304,7 +306,7 @@
|
||||
|
||||
async function saveScope(e: Event): Promise<void> {
|
||||
e.preventDefault();
|
||||
if (updatingScope || !scopeImpact || scopePreviewLoading) return;
|
||||
if (!editable || updatingScope || !scopeImpact || scopePreviewLoading) return;
|
||||
if (scopeType !== 'none' && !editedScopeValue) {
|
||||
flash = `Pick a ${scopeType}`;
|
||||
return;
|
||||
@@ -351,6 +353,7 @@
|
||||
}
|
||||
|
||||
async function deleteArtifact(): Promise<void> {
|
||||
if (!editable) return;
|
||||
if (
|
||||
!(await confirmAction({
|
||||
title: 'Delete ' + family + '?',
|
||||
@@ -428,13 +431,15 @@
|
||||
variant="primary"
|
||||
href={contextHref(`/betas/${runningBetaId}`, $page.url)}
|
||||
>View active beta</Button
|
||||
>{:else if latestVersion.id !== a.current_version_id}<Button
|
||||
>{:else if editable && latestVersion.id !== a.current_version_id}<Button
|
||||
variant="primary"
|
||||
href={contextHref(
|
||||
betaSelectionHref([{ scriptId: id, versionId: latestVersion.id }]),
|
||||
$page.url
|
||||
)}>Test latest version</Button
|
||||
>{:else}<span class="dim">Latest version is promoted</span>{/if}
|
||||
>{:else if latestVersion.id === a.current_version_id}<span class="dim"
|
||||
>Latest version is promoted</span
|
||||
>{/if}
|
||||
</div></Card
|
||||
>{/if}
|
||||
|
||||
@@ -450,24 +455,24 @@
|
||||
<Card padding="lg">
|
||||
<div class="panel-head">
|
||||
<h2>Details</h2>
|
||||
<div class="head-actions">
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
onclick={openScopeModal}
|
||||
disabled={runningBetaId !== null}
|
||||
>
|
||||
Edit scope
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
onclick={openUploadModal}
|
||||
disabled={runningBetaId !== null}
|
||||
>
|
||||
Upload new version
|
||||
</Button>
|
||||
</div>
|
||||
{#if editable}<div class="head-actions">
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
onclick={openScopeModal}
|
||||
disabled={runningBetaId !== null}
|
||||
>
|
||||
Edit scope
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
onclick={openUploadModal}
|
||||
disabled={runningBetaId !== null}
|
||||
>
|
||||
Upload new version
|
||||
</Button>
|
||||
</div>{/if}
|
||||
</div>
|
||||
|
||||
<details class="file-settings">
|
||||
@@ -584,7 +589,7 @@
|
||||
<Badge variant="online" size="sm">Promoted</Badge>
|
||||
{:else}
|
||||
<StatusBadge state={v.state} size="sm" />
|
||||
{#if runningBetaId === null}<Button
|
||||
{#if editable && runningBetaId === null}<Button
|
||||
size="sm"
|
||||
href={contextHref(
|
||||
betaSelectionHref([{ scriptId: id, versionId: v.id }]),
|
||||
@@ -604,7 +609,7 @@
|
||||
<ListFooter total={versions.length} bind:limit label="versions" />
|
||||
</Card>
|
||||
|
||||
{#if family === 'plugin'}
|
||||
{#if editable && family === 'plugin'}
|
||||
<div id="github-releases">
|
||||
<PluginGithubImport
|
||||
pluginId={id}
|
||||
@@ -615,170 +620,173 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<Card padding="lg">
|
||||
<div class="panel-head"><h2>Danger zone</h2></div>
|
||||
<Button variant="danger" onclick={deleteArtifact}>Delete {family}</Button>
|
||||
</Card>
|
||||
{#if editable}<Card padding="lg">
|
||||
<div class="panel-head"><h2>Danger zone</h2></div>
|
||||
<Button variant="danger" onclick={deleteArtifact}>Delete {family}</Button>
|
||||
</Card>{/if}
|
||||
|
||||
{#if flash}<div class="alert" role="alert">{flash}</div>{/if}
|
||||
|
||||
<!-- Scope dialog -->
|
||||
<dialog
|
||||
aria-label="Edit scope"
|
||||
onclose={() => (scopeReviewOpen = false)}
|
||||
oncancel={(event) => {
|
||||
if (updatingScope) event.preventDefault();
|
||||
}}
|
||||
bind:this={scopeDialog}
|
||||
onclick={(e) => onDialogClick(scopeDialog, e)}
|
||||
>
|
||||
<form class="dlg" onsubmit={saveScope}>
|
||||
<header class="dlg-head">
|
||||
<h3>Edit scope</h3>
|
||||
<button
|
||||
type="button"
|
||||
class="close"
|
||||
disabled={updatingScope}
|
||||
onclick={cancelScopeModal}
|
||||
aria-label="Close">×</button
|
||||
>
|
||||
</header>
|
||||
<Field label="Scope">
|
||||
<div class="scope-row">
|
||||
<Select
|
||||
ariaLabel="Scope type"
|
||||
bind:value={scopeType}
|
||||
options={scopeTypeOptions}
|
||||
{#if editable}<dialog
|
||||
aria-label="Edit scope"
|
||||
onclose={() => (scopeReviewOpen = false)}
|
||||
oncancel={(event) => {
|
||||
if (updatingScope) event.preventDefault();
|
||||
}}
|
||||
bind:this={scopeDialog}
|
||||
onclick={(e) => onDialogClick(scopeDialog, e)}
|
||||
>
|
||||
<form class="dlg" onsubmit={saveScope}>
|
||||
<header class="dlg-head">
|
||||
<h3>Edit scope</h3>
|
||||
<button
|
||||
type="button"
|
||||
class="close"
|
||||
disabled={updatingScope}
|
||||
/>
|
||||
{#if scopeType === 'game' || scopeType === 'map'}
|
||||
onclick={cancelScopeModal}
|
||||
aria-label="Close">×</button
|
||||
>
|
||||
</header>
|
||||
<Field label="Scope">
|
||||
<div class="scope-row">
|
||||
<Select
|
||||
ariaLabel="Scope game"
|
||||
bind:value={scopeGame}
|
||||
onchange={() => (scopeMap = '')}
|
||||
options={gameOptions}
|
||||
ariaLabel="Scope type"
|
||||
bind:value={scopeType}
|
||||
options={scopeTypeOptions}
|
||||
disabled={updatingScope}
|
||||
/>
|
||||
{/if}
|
||||
{#if scopeType === 'map'}
|
||||
<Select
|
||||
ariaLabel="Scope map"
|
||||
bind:value={scopeMap}
|
||||
options={mapOptions}
|
||||
disabled={updatingScope || !scopeGame}
|
||||
/>
|
||||
{/if}
|
||||
{#if scopeType === 'game' || scopeType === 'map'}
|
||||
<Select
|
||||
ariaLabel="Scope game"
|
||||
bind:value={scopeGame}
|
||||
onchange={() => (scopeMap = '')}
|
||||
options={gameOptions}
|
||||
disabled={updatingScope}
|
||||
/>
|
||||
{/if}
|
||||
{#if scopeType === 'map'}
|
||||
<Select
|
||||
ariaLabel="Scope map"
|
||||
bind:value={scopeMap}
|
||||
options={mapOptions}
|
||||
disabled={updatingScope || !scopeGame}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</Field>
|
||||
{#if scopePreviewLoading}<p role="status">
|
||||
Reviewing eligible servers…
|
||||
</p>{:else if scopePreviewError}<p role="alert">
|
||||
{scopePreviewError}
|
||||
</p>{:else if scopeImpact}<section
|
||||
class="scope-impact"
|
||||
aria-label="Scope change review"
|
||||
>
|
||||
<strong
|
||||
>{scopeImpact.after.length} eligible · {scopeImpact.added.length} added
|
||||
·
|
||||
{scopeImpact.removed.length} removed</strong
|
||||
>{#if scopeImpact.added.length}<p>
|
||||
Added: {scopeImpact.added.join(', ')}
|
||||
</p>{/if}{#if scopeImpact.removed.length}<p>
|
||||
Removed: {scopeImpact.removed.join(', ')}
|
||||
</p>{/if}
|
||||
<p>
|
||||
{scopeImpact.composed
|
||||
? 'This changes intended configuration composition.'
|
||||
: scopeType === 'none'
|
||||
? `${scopeImpact.detached.length} recorded installations will become untracked. Maintenance will detect orphaned files as drift.`
|
||||
: 'Scope describes eligibility for promoted files.'}
|
||||
</p>
|
||||
<p>
|
||||
When automation is enabled, Maintenance rebuilds servers with drift
|
||||
after verifying a safe server state. Rebuilds delete the existing
|
||||
instance and all of its files.
|
||||
</p>
|
||||
</section>{/if}
|
||||
<div class="dlg-actions">
|
||||
<Button variant="ghost" onclick={cancelScopeModal} disabled={updatingScope}
|
||||
>Cancel</Button
|
||||
>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="primary"
|
||||
disabled={updatingScope || scopePreviewLoading || !scopeImpact}
|
||||
loading={updatingScope}
|
||||
>
|
||||
Save reviewed scope
|
||||
</Button>
|
||||
</div>
|
||||
</Field>
|
||||
{#if scopePreviewLoading}<p role="status">
|
||||
Reviewing eligible servers…
|
||||
</p>{:else if scopePreviewError}<p role="alert">
|
||||
{scopePreviewError}
|
||||
</p>{:else if scopeImpact}<section
|
||||
class="scope-impact"
|
||||
aria-label="Scope change review"
|
||||
>
|
||||
<strong
|
||||
>{scopeImpact.after.length} eligible · {scopeImpact.added.length} added ·
|
||||
{scopeImpact.removed.length} removed</strong
|
||||
>{#if scopeImpact.added.length}<p>
|
||||
Added: {scopeImpact.added.join(', ')}
|
||||
</p>{/if}{#if scopeImpact.removed.length}<p>
|
||||
Removed: {scopeImpact.removed.join(', ')}
|
||||
</p>{/if}
|
||||
<p>
|
||||
{scopeImpact.composed
|
||||
? 'This changes intended configuration composition.'
|
||||
: scopeType === 'none'
|
||||
? `${scopeImpact.detached.length} recorded installations will become untracked. Maintenance will detect orphaned files as drift.`
|
||||
: 'Scope describes eligibility for promoted files.'}
|
||||
</p>
|
||||
<p>
|
||||
When automation is enabled, Maintenance rebuilds servers with drift
|
||||
after verifying a safe server state. Rebuilds delete the existing
|
||||
instance and all of its files.
|
||||
</p>
|
||||
</section>{/if}
|
||||
<div class="dlg-actions">
|
||||
<Button variant="ghost" onclick={cancelScopeModal} disabled={updatingScope}
|
||||
>Cancel</Button
|
||||
>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="primary"
|
||||
disabled={updatingScope || scopePreviewLoading || !scopeImpact}
|
||||
loading={updatingScope}
|
||||
>
|
||||
Save reviewed scope
|
||||
</Button>
|
||||
</div>
|
||||
{#if flash}<div class="alert alert-danger" role="alert">{flash}</div>{/if}
|
||||
</form>
|
||||
</dialog>
|
||||
{#if flash}<div class="alert alert-danger" role="alert">{flash}</div>{/if}
|
||||
</form>
|
||||
</dialog>
|
||||
|
||||
<!-- Upload dialog -->
|
||||
<dialog
|
||||
aria-label="Upload new version"
|
||||
oncancel={(event) => {
|
||||
if (uploadingVersion) event.preventDefault();
|
||||
}}
|
||||
bind:this={uploadDialog}
|
||||
onclick={(e) => onDialogClick(uploadDialog, e)}
|
||||
>
|
||||
<form class="dlg" onsubmit={uploadVersion}>
|
||||
<header class="dlg-head">
|
||||
<h3>Upload new version</h3>
|
||||
<button
|
||||
type="button"
|
||||
class="close"
|
||||
disabled={uploadingVersion}
|
||||
onclick={cancelUploadModal}
|
||||
aria-label="Close">×</button
|
||||
>
|
||||
</header>
|
||||
<Field label="File">
|
||||
<Input
|
||||
type="file"
|
||||
accept={family === 'plugin' ? '.dll' : undefined}
|
||||
onchange={pickVersionFile}
|
||||
disabled={uploadingVersion}
|
||||
/>
|
||||
</Field>
|
||||
{#if family === 'plugin'}
|
||||
<Field
|
||||
label="Release version"
|
||||
hint="Optional. Use the publisher’s version or release tag, such as v3.1.0."
|
||||
>
|
||||
<!-- Upload dialog -->
|
||||
<dialog
|
||||
aria-label="Upload new version"
|
||||
oncancel={(event) => {
|
||||
if (uploadingVersion) event.preventDefault();
|
||||
}}
|
||||
bind:this={uploadDialog}
|
||||
onclick={(e) => onDialogClick(uploadDialog, e)}
|
||||
>
|
||||
<form class="dlg" onsubmit={uploadVersion}>
|
||||
<header class="dlg-head">
|
||||
<h3>Upload new version</h3>
|
||||
<button
|
||||
type="button"
|
||||
class="close"
|
||||
disabled={uploadingVersion}
|
||||
onclick={cancelUploadModal}
|
||||
aria-label="Close">×</button
|
||||
>
|
||||
</header>
|
||||
<Field label="File">
|
||||
<Input
|
||||
bind:value={releaseVersion}
|
||||
placeholder="v3.1.0"
|
||||
type="file"
|
||||
accept={family === 'plugin' ? '.dll' : undefined}
|
||||
onchange={pickVersionFile}
|
||||
disabled={uploadingVersion}
|
||||
/>
|
||||
</Field>
|
||||
{/if}
|
||||
<Field label="Notes" hint="Optional. What changed since the previous version.">
|
||||
<Input
|
||||
bind:value={versionNotes}
|
||||
placeholder="what changed"
|
||||
disabled={uploadingVersion}
|
||||
/>
|
||||
</Field>
|
||||
<div class="dlg-actions">
|
||||
<Button variant="ghost" onclick={cancelUploadModal} disabled={uploadingVersion}
|
||||
>Cancel</Button
|
||||
>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="primary"
|
||||
disabled={uploadingVersion || !versionFile}
|
||||
loading={uploadingVersion}
|
||||
>
|
||||
Upload version
|
||||
</Button>
|
||||
</div>
|
||||
{#if flash}<div class="alert alert-danger" role="alert">{flash}</div>{/if}
|
||||
</form>
|
||||
</dialog>
|
||||
{#if family === 'plugin'}
|
||||
<Field
|
||||
label="Release version"
|
||||
hint="Optional. Use the publisher’s version or release tag, such as v3.1.0."
|
||||
>
|
||||
<Input
|
||||
bind:value={releaseVersion}
|
||||
placeholder="v3.1.0"
|
||||
disabled={uploadingVersion}
|
||||
/>
|
||||
</Field>
|
||||
{/if}
|
||||
<Field label="Notes" hint="Optional. What changed since the previous version.">
|
||||
<Input
|
||||
bind:value={versionNotes}
|
||||
placeholder="what changed"
|
||||
disabled={uploadingVersion}
|
||||
/>
|
||||
</Field>
|
||||
<div class="dlg-actions">
|
||||
<Button
|
||||
variant="ghost"
|
||||
onclick={cancelUploadModal}
|
||||
disabled={uploadingVersion}>Cancel</Button
|
||||
>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="primary"
|
||||
disabled={uploadingVersion || !versionFile}
|
||||
loading={uploadingVersion}
|
||||
>
|
||||
Upload version
|
||||
</Button>
|
||||
</div>
|
||||
{#if flash}<div class="alert alert-danger" role="alert">{flash}</div>{/if}
|
||||
</form>
|
||||
</dialog>{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user