Qualify firewall layouts and keep device names compatible with JS target

This commit is contained in:
archipelago
2026-10-08 07:06:30 -04:00
parent ccfa91aa57
commit 8a70232f18
3 changed files with 28 additions and 8 deletions
+26 -6
View File
@@ -27,9 +27,29 @@ local JSON. They are not exposed as controls that claim to open or close real po
The existing OpenWrt management screen remains linked. Actual node firewall rule The existing OpenWrt management screen remains linked. Actual node firewall rule
inspection/editing and the complete app exposure workflow are still separate work. inspection/editing and the complete app exposure workflow are still separate work.
Validation checkpoint: 21 focused UI tests passed before the final background Validation: current focused tests pass 25/25 (22 component cases plus three
resolver and pending-operation display additions. Those additions, the complete background resolver cases), and the full app typecheck passes. Typecheck exposed
app typecheck, responsive rendered-route/background checks and production build an unsupported replaceAll call and a test-wrapper assertion; both were corrected
remain queued behind IndeeHub recovery qualification. No live acceptance is claimed. and the changed device test file was rerun successfully (16/16).
The backend peer-safety changes are isolated separately and also require tests and
paired helper deployment before these new mutation controls can be enabled. Source browser fixtures pass at 320, 390, 768 and 1440 pixels: full content width,
right-aligned values, no horizontal overflow, QR containment and unavailable
mutation controls against the old-backend fixture. A routed source fixture using
the same background resolver preserved the rendered Network background when
entering Firewalls & tunnels. This is not a full production Dashboard acceptance
or a live-node test. All RPC replies were synthetic; no node mutation occurred.
Local receipts:
- `/tmp/archy-firewall-focused-current-20261008.log` — three background tests.
- `/tmp/archy-firewall-focused-components-20261008.log` — 22 component tests.
- `/tmp/archy-firewall-device-final-20261008.log` — corrected device tests.
- `/tmp/archy-firewall-typecheck-20261008.log` — successful exit 0, no diagnostics.
- `/tmp/archy-firewall-responsive-20261008.log` — all four rendered viewport cases.
- `/tmp/archy-firewall-fixture-server.mjs` and
`/tmp/archy-firewall-responsive.cjs` — the source fixture harnesses.
Production build and live acceptance remain pending. The backend peer-safety
changes are isolated separately; actual ephemeral-kernel helper qualification
passed, but Rust compilation/tests and paired helper deployment remain required
before the new mutation controls can be enabled on a node. Task 18 remains open
for broader host firewall management, persistence and rollback.
@@ -62,7 +62,7 @@ describe('Private device management', () => {
const w = view(); await flushPromises(); await w.get('[aria-label="Remove Phone"]').trigger('click') const w = view(); await flushPromises(); await w.get('[aria-label="Remove Phone"]').trigger('click')
expect(vi.mocked(rpcClient.call).mock.calls.filter(([a]) => a.method === 'vpn.remove-peer')).toHaveLength(0) expect(vi.mocked(rpcClient.call).mock.calls.filter(([a]) => a.method === 'vpn.remove-peer')).toHaveLength(0)
await button(w, 'Remove device').trigger('click'); await flushPromises() await button(w, 'Remove device').trigger('click'); await flushPromises()
expect(w.text()).toContain('Could not confirm removal'); expect(w.get('[aria-label="Remove Phone"]').exists()).toBe(true); w.unmount() expect(w.text()).toContain('Could not confirm removal'); expect(w.find('[aria-label="Remove Phone"]').exists()).toBe(true); w.unmount()
}) })
it('keeps a failed list distinct from no devices and disables creation', async () => { it('keeps a failed list distinct from no devices and disables creation', async () => {
vi.mocked(rpcClient.call).mockRejectedValue(new Error('offline')) vi.mocked(rpcClient.call).mockRejectedValue(new Error('offline'))
@@ -18,7 +18,7 @@ const removeName = ref('')
const copied = ref(false) const copied = ref(false)
const needsRefresh = ref(false) const needsRefresh = ref(false)
const safeName = (value: string) => /^[A-Za-z0-9][A-Za-z0-9 _-]{0,63}$/.test(value) const safeName = (value: string) => /^[A-Za-z0-9][A-Za-z0-9 _-]{0,63}$/.test(value)
const filename = (value: string) => value.toLowerCase().replaceAll(' ', '-') const filename = (value: string) => value.toLowerCase().replace(/ /g, '-')
const canCreate = computed(() => props.statusKnown && props.configured && props.managementVerified && devices.value !== null && !needsRefresh.value && safeName(name.value.trim()) && !devices.value.some(d => filename(d.name) === filename(name.value.trim()))) const canCreate = computed(() => props.statusKnown && props.configured && props.managementVerified && devices.value !== null && !needsRefresh.value && safeName(name.value.trim()) && !devices.value.some(d => filename(d.name) === filename(name.value.trim())))
let active = true let active = true
let request = 0 let request = 0