diff --git a/docs/firewall-tunnel-followup-20261008.md b/docs/firewall-tunnel-followup-20261008.md index 1a40c23e..15488b90 100644 --- a/docs/firewall-tunnel-followup-20261008.md +++ b/docs/firewall-tunnel-followup-20261008.md @@ -11,8 +11,11 @@ actual device tunnels, mesh connections, router settings and merely saved port entries. A running mesh no longer produces a false Protected firewall label. The new device section uses existing WireGuard APIs for add, reveal/copy and remove. -Mutation controls require the new backend's explicit peer_management_verified flag; -older or unavailable backends cannot enable them. Private configuration is fetched +All saved-device reads and changes require known status and the new backend's +explicit peer_management_verified flag. Older or unavailable backends receive no +list/config RPCs: legacy list replies contain private configurations, and legacy +reveal can also write an endpoint. Capability loss clears device/private state and +rejects late replies; batched prop changes cannot briefly enable a private read. Private configuration is fetched only by an explicit reveal action, QR SVG is sanitized, and cached navigation clears private details and rejects late replies. Failed creation/removal requires a fresh list before retrying. Pending/revoking operations get recovery guidance. @@ -27,8 +30,8 @@ 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 inspection/editing and the complete app exposure workflow are still separate work. -Validation: current focused tests pass 25/25 (22 component cases plus three -background resolver cases), and the full app typecheck passes. Typecheck exposed +Prior checkpoint at b2fee5c5: focused tests passed 25/25 (22 component cases plus three +background resolver cases), and the full app typecheck passed. Typecheck exposed an unsupported replaceAll call and a test-wrapper assertion; both were corrected and the changed device test file was rerun successfully (16/16). @@ -53,3 +56,34 @@ 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. + +## Header/back and legacy privacy follow-up + +The header now matches the OpenWrt Gateway sibling: shared BackButton with +`desktop-margin="mb-0"`, an inline `items-center gap-3 mb-6` row and `text-lg` +semibold title. The shared component provides the standard floating mobile back +control; navigation always returns to the named Network/server route. + +Current affected checks passed: seven firewall/header cases and seventeen device +cases. The initial capability regression found that a synchronous watcher could +briefly read mixed old/new props while status became unknown; the batched watcher +and response/render guards fixed this, and the full device file passed on rerun. +The unchanged three background cases retain their earlier pass. + +The updated browser fixture passed at all four widths (320/390/768/1440), checking +header font/spacing, desktop alignment, floating mobile BackButton, return to +Network, unchanged background, full width, status alignment, QR containment and +zero legacy private RPCs while unverified. These are source-component fixtures +with synthetic RPCs, not live acceptance or a full production Dashboard test. +The app-wide typecheck has not been repeated after this final focused follow-up; +it and production build remain part of integration qualification. + +Final follow-up receipts: +- `/tmp/archy-firewall-header-privacy-tests-20261008.log` — seven header/firewall + passes and the initial privacy transition failure (retained). +- `/tmp/archy-firewall-privacy-batched-tests-20261008.log` — all 17 device cases + passed after the transition correction. +- `/tmp/archy-firewall-header-privacy-browser-20261008.log` — four viewport passes. + +All fixture browser/server processes stopped after verification. No node, helper, +firewall, wallet or saved-device configuration was changed by these UI checks. diff --git a/docs/post-1.9.0-work-backlog.md b/docs/post-1.9.0-work-backlog.md index 4bfc056f..4985b3e0 100644 --- a/docs/post-1.9.0-work-backlog.md +++ b/docs/post-1.9.0-work-backlog.md @@ -522,6 +522,8 @@ Operator refinements on 8 October, retained as acceptance requirements: present the mesh service as proof that the node firewall is protected. - Fill the main content width and inherit the Network tab background; opening Firewalls & tunnels must not switch to a different page background. +- Match the established header and shared back button, including desktop title + alignment and mobile back placement. Use the OpenWrt Gateway sibling pattern. - Use the standard black primary button at the bottom of the Local Network container, matching other container actions. - Use plain language throughout and provide real, supported configuration diff --git a/neode-ui/src/views/server/FirewallTunnelSettings.test.ts b/neode-ui/src/views/server/FirewallTunnelSettings.test.ts index d964a5b9..13177604 100644 --- a/neode-ui/src/views/server/FirewallTunnelSettings.test.ts +++ b/neode-ui/src/views/server/FirewallTunnelSettings.test.ts @@ -1,9 +1,12 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import { flushPromises, mount } from '@vue/test-utils' +const { navigate } = vi.hoisted(() => ({ navigate: vi.fn() })) +vi.mock('vue-router', async importOriginal => ({ ...await importOriginal(), useRouter: () => ({ push: navigate }) })) vi.mock('@/api/rpc-client', () => ({ rpcClient: { call: vi.fn(), vpnStatus: vi.fn() } })) import { rpcClient } from '@/api/rpc-client' +import BackButton from '@/components/BackButton.vue' import FirewallTunnelSettings from './FirewallTunnelSettings.vue' -const mountPage = () => mount(FirewallTunnelSettings, { global: { stubs: { RouterLink: { template: '' }, VpnDeviceSettings: { props: ['configured', 'managementVerified'], template: '
' } } } }) +const mountPage = () => mount(FirewallTunnelSettings, { global: { stubs: { BackButton: { props: ['desktopMargin'], template: '' }, RouterLink: { template: '' }, VpnDeviceSettings: { props: ['configured', 'managementVerified'], template: '
' } } } }) const vpn = { connected: true, provider: 'tailscale', configured: true, configured_provider: 'nostrvpn', wg_ip: '10.44.0.1' } beforeEach(() => { vi.resetAllMocks() @@ -16,6 +19,15 @@ beforeEach(() => { }) }) describe('Firewall and tunnel settings', () => { + it('uses the standard OpenWrt sibling header and shared back control to Network', async () => { + const w = mountPage(); await flushPromises() + expect(w.get('[data-testid="firewall-header"]').classes()).toEqual(expect.arrayContaining(['flex', 'items-center', 'gap-3', 'mb-6'])) + expect(w.get('h1').classes()).toEqual(expect.arrayContaining(['text-lg', 'font-semibold'])) + expect(w.findComponent(BackButton).props('desktopMargin')).toBe('mb-0') + await w.get('[data-testid="back"]').trigger('click') + expect(navigate).toHaveBeenCalledWith({ name: 'server' }) + w.unmount() + }) it('recognizes the existing device tunnel independently of router setup and never infers firewall protection from FIPS', async () => { const w = mountPage(); await flushPromises() expect(w.text()).toContain('Your device tunnel is already set up') @@ -35,7 +47,7 @@ describe('Firewall and tunnel settings', () => { const w = mountPage(); await flushPromises() vi.mocked(rpcClient.vpnStatus).mockRejectedValue(new Error('offline')) vi.mocked(rpcClient.call).mockRejectedValue(new Error('offline')) - await w.get('button').trigger('click'); await flushPromises() + await w.findAll('button').find(b => b.text() === 'Refresh status')!.trigger('click'); await flushPromises() expect(w.text()).not.toContain('Your device tunnel is already set up') expect(w.text()).toContain('Could not check saved entries.') expect(w.text()).not.toContain('No entries are saved here.') diff --git a/neode-ui/src/views/server/FirewallTunnelSettings.vue b/neode-ui/src/views/server/FirewallTunnelSettings.vue index e450b984..527d866b 100644 --- a/neode-ui/src/views/server/FirewallTunnelSettings.vue +++ b/neode-ui/src/views/server/FirewallTunnelSettings.vue @@ -1,12 +1,14 @@