Match the Technitium canvas: palette, shell, KPI strips, framed tables
CI / check (push) Failing after 37s
CI / image (push) Skipped

The live UI did not follow the canvas because its base layer differed: dark
palette in hex (now the canvas oklch values), 13 px body text, flat Frame panels.

- Tokens: canvas palette, 14 px body, 1.4 line height; Frame without `dense`
  gets the 3 px ring; Panel takes a description line.
- Shell: 56 px brand button with version, 11 px group headings, user card at the
  foot, versions moved into the header status popover, Home / group / section
  crumbs, amber "Attention" status when blocking is off or paused.
- New KpiStrip and PageTabs; Dashboard KPIs in three frames and a Refresh button.
- Zones: KPI strip, tab with count, views, framed table with the canvas columns.
- Apps: Installed / App Store tabs and a grid instead of cards (store inline).
- Blocking: state banner and tab counts; one "Blocking" title.
- Settings: actions in the header, label-and-help rows with the control on the right.
- DHCP, Administration, DNS Client, Logs, Cache, About: subtitles, framed tables,
  KPI strips where the data exists; Login cover with the three-point list.
- docs/compose.shnt.example.yaml: production compose with the console image.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
This commit is contained in:
DenozordecandClaude Sonnet 5.5 committed 2026-10-27 12:47:27 +07:00
1 parent ea98a33b25
commit 87281a87d3
53 files changed
+1011 -487

No files matched your search

+26 -15
View File
@@ -392,7 +392,8 @@ export function Shell({
)
}
const crumbs = [info?.dnsServerDomain, current?.label, currentSub].filter((x): x is string => !!x)
const group = GROUPS.find((g) => current != null && g.ids.includes(current.id))?.name
const crumbs = ['Home', group, current?.label, currentSub].filter((x): x is string => !!x)
return (
<HandoffContext.Provider value={handoff}>
@@ -411,13 +412,16 @@ export function Shell({
<aside className={styles.side} data-open={drawer}>
<div className={styles.sbrand}>
<img className={styles.mark} src={publicUrl('img/logo.png')} alt="" width={22} height={22} />
<span className={styles.lbl}>Technitium</span>
<span className={`${styles.lbl} ${styles.sbtag}`}>console</span>
<img className={styles.mark} src={publicUrl('img/logo.png')} alt="" width={32} height={32} />
<span className={`${styles.lbl} ${styles.bname}`}>
<b>Technitium DNS</b>
<small>console {__CONSOLE_VERSION__}</small>
</span>
<span className={`${styles.lbl} ${styles.updown}`} aria-hidden="true"><Icon name="chevronDown" size={14} /></span>
</div>
<button type="button" className={styles.sbsearch} onClick={() => setPalette(true)} aria-label="Search">
<Icon name="menu" size={14} />
<span className={styles.lbl}>Search</span>
<span className={styles.lbl}>Search and commands</span>
<kbd className={styles.lbl}>{'\u2318K'}</kbd>
</button>
<nav className={styles.slist} role="navigation" aria-label="Sections">
@@ -486,14 +490,13 @@ export function Shell({
domain heads it because the block is about that server, which is the
pairing upstream makes in its panel title.
*/}
<Versions
token={session.token}
serverVersion={info?.version}
domain={info?.dnsServerDomain}
markHidden={markHidden}
canCheckConsole={permissions?.DnsClient?.canView === true}
/>
<Menu label={displayName} text={displayName} anchor="left" asRow>
<Menu
label={displayName}
text={displayName}
anchor="left"
asRow
lead={<span className={styles.av} aria-hidden="true">{displayName.slice(0, 2).toUpperCase()}</span>}
>
{(close) => (
<>
<button type="button" onClick={() => { close(); open('profile') }}>
@@ -571,9 +574,9 @@ export function Shell({
))}
</div>
<Popover>
<PopoverTrigger className={styles.status} aria-label="Server status">
<PopoverTrigger className={styles.status} data-attention={blockingNow != null} aria-label="Server status">
<i className={styles.pdot} data-tone={blockingNow?.tone ?? 'ok'} />
<span className={styles.lbl}>{info?.dnsServerDomain ?? 'Server'}</span>
<span className={styles.lbl}>{blockingNow != null ? 'Attention' : (info?.dnsServerDomain ?? 'Server')}</span>
</PopoverTrigger>
<PopoverContent align="end" className={styles.pop}>
<dl>
@@ -582,6 +585,14 @@ export function Shell({
<dt>Up since</dt><dd>{info?.uptimestamp ? new Date(info.uptimestamp).toLocaleString() : '-'}</dd>
<dt>Blocking</dt><dd>{blockingNow ? blockingNow.label.replace(/^Blocking,?\s*/, '') : 'Enabled'}</dd>
</dl>
<Versions
token={session.token}
serverVersion={info?.version}
domain={info?.dnsServerDomain}
markHidden={markHidden}
canCheckConsole={permissions?.DnsClient?.canView === true}
/>
</PopoverContent>
</Popover>
</header>