Match the Technitium canvas: palette, shell, KPI strips, framed tables
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:
1 parent
ea98a33b25
commit
87281a87d3
53 files changed
+1011
-487
No files matched your search
+26
-15
@@ -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>
|
||||
|
||||
Reference in new issue
Block a user