checkpoint ISAC registries and system monitoring

This commit is contained in:
JB
2026-07-27 00:31:16 -04:00
parent 8b80e55e6c
commit 7b3aff30d8
53 changed files with 1351 additions and 179 deletions
@@ -0,0 +1,17 @@
<script lang="ts">
import type { Snippet } from 'svelte';
let {
id,
label,
children
}: {
id: string;
label: string;
children: Snippet;
} = $props();
</script>
<section class="registry-inline-editor" {id} aria-label={label}>
{@render children()}
</section>
+1 -1
View File
@@ -87,6 +87,6 @@
{/each}
<a class:disabled={pagination.page === pagination.pageCount} aria-disabled={pagination.page === pagination.pageCount} href={href(Math.min(pagination.pageCount, pagination.page + 1))}>NEXT →</a>
</div>
<div class="pagination-sizes"><span>ROWS</span>{#each [10, 25, 50, 100] as size}<a class:active={size === pagination.perPage} href={href(1, size)}>{size}</a>{/each}</div>
{/if}
<div class="pagination-sizes"><span>ROWS</span>{#each [10, 25, 50, 100] as size}<a class:active={size === pagination.perPage} href={href(1, size)}>{size}</a>{/each}</div>
</nav>
@@ -1,5 +1,5 @@
<script lang="ts">
type Item = { id: string; name: string; pending?: boolean; proposerName?: string };
type Item = { id: string; name: string; unit?: string; pending?: boolean; proposerName?: string };
let {
items,
selected = $bindable<string[]>([]),
@@ -33,10 +33,12 @@
const normalizedQuery = $derived(query.normalize('NFKC').trim().toLocaleLowerCase());
const available = $derived(items.filter((item) =>
!excludedIds.includes(item.id)
&& (!normalizedQuery || item.name.toLocaleLowerCase().includes(normalizedQuery))
&& (!normalizedQuery || `${item.name} ${item.unit ?? ''}`.toLocaleLowerCase().includes(normalizedQuery))
));
const visible = $derived(available.slice(0, 30));
const selectedItems = $derived(selectedIds.map((id) => items.find((item) => item.id === id)).filter((item): item is Item => Boolean(item)));
const unitLabel = (item: Item) => item.unit === undefined ? '' : item.unit || 'FLAT';
const accessibleName = (item: Item) => unitLabel(item) ? `${item.name}, unit ${unitLabel(item)}` : item.name;
const toggle = (id: string) => {
if (mode === 'single') {
value = value === id ? '' : id;
@@ -57,7 +59,7 @@
<div class="assignment-summary"><span>{mode === 'single' ? 'SELECTED' : 'ASSIGNED'}</span><b>{selectedIds.length}</b></div>
<div class="assignment-chips">
{#each selectedItems as item}
<button type="button" class:pending-reference={item.pending} onclick={() => toggle(item.id)} aria-label={`Remove ${item.name}`}><span>{item.name}{#if item.pending}<small>PENDING</small>{/if}</span><b>×</b></button>
<button type="button" class:pending-reference={item.pending} onclick={() => toggle(item.id)} aria-label={`Remove ${accessibleName(item)}`}><span>{item.name}{#if unitLabel(item)}<small class="assignment-item-unit">UNIT · {unitLabel(item)}</small>{/if}{#if item.pending}<small>PENDING</small>{/if}</span><b>×</b></button>
{:else}
<small>{emptySelectionLabel}</small>
{/each}
@@ -68,7 +70,7 @@
<div class="assignment-results" role="list">
{#each visible as item}
<button type="button" class:selected={selectedIds.includes(item.id)} class:pending-reference={item.pending} aria-pressed={selectedIds.includes(item.id)} onclick={() => toggle(item.id)}>
<span>{item.name}{#if item.pending}<small>PENDING · {item.proposerName ?? 'CONTRIBUTOR'}</small>{/if}</span><b>{selectedIds.includes(item.id) ? (mode === 'single' ? 'SELECTED' : 'ASSIGNED') : (mode === 'single' ? 'SELECT →' : 'ADD +')}</b>
<span>{item.name}{#if unitLabel(item)}<small class="assignment-item-unit">UNIT · {unitLabel(item)}</small>{/if}{#if item.pending}<small>PENDING · {item.proposerName ?? 'CONTRIBUTOR'}</small>{/if}</span><b>{selectedIds.includes(item.id) ? (mode === 'single' ? 'SELECTED' : 'ASSIGNED') : (mode === 'single' ? 'SELECT →' : 'ADD +')}</b>
</button>
{:else}
<p>{emptyLabel}</p>
@@ -0,0 +1,48 @@
<script lang="ts">
import { page } from '$app/state';
import type { RegistrySort, RegistrySortDirection } from '$lib/server/pagination';
let {
label,
column,
sort,
namespace = '',
defaultDirection = 'asc',
class: className = ''
}: {
label: string;
column: string;
sort: Pick<RegistrySort, 'key' | 'direction'>;
namespace?: string;
defaultDirection?: RegistrySortDirection;
class?: string;
} = $props();
const active = $derived(sort.key === column);
const nextDirection = $derived<RegistrySortDirection>(
active ? (sort.direction === 'asc' ? 'desc' : 'asc') : defaultDirection
);
const sortKey = $derived(namespace ? `${namespace}Sort` : 'sort');
const directionKey = $derived(namespace ? `${namespace}Direction` : 'direction');
const pageKey = $derived(namespace ? `${namespace}Page` : 'page');
const href = $derived.by(() => {
const params = new URLSearchParams(page.url.searchParams);
params.set(sortKey, column);
params.set(directionKey, nextDirection);
params.delete(pageKey);
const query = params.toString();
return `${page.url.pathname}${query ? `?${query}` : ''}`;
});
</script>
<a
class="sortable-column {className}"
class:active
{href}
role="columnheader"
aria-sort={active ? (sort.direction === 'asc' ? 'ascending' : 'descending') : 'none'}
aria-label={`${label}: ${active ? `sorted ${sort.direction}. Activate to sort ${nextDirection}` : `activate to sort ${nextDirection}`}`}
>
<span class="sort-direction" aria-hidden="true">{active ? (sort.direction === 'asc' ? '↑' : '↓') : '↕'}</span>
<span>{label}</span>
</a>