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

+181
View File
@@ -0,0 +1,181 @@
version: "3.8"
services:
traefik:
image: traefik:v3
container_name: traefik
restart: unless-stopped
command:
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--providers.docker.network=dns-net"
- "--providers.file.directory=/etc/traefik/dynamic"
- "--providers.file.watch=true"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--entrypoints.websecure.http3"
- "--entrypoints.dot.address=:853"
- "--entrypoints.doq.address=:853/udp"
- "--entrypoints.cluster.address=:53443"
- "--certificatesresolvers.myresolver.acme.dnschallenge=true"
- "--certificatesresolvers.myresolver.acme.dnschallenge.provider=cloudflare"
- "--certificatesresolvers.myresolver.acme.dnschallenge.resolvers=1.1.1.1:53,8.8.8.8:53"
- "--certificatesresolvers.myresolver.acme.dnschallenge.delaybeforecheck=60s"
- "[email protected]"
- "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"
environment:
- CF_DNS_API_TOKEN=${CF_DNS_API_TOKEN}
ports:
- target: 80
published: 80
protocol: tcp
mode: host
- target: 443
published: 443
protocol: tcp
mode: host
- target: 443
published: 443
protocol: udp
mode: host
- target: 853
published: 853
protocol: tcp
mode: host
- target: 853
published: 853
protocol: udp
mode: host
- target: 53443
published: 53443
protocol: tcp
mode: host
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./letsencrypt:/letsencrypt
- ./traefik/dynamic:/etc/traefik/dynamic:ro
networks:
- dns-net
technitium-smart:
image: technitium/dns-server:latest
container_name: technitium-smart
restart: unless-stopped
networks:
- dns-net
cap_add:
- NET_ADMIN
- NET_RAW
- NET_BIND_SERVICE
environment:
- DNS_SERVER_ADMIN_PASSWORD=${DNS_SERVER_ADMIN_PASSWORD}
- DNS_SERVER_ENABLE_DNS_OVER_HTTP=true
- DNS_SERVER_ENABLE_DNS_OVER_TLS=false
- DNS_SERVER_WEB_SERVICE_ENABLE_HTTPS=true
- DNS_SERVER_WEB_SERVICE_HTTPS_PORT=53443
- DNS_SERVER_WEB_SERVICE_USE_SELF_SIGNED_CERT=true
# Новая консоль: папку со статикой отдаёт том technitium-console (нужен Technitium 15.5+)
- DNS_SERVER_WEB_SERVICE_WWW_FOLDER_PATH=/opt/technitium-console
- TZ=Asia/Novosibirsk
- ASPNETCORE_FORWARDEDHEADERS_ENABLED=true
- DOTNET_SYSTEM_NET_SOCKETS_INLINE_RECEIVE=1
volumes:
- technitium-data:/etc/dns
- technitium-console:/opt/technitium-console:ro
# AXFR зоны кластера (TLSA). Если 53 уже слушает не этот контейнер — удалите блок.
# Файрволом пускайте TCP/53 только с IP соседних нод.
# ports:
# - target: 53
# published: 53
# protocol: tcp
# mode: host
# - target: 53
# published: 53
# protocol: udp
# mode: host
labels:
- "traefik.enable=true"
- "traefik.docker.network=dns-net"
- "traefik.http.middlewares.old-panel-headers.headers.customrequestheaders.X-Forwarded-Proto=https"
- "traefik.http.middlewares.old-panel-headers.headers.customrequestheaders.X-Forwarded-Host=old.dns.shnt.top"
# DoH
- "traefik.http.routers.doh.rule=(Host(`dns.shnt.top`) || HostRegexp(`^[a-z0-9-]+\\.dns\\.shnt\\.top$`) || Host(`ndns.shnt.top`) || HostRegexp(`^[a-z0-9-]+\\.ndns\\.shnt\\.top$`)) && PathPrefix(`/dns-query`)"
- "traefik.http.routers.doh.entrypoints=websecure"
- "traefik.http.routers.doh.tls.certresolver=myresolver"
- "traefik.http.routers.doh.tls.domains[0].main=dns.shnt.top"
- "traefik.http.routers.doh.tls.domains[0].sans=*.dns.shnt.top"
- "traefik.http.routers.doh.tls.domains[1].main=ndns.shnt.top"
- "traefik.http.routers.doh.tls.domains[1].sans=*.ndns.shnt.top"
- "traefik.http.routers.doh.priority=1000"
- "traefik.http.routers.doh.service=doh-svc"
- "traefik.http.services.doh-svc.loadbalancer.server.port=8080"
- "traefik.http.services.doh-svc.loadbalancer.server.scheme=http"
# Панель
- "traefik.http.routers.panel-old.rule=Host(`dns.shnt.top`) || Host(`old.dns.shnt.top`) || Host(`ndns.shnt.top`) || HostRegexp(`^[a-z0-9-]+\\.ndns\\.shnt\\.top$`)"
- "traefik.http.routers.panel-old.entrypoints=websecure"
- "traefik.http.routers.panel-old.tls.certresolver=myresolver"
- "traefik.http.routers.panel-old.tls.domains[0].main=dns.shnt.top"
- "traefik.http.routers.panel-old.tls.domains[0].sans=*.dns.shnt.top"
- "traefik.http.routers.panel-old.tls.domains[1].main=ndns.shnt.top"
- "traefik.http.routers.panel-old.tls.domains[1].sans=*.ndns.shnt.top"
- "traefik.http.routers.panel-old.priority=10"
- "traefik.http.routers.panel-old.middlewares=old-panel-headers"
- "traefik.http.routers.panel-old.service=panel-old-svc"
- "traefik.http.services.panel-old-svc.loadbalancer.server.port=5380"
# DoT
- "traefik.tcp.routers.dot.rule=HostSNI(`dns.shnt.top`) || HostSNIRegexp(`^.+\\.dns\\.shnt\\.top$`) || HostSNI(`ndns.shnt.top`) || HostSNIRegexp(`^.+\\.ndns\\.shnt\\.top$`)"
- "traefik.tcp.routers.dot.entrypoints=dot"
- "traefik.tcp.routers.dot.tls=true"
- "traefik.tcp.routers.dot.tls.certresolver=myresolver"
- "traefik.tcp.routers.dot.tls.domains[0].main=dns.shnt.top"
- "traefik.tcp.routers.dot.tls.domains[0].sans=*.dns.shnt.top"
- "traefik.tcp.routers.dot.tls.domains[1].main=ndns.shnt.top"
- "traefik.tcp.routers.dot.tls.domains[1].sans=*.ndns.shnt.top"
- "traefik.tcp.routers.dot.tls.options=dot@file"
- "traefik.tcp.routers.dot.service=dot-svc"
- "traefik.tcp.services.dot-svc.loadbalancer.server.port=538"
- "traefik.tcp.services.dot-svc.loadbalancer.proxyprotocol.version=2"
# DoQ
- "traefik.udp.routers.doq.entrypoints=doq"
- "traefik.udp.routers.doq.service=doq-svc"
- "traefik.udp.services.doq-svc.loadbalancer.server.port=8530"
# Кластер: TLS passthrough на Technitium :53443 (без certresolver и Proxy Protocol)
- "traefik.tcp.routers.cluster.rule=HostSNI(`ndns.shnt.top`) || HostSNIRegexp(`^.+\\.ndns\\.shnt\\.top$`) || HostSNI(`dns.shnt.top`) || HostSNIRegexp(`^.+\\.dns\\.shnt\\.top$`)"
- "traefik.tcp.routers.cluster.entrypoints=cluster"
- "traefik.tcp.routers.cluster.tls.passthrough=true"
- "traefik.tcp.routers.cluster.service=cluster-svc"
- "traefik.tcp.services.cluster-svc.loadbalancer.server.port=53443"
# Копирует новую консоль в том technitium-console и завершается.
# Без labels Traefik: наружу этот контейнер ничего не отдаёт.
# Обновление: docker compose pull technitium-console && docker compose up -d technitium-console
technitium-console:
image: git.shx.one/denozord/technitium-console:latest
container_name: technitium-console
restart: "no"
volumes:
- technitium-console:/target
networks:
- dns-net
networks:
dns-net:
driver: bridge
name: dns-net
volumes:
technitium-data:
technitium-console: