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 <noreply@anthropic.com>
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

+4
View File
@@ -321,3 +321,7 @@ one pill per group.
padding-left: var(--s-8);
margin-left: calc(-1 * var(--s-8));
}
/* The status dot beside a domain, in the row's own colour (`--ql-tone`), as the canvas draws it. */
.dotTone { display:inline-flex; margin-right: var(--s-5); vertical-align: middle; }
.dot { display:inline-block; width:8px; height:8px; border-radius:50%; background: var(--ql-tone, var(--mute)); }
+10 -3
View File
@@ -1,3 +1,4 @@
import { Panel } from '../../ui/Panel'
import type { CSSProperties, ReactNode } from 'react'
import { useCallback, useEffect, useRef, useState } from 'react'
import { flushSync } from 'react-dom'
@@ -604,6 +605,7 @@ export function QueryLogs({ tabs, token, node = '', request }: QueryLogsProps) {
<div className={styles.wrap}>
<SectionHeader
title="Logs"
subtitle="Server log files and the query log of the installed logging app"
tabs={tabs}
labels={f.appName !== '' ? <Tag>app: {f.appName}</Tag> : undefined}
actions={<>
@@ -912,7 +914,7 @@ export function QueryLogs({ tabs, token, node = '', request }: QueryLogsProps) {
</div>
{page != null && (
<>
<Panel title="Query Logs" description="Logging app, dates, client, domain and response type">
<div className={styles.count}>
<span>{statusText(page)}</span>
{/* logs.js:589. "Last" is asked for with -1; the server resolves it. */}
@@ -979,7 +981,12 @@ export function QueryLogs({ tabs, token, node = '', request }: QueryLogsProps) {
id: 'c6',
header: 'Domain',
td: () => ({ className: `${styles.mono} ${tbl.lead}` }),
cell: (e) => (<Clip text={e.qname === '' ? '.' : (e.qname ?? '')} className={styles.clipDomain} />),
cell: (e) => (
<>
<span className={styles.dotTone} aria-hidden="true"><i className={styles.dot} /></span>
<Clip text={e.qname === '' ? '.' : (e.qname ?? '')} className={styles.clipDomain} />
</>
),
},
{
id: 'c7',
@@ -1045,7 +1052,7 @@ export function QueryLogs({ tabs, token, node = '', request }: QueryLogsProps) {
<div className={styles.count}>
<span>{statusText(page)}</span>
</div>
</>
</Panel>
)}
</div>
)
+2 -1
View File
@@ -235,6 +235,7 @@ export function ViewLogs({
<div className={styles.viewLogs} data-fills-window="">
<SectionHeader
title="Logs"
subtitle="Server log files and the query log of the installed logging app"
tabs={tabs}
actions={<>
{/*
@@ -275,7 +276,7 @@ export function ViewLogs({
<Notifier notice={notice} onClose={() => setNotice(null)} />
<div className={styles.split}>
<Panel title="Log Files">
<Panel title="Log Files" description="Server log files by date">
<Body className={styles.pbList}>
{files.length === 0 ? (
<Empty>