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
+91 -84
View File
@@ -1,5 +1,6 @@
<script lang="ts">
import { page } from '$app/stores';
import { canOperate } from '$lib/auth.js';
import { contextHref } from '$lib/navigation-context.js';
import { onMount } from 'svelte';
import { afterNavigate } from '$app/navigation';
@@ -20,6 +21,7 @@
import Input from '$ui/components/primitives/Input.svelte';
import Select from '$ui/components/primitives/Select.svelte';
import { libraryLimit, saveLibraryContext } from '$ui/library-context.js';
const editable = $derived(canOperate($page.data?.user?.role));
type ArtifactKind = 'script' | 'config' | 'plugin' | 'baseline';
interface ArtifactRow {
@@ -266,11 +268,11 @@
title="Plugins"
subtitle="Manage plugin versions and test changes before promotion."
>
{#snippet actions()}{#if plugins.length}<Button
{#snippet actions()}{#if editable && plugins.length}<Button
variant="ghost"
onclick={() => (updatesOpen = !updatesOpen)}
>{updatesOpen ? 'Hide update details' : 'Check for updates'}</Button
>{/if}{#if loading || plugins.length || flash}<Button
>{/if}{#if editable && (loading || plugins.length || flash)}<Button
variant="primary"
onclick={() => (addOpen = true)}>Add plugin</Button
>{/if}{/snippet}
@@ -339,7 +341,7 @@
{#if game}<p class="filter-hint">
Game results include unscoped plugins that can use explicitly selected beta servers.
</p>{/if}
{#if updatesOpen}<details
{#if editable && updatesOpen}<details
open
ontoggle={(event) => {
if (!event.currentTarget.open) updatesOpen = false;
@@ -377,8 +379,9 @@
game = '';
status = '';
}}>Clear filters</Button
>{:else}<Button variant="primary" onclick={() => (addOpen = true)}
>Add plugin</Button
>{:else if editable}<Button
variant="primary"
onclick={() => (addOpen = true)}>Add plugin</Button
>{/if}{/snippet}
</EmptyState>
{:else}
@@ -449,91 +452,95 @@
</section>
</div>
<Dialog bind:open={addOpen} title="Add plugin" busy={uploading || githubBusy} wide>
<Tabs
tabs={[
{ id: 'upload', label: 'Upload file', disabled: uploading || githubBusy },
{ id: 'github', label: 'GitHub release', disabled: uploading || githubBusy }
]}
bind:value={method}
ariaLabel="Plugin source"
idPrefix="plugin-source"
panelId="plugin-source-panel"
/>
<div id="plugin-source-panel" role="tabpanel" aria-labelledby={'plugin-source-' + method}>
{#if method === 'github'}<PluginGithubImport
bind:busy={githubBusy}
onimport={async (result) => {
importComplete = result;
updates = updates.filter((update) => update.pluginId !== result.pluginId);
importCount++;
addOpen = false;
await load();
}}
/>
{:else}
<form onsubmit={createPlugin} class="upload-form">
<Field label="Name">
<Input bind:value={newName} placeholder="my plugin" disabled={uploading} />
</Field>
<Field
label="Release version"
hint="Optional. Use the publisher’s version or release tag, such as v3.1.0."
>
<Input
bind:value={newReleaseVersion}
placeholder="v3.1.0"
disabled={uploading}
/>
</Field>
<Field label="Notes" hint="Optional. What changed in this version.">
<Input bind:value={newNotes} placeholder="what changed" disabled={uploading} />
</Field>
<Field label="Scope" hint="Where this plugin deploys.">
<div class="scope-row">
<Select
ariaLabel="Scope type"
bind:value={newScopeType}
options={scopeTypeOptions}
{#if editable}<Dialog bind:open={addOpen} title="Add plugin" busy={uploading || githubBusy} wide>
<Tabs
tabs={[
{ id: 'upload', label: 'Upload file', disabled: uploading || githubBusy },
{ id: 'github', label: 'GitHub release', disabled: uploading || githubBusy }
]}
bind:value={method}
ariaLabel="Plugin source"
idPrefix="plugin-source"
panelId="plugin-source-panel"
/>
<div id="plugin-source-panel" role="tabpanel" aria-labelledby={'plugin-source-' + method}>
{#if method === 'github'}<PluginGithubImport
bind:busy={githubBusy}
onimport={async (result) => {
importComplete = result;
updates = updates.filter((update) => update.pluginId !== result.pluginId);
importCount++;
addOpen = false;
await load();
}}
/>
{:else}
<form onsubmit={createPlugin} class="upload-form">
<Field label="Name">
<Input bind:value={newName} placeholder="my plugin" disabled={uploading} />
</Field>
<Field
label="Release version"
hint="Optional. Use the publisher’s version or release tag, such as v3.1.0."
>
<Input
bind:value={newReleaseVersion}
placeholder="v3.1.0"
disabled={uploading}
/>
{#if newScopeType === 'game' || newScopeType === 'map'}
</Field>
<Field label="Notes" hint="Optional. What changed in this version.">
<Input
bind:value={newNotes}
placeholder="what changed"
disabled={uploading}
/>
</Field>
<Field label="Scope" hint="Where this plugin deploys.">
<div class="scope-row">
<Select
ariaLabel="Scope game"
bind:value={newScopeGame}
onchange={() => (newScopeMap = '')}
options={gameOptions}
ariaLabel="Scope type"
bind:value={newScopeType}
options={scopeTypeOptions}
disabled={uploading}
/>
{/if}
{#if newScopeType === 'map'}
<Select
ariaLabel="Scope map"
bind:value={newScopeMap}
options={mapOptions}
disabled={uploading || !newScopeGame}
/>
{/if}
{#if newScopeType === 'game' || newScopeType === 'map'}
<Select
ariaLabel="Scope game"
bind:value={newScopeGame}
onchange={() => (newScopeMap = '')}
options={gameOptions}
disabled={uploading}
/>
{/if}
{#if newScopeType === 'map'}
<Select
ariaLabel="Scope map"
bind:value={newScopeMap}
options={mapOptions}
disabled={uploading || !newScopeGame}
/>
{/if}
</div>
</Field>
<Field label="Plugin file (.dll)">
<Input type="file" accept=".dll" onchange={pickFile} disabled={uploading} />
</Field>
<div class="form-actions ui-sticky-actions">
<Button
type="submit"
variant="primary"
disabled={uploading || !newFile || !newName}
loading={uploading}
>
Upload plugin
</Button>
{#if flash}<span class="flash" role="alert">{flash}</span>{/if}
</div>
</Field>
<Field label="Plugin file (.dll)">
<Input type="file" accept=".dll" onchange={pickFile} disabled={uploading} />
</Field>
<div class="form-actions ui-sticky-actions">
<Button
type="submit"
variant="primary"
disabled={uploading || !newFile || !newName}
loading={uploading}
>
Upload plugin
</Button>
{#if flash}<span class="flash" role="alert">{flash}</span>{/if}
</div>
</form>
{/if}
</div>
</Dialog>
</form>
{/if}
</div>
</Dialog>{/if}
<style>
.page {