Add Prettier and ESLint tooling (Refs #77)
This commit is contained in:
@@ -11,12 +11,32 @@
|
||||
const groups = $derived(visibleNavigation(role));
|
||||
const attention = $derived.by(() => {
|
||||
if (!$fleet.receivedAt) return null;
|
||||
const statuses = $fleetServers.map(server => ({ server, state: serverHealth(server, $uiNow).state }));
|
||||
const servers = statuses.filter(({ server, state }) => !['online', 'starting', 'provisioning'].includes(state)
|
||||
|| $fleet.operations.some(operation => operation.server === server.name && operation.state === 'attention')).length;
|
||||
const hosts = $fleet.hostAttention ?? new Set(statuses.filter(({ server, state }) => server.host && state === 'host-offline').map(({ server }) => server.host!.id)).size;
|
||||
const maintenance = $fleet.operations.filter(operation => operation.kind === 'maintenance' && operation.state === 'attention').length;
|
||||
return { '/': servers, '/hosts': hosts, '/maintenance': maintenance } as Record<string, number>;
|
||||
const statuses = $fleetServers.map((server) => ({
|
||||
server,
|
||||
state: serverHealth(server, $uiNow).state
|
||||
}));
|
||||
const servers = statuses.filter(
|
||||
({ server, state }) =>
|
||||
!['online', 'starting', 'provisioning'].includes(state) ||
|
||||
$fleet.operations.some(
|
||||
(operation) =>
|
||||
operation.server === server.name && operation.state === 'attention'
|
||||
)
|
||||
).length;
|
||||
const hosts =
|
||||
$fleet.hostAttention ??
|
||||
new Set(
|
||||
statuses
|
||||
.filter(({ server, state }) => server.host && state === 'host-offline')
|
||||
.map(({ server }) => server.host!.id)
|
||||
).size;
|
||||
const maintenance = $fleet.operations.filter(
|
||||
(operation) => operation.kind === 'maintenance' && operation.state === 'attention'
|
||||
).length;
|
||||
return { '/': servers, '/hosts': hosts, '/maintenance': maintenance } as Record<
|
||||
string,
|
||||
number
|
||||
>;
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -24,25 +44,86 @@
|
||||
{#each groups as group (group.id)}
|
||||
<section aria-labelledby={`${id}-${group.id}`}>
|
||||
<h2 id={`${id}-${group.id}`}>{group.label}</h2>
|
||||
<ul>{#each group.items as item (item.href)}
|
||||
<li><a href={item.href} aria-current={navigationItemActive(item, $page.url.pathname) ? 'page' : undefined}
|
||||
aria-label={attention && attention[item.href] > 0 ? `${item.label}, ${attention[item.href]} ${attention[item.href] === 1 ? 'needs' : 'need'} attention` : undefined} onclick={() => onNavigate?.()}>
|
||||
<span>{item.label}</span>
|
||||
{#if attention && attention[item.href] > 0}<span class="attention" aria-hidden="true">{attention[item.href]}</span>{/if}
|
||||
</a></li>
|
||||
{/each}</ul>
|
||||
<ul>
|
||||
{#each group.items as item (item.href)}
|
||||
<li>
|
||||
<a
|
||||
href={item.href}
|
||||
aria-current={navigationItemActive(item, $page.url.pathname)
|
||||
? 'page'
|
||||
: undefined}
|
||||
aria-label={attention && attention[item.href] > 0
|
||||
? `${item.label}, ${attention[item.href]} ${attention[item.href] === 1 ? 'needs' : 'need'} attention`
|
||||
: undefined}
|
||||
onclick={() => onNavigate?.()}
|
||||
>
|
||||
<span>{item.label}</span>
|
||||
{#if attention && attention[item.href] > 0}<span
|
||||
class="attention"
|
||||
aria-hidden="true">{attention[item.href]}</span
|
||||
>{/if}
|
||||
</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</section>
|
||||
{/each}
|
||||
</nav>
|
||||
|
||||
<style>
|
||||
nav { display: grid; gap: var(--space-4); min-width: 0; }
|
||||
h2 { margin: 0 0 var(--space-1); padding: 0 var(--space-3); color: var(--text-tertiary); font-size: var(--text-xs); font-weight: var(--weight-semibold); letter-spacing: var(--tracking-label); text-transform: uppercase; }
|
||||
ul { list-style: none; padding: 0; margin: 0; }
|
||||
a { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); min-height: 32px; padding: 5px var(--space-3); border-radius: var(--radius-sm); color: var(--text-secondary); font-size: var(--text-sm); text-decoration: none; }
|
||||
a:hover { color: var(--text-primary); background: var(--surface-raised); }
|
||||
a[aria-current] { color: var(--text-primary); background: var(--surface-raised); box-shadow: inset 2px 0 var(--accent-primary); font-weight: var(--weight-semibold); }
|
||||
a:focus-visible { outline-offset: -2px; }
|
||||
.attention { color: var(--state-warning); font-variant-numeric: tabular-nums; font-size: var(--text-xs); font-weight: var(--weight-semibold); }
|
||||
@media (max-width: 960px) { a { min-height: 44px; } }
|
||||
nav {
|
||||
display: grid;
|
||||
gap: var(--space-4);
|
||||
min-width: 0;
|
||||
}
|
||||
h2 {
|
||||
margin: 0 0 var(--space-1);
|
||||
padding: 0 var(--space-3);
|
||||
color: var(--text-tertiary);
|
||||
font-size: var(--text-xs);
|
||||
font-weight: var(--weight-semibold);
|
||||
letter-spacing: var(--tracking-label);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-2);
|
||||
min-height: 32px;
|
||||
padding: 5px var(--space-3);
|
||||
border-radius: var(--radius-sm);
|
||||
color: var(--text-secondary);
|
||||
font-size: var(--text-sm);
|
||||
text-decoration: none;
|
||||
}
|
||||
a:hover {
|
||||
color: var(--text-primary);
|
||||
background: var(--surface-raised);
|
||||
}
|
||||
a[aria-current] {
|
||||
color: var(--text-primary);
|
||||
background: var(--surface-raised);
|
||||
box-shadow: inset 2px 0 var(--accent-primary);
|
||||
font-weight: var(--weight-semibold);
|
||||
}
|
||||
a:focus-visible {
|
||||
outline-offset: -2px;
|
||||
}
|
||||
.attention {
|
||||
color: var(--state-warning);
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-size: var(--text-xs);
|
||||
font-weight: var(--weight-semibold);
|
||||
}
|
||||
@media (max-width: 960px) {
|
||||
a {
|
||||
min-height: 44px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user