Move the console onto ReUI PRO
Install ReUI (MCP, skill, @reui registry, Tailwind v4, shadcn base-nova) and move the shared UI onto it: Button, Tag, Check, Input/Textarea, Segmented, Alert, Panel, Dialog, Tooltip, Menu and Select (Popover), Toast (sonner), the Table frame (DataGrid), the Dashboard KPI strip, the shnt Ops palette, Geist and the type scale. Design only: behaviour, texts and roles are unchanged. Tests that asserted the old DOM were updated; the tests now format numbers in English wherever they run. docs/reui-migration-plan.md records the plan and its decisions. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
f3531c64f9
680 files changed
+139836
No files matched your search
@@ -0,0 +1,37 @@
|
||||
# The console's tarball and its .sha256, built from the checked-out commit.
|
||||
# The release job and the develop image both use this, so a develop image
|
||||
# carries exactly what a release built from the same commit would carry.
|
||||
name: Console tarball
|
||||
description: Checks and builds the console, then packs dist/ as technitium-console.tar.gz with its .sha256.
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '22'
|
||||
cache: npm
|
||||
|
||||
- shell: bash
|
||||
run: npm ci
|
||||
|
||||
# The console goes in front of a DNS server, so a build that fails its own
|
||||
# checks must not exist.
|
||||
- shell: bash
|
||||
run: npm run typecheck
|
||||
- shell: bash
|
||||
run: npm run lint
|
||||
- shell: bash
|
||||
run: npm test
|
||||
|
||||
- shell: bash
|
||||
run: npm run build
|
||||
|
||||
# Flat archive: install.sh unpacks it straight into the web root, so the
|
||||
# files have to be at the root of the tarball and not inside dist/.
|
||||
- name: Pack
|
||||
shell: bash
|
||||
run: |
|
||||
tar -czf technitium-console.tar.gz -C dist .
|
||||
sha256sum technitium-console.tar.gz > technitium-console.tar.gz.sha256
|
||||
echo "Routes emitted: $(find dist -name index.html | wc -l)"
|
||||
@@ -0,0 +1,154 @@
|
||||
# The init image for Docker (README, "Docker"): busybox, the install.sh of the
|
||||
# checked-out commit and a console tarball, checked against its own .sha256
|
||||
# before building and again inside every platform after pushing (contract D4).
|
||||
# Only then do the moving tags (`floating`) point at it, so a failed check
|
||||
# leaves them where they were. For a release, which of them move is decided in
|
||||
# that same step, from the repo's tags as they are then (floating-tags.sh).
|
||||
# Shared by the release and develop workflows, so both publish the same image
|
||||
# the same way.
|
||||
name: Init image
|
||||
description: Builds docker/Dockerfile around a console tarball, pushes it for three platforms, checks the bytes in each, then moves the floating tags.
|
||||
|
||||
inputs:
|
||||
image:
|
||||
description: The image name, without a tag.
|
||||
required: true
|
||||
tarball-dir:
|
||||
description: The folder holding technitium-console.tar.gz and technitium-console.tar.gz.sha256.
|
||||
required: true
|
||||
tags:
|
||||
description: The tags pushed with the build, as docker/metadata-action `tags` lines. Never floating ones.
|
||||
required: true
|
||||
floating:
|
||||
description: Space-separated tag names moved to the build once its bytes are checked. Empty moves none.
|
||||
required: false
|
||||
default: ''
|
||||
release-tag:
|
||||
description: A release's git tag (vX.Y.Z). Its X.Y and latest are added to `floating` when floating-tags.sh says so, decided from origin's tags right before they move.
|
||||
required: false
|
||||
default: ''
|
||||
token:
|
||||
description: The token that pushes to GHCR.
|
||||
required: false
|
||||
default: ${{ github.token }}
|
||||
|
||||
outputs:
|
||||
version:
|
||||
description: The version docker/metadata-action read from the first tag.
|
||||
value: ${{ steps.meta.outputs.version }}
|
||||
digest:
|
||||
description: The digest of the manifest list that was pushed and checked.
|
||||
value: ${{ steps.build.outputs.digest }}
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: The build context, with the tarball checked against its checksum
|
||||
id: context
|
||||
shell: bash
|
||||
env:
|
||||
TARBALLS: ${{ inputs.tarball-dir }}
|
||||
run: |
|
||||
ctx="$RUNNER_TEMP/init-image"
|
||||
rm -rf "$ctx"; mkdir -p "$ctx"
|
||||
cp install.sh docker/Dockerfile "$ctx/"
|
||||
cp "$TARBALLS/technitium-console.tar.gz" "$TARBALLS/technitium-console.tar.gz.sha256" "$ctx/"
|
||||
(cd "$ctx" && sha256sum -c technitium-console.tar.gz.sha256)
|
||||
echo "dir=$ctx" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# latest is never this step's: it moves, with the other floating tags, at the end.
|
||||
- id: meta
|
||||
uses: docker/metadata-action@v6
|
||||
with:
|
||||
images: ${{ inputs.image }}
|
||||
flavor: latest=false
|
||||
tags: ${{ inputs.tags }}
|
||||
labels: |
|
||||
org.opencontainers.image.title=technitium-console
|
||||
org.opencontainers.image.description=Copies the technitium-console web console into a volume shared with Technitium DNS Server, then exits.
|
||||
org.opencontainers.image.licenses=GPL-3.0-or-later
|
||||
|
||||
- uses: docker/setup-buildx-action@v4
|
||||
|
||||
- uses: docker/login-action@v4
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ inputs.token }}
|
||||
|
||||
# No QEMU: the Dockerfile has no RUN, so nothing executes for the foreign
|
||||
# platforms. No attestations: they show up as unknown/unknown entries in
|
||||
# the manifest list, and older engines on NAS boxes trip over them.
|
||||
- id: build
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: ${{ steps.context.outputs.dir }}
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
push: true
|
||||
provenance: false
|
||||
sbom: false
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
||||
# By digest: what was pushed, whatever a tag points at by now. Each
|
||||
# platform is pulled by ITS OWN manifest digest, not the index's: the
|
||||
# runner's containerd store refuses a second platform under the same
|
||||
# index digest ("cannot overwrite digest", run 36910489933).
|
||||
- name: Every platform carries the tarball, byte for byte (D4)
|
||||
shell: bash
|
||||
env:
|
||||
IMAGE: ${{ inputs.image }}
|
||||
DIGEST: ${{ steps.build.outputs.digest }}
|
||||
CONTEXT: ${{ steps.context.outputs.dir }}
|
||||
run: |
|
||||
ref="$IMAGE@$DIGEST"
|
||||
want="$(cut -d' ' -f1 "$CONTEXT/technitium-console.tar.gz.sha256")"
|
||||
index="$(docker buildx imagetools inspect --raw "$ref")"
|
||||
jq -r '.manifests[].platform | "\(.os)/\(.architecture)\(if .variant then "/" + .variant else "" end)"' <<<"$index" \
|
||||
| tee "$RUNNER_TEMP/platforms"
|
||||
if ! grep -qx 'linux/amd64' "$RUNNER_TEMP/platforms" || ! grep -Eqx 'linux/arm64(/v8)?' "$RUNNER_TEMP/platforms" \
|
||||
|| ! grep -qx 'linux/arm/v7' "$RUNNER_TEMP/platforms"; then
|
||||
echo "::error::the manifest list does not hold the three platforms"; exit 1
|
||||
fi
|
||||
for p in linux/amd64 linux/arm64 linux/arm/v7; do
|
||||
d="$(jq -r --arg p "$p" 'first(.manifests[]
|
||||
| select((.platform.os + "/" + .platform.architecture + (if .platform.variant then "/" + .platform.variant else "" end)) as $q
|
||||
| $q == $p or ($p == "linux/arm64" and $q == "linux/arm64/v8"))
|
||||
| .digest) // empty' <<<"$index")"
|
||||
[ -n "$d" ] || { echo "::error::no manifest for $p"; exit 1; }
|
||||
docker pull -q --platform "$p" "$IMAGE@$d" >/dev/null
|
||||
cid="$(docker create --platform "$p" "$IMAGE@$d")"
|
||||
got="$(docker cp "$cid:/usr/share/technitium-console/technitium-console.tar.gz" - | tar -xO | sha256sum | cut -d' ' -f1)"
|
||||
docker rm "$cid" >/dev/null
|
||||
echo "$p $d $got"
|
||||
[ "$got" = "$want" ] || { echo "::error::$p does not carry the tarball"; exit 1; }
|
||||
done
|
||||
|
||||
# A release's are decided here and not by an earlier job: "Re-run failed
|
||||
# jobs" hands an earlier job's outputs back as they were, and a release
|
||||
# tagged since then must keep latest and its X.Y. Anonymous ls-remote: the
|
||||
# repo is public and checkout kept no token; a failure fails the step.
|
||||
- name: The floating tags, now that the bytes are checked
|
||||
if: inputs.floating != '' || inputs.release-tag != ''
|
||||
shell: bash
|
||||
env:
|
||||
IMAGE: ${{ inputs.image }}
|
||||
DIGEST: ${{ steps.build.outputs.digest }}
|
||||
FLOATING: ${{ inputs.floating }}
|
||||
RELEASE_TAG: ${{ inputs.release-tag }}
|
||||
run: |
|
||||
if [ -n "$RELEASE_TAG" ]; then
|
||||
decided="$(git ls-remote --tags --refs origin 'v*' | sh "$GITHUB_ACTION_PATH/floating-tags.sh" "$RELEASE_TAG")"
|
||||
FLOATING="${FLOATING:+$FLOATING }$decided"
|
||||
fi
|
||||
read -r -a names <<< "$FLOATING"
|
||||
if [ "${#names[@]}" -eq 0 ]; then echo "No floating tags to move."; exit 0; fi
|
||||
args=()
|
||||
for t in "${names[@]}"; do args+=(--tag "$IMAGE:$t"); done
|
||||
docker buildx imagetools create "${args[@]}" "$IMAGE@$DIGEST"
|
||||
want="$(docker buildx imagetools inspect --raw "$IMAGE@$DIGEST" | jq -c '[.manifests[].digest]')"
|
||||
for t in "${names[@]}"; do
|
||||
got="$(docker buildx imagetools inspect --raw "$IMAGE:$t" | jq -c '[.manifests[].digest]')"
|
||||
echo "$t $got"
|
||||
[ "$got" = "$want" ] || { echo "::error::$t does not point at the images just checked"; exit 1; }
|
||||
done
|
||||
@@ -0,0 +1,36 @@
|
||||
#!/bin/sh
|
||||
# Which of Docker's floating tags the release TAG moves, printed on one line
|
||||
# (`X.Y latest`, `X.Y`, or nothing). The repo's tags come on stdin, as
|
||||
# `git ls-remote --tags --refs origin 'v*'` prints them or one name a line.
|
||||
#
|
||||
# Floating tags only ever move forward: latest goes to the highest release in
|
||||
# the repo and X.Y to the highest of its own line, so re-running an old tag or
|
||||
# tagging a backport points neither back in time. Prereleases are not releases
|
||||
# here: they move neither. Called right before the tags move, never from an
|
||||
# earlier job's outputs, which "Re-run failed jobs" would hand back unchanged.
|
||||
set -eu
|
||||
|
||||
[ $# -eq 1 ] || { echo "usage: git ls-remote --tags --refs origin 'v*' | $0 vX.Y.Z" >&2; exit 2; }
|
||||
tag="$1"
|
||||
|
||||
releases="$(sed 's|.*refs/tags/||' | { grep -Ex 'v[0-9]+\.[0-9]+\.[0-9]+' || true; } | sort -V)"
|
||||
|
||||
if ! printf '%s\n' "$tag" | grep -Eqx 'v[0-9]+\.[0-9]+\.[0-9]+'; then
|
||||
echo "$tag is not a release (vX.Y.Z): no floating tags" >&2
|
||||
exit 0
|
||||
fi
|
||||
# A release that cannot find itself among the tags has not read them.
|
||||
if ! printf '%s\n' "$releases" | grep -Fqx "$tag"; then
|
||||
echo "::error::$tag is not among the tags read from origin" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
line="$(printf '%s\n' "$tag" | cut -d. -f1-2)."
|
||||
top="$(printf '%s\n' "$releases" | tail -n1)"
|
||||
top_of_line="$(printf '%s\n' "$releases" | awk -v p="$line" 'index($0, p) == 1' | tail -n1)"
|
||||
|
||||
floating=""
|
||||
if [ "$tag" = "$top_of_line" ]; then floating="$(printf '%s\n' "${tag#v}" | cut -d. -f1-2)"; fi
|
||||
if [ "$tag" = "$top" ]; then floating="${floating:+$floating }latest"; fi
|
||||
echo "$tag: highest release $top, highest of its line $top_of_line -> floating tags: ${floating:-none}" >&2
|
||||
printf '%s\n' "$floating"
|
||||
Reference in new issue
Block a user