@@ -7,7 +7,7 @@ The OpenWrt Firewall Relationship Visualiser is a single-page browser applicatio
77The application is intentionally local-first:
88
99- Firewall text is parsed in the browser.
10- - Device mappings are held in browser memory for the current session .
10+ - Firewall text, device mappings, graph preferences, and the selected test path are saved in ` localStorage ` when available .
1111- No backend, database, build step, or package manager is required.
1212- The only runtime dependency is Cytoscape.js, loaded from a CDN for graph rendering.
1313
@@ -43,6 +43,8 @@ The app keeps a small amount of global state inside `public/index.html`:
4343| ` firewallModel ` | Parsed zones, forwardings, and traffic rules |
4444| ` cy ` | Current Cytoscape graph instance |
4545| ` currentLayout ` | Selected graph layout name |
46+ | ` selectedTestPath ` | Source and destination device indexes used by the path tester |
47+ | ` storageAvailable ` | Guard for browsers where ` localStorage ` is blocked or unavailable |
4648
4749## Data Model
4850
@@ -334,6 +336,7 @@ The example is useful for demonstrating the relationship views, but it should no
334336## Security Considerations
335337
336338- Firewall text and device mappings stay in the browser.
339+ - Saved sessions use browser ` localStorage ` ; they are not sent to a server.
337340- Local file loading uses the browser ` FileReader ` API.
338341- User-controlled strings are escaped before being inserted into rendered HTML.
339342- The app is static and can be hosted on any static web server.
@@ -348,76 +351,69 @@ The example is useful for demonstrating the relationship views, but it should no
348351| Simplified parser | Good for common UCI files, not a complete UCI interpreter |
349352| Simplified decision engine | Useful for visual exploration, not a full firewall simulation |
350353| Protocol and port fields are not evaluated | Per-service conclusions may be inaccurate |
351- | No persistence | Device mappings and edited config are lost on page reload |
354+ | Browser-local persistence | Saved state is per browser/profile and is not portable without a future import/export feature |
352355| CDN dependency | Graph rendering depends on external script availability |
353356| No automated tests | Behaviour is currently verified manually |
354357
355358## Roadmap
356359
357360### Phase A: Persistence
358361
359- Add browser persistence with ` localStorage ` .
362+ Status: implemented .
360363
361- Store:
364+ The app saves session state to ` localStorage ` after parsing, device changes, graph layout/filter changes, and path-test selection changes.
365+
366+ Stored fields:
362367
363368- Firewall config text.
364369- Device list.
365370- Selected graph layout.
366371- Selected graph filter.
367- - Last tested source and destination device.
372+ - Last tested source and destination device indexes .
368373
369- Implementation notes :
374+ Relevant functions :
370375
371- - Add ` saveToLocalStorage() ` .
372- - Add ` loadFromLocalStorage() ` .
373- - Save after parsing and after device changes.
374- - Load before the initial render in ` main() ` .
376+ - ` saveToLocalStorage() ` .
377+ - ` loadFromLocalStorage() ` .
378+ - ` readStoredState() ` .
379+ - ` normaliseSavedDevices() ` .
380+ - ` normaliseSavedTestPath() ` .
375381
376382### Phase B: Reactive Updates
377383
378- Reduce explicit button clicks by making the page respond to input changes .
384+ Status: implemented .
379385
380- Implementation notes:
386+ The app now responds to input changes while keeping the explicit buttons as manual controls.
381387
382- - Add a debounced ` input ` handler for the firewall textarea.
383- - Keep the manual "Parse Firewall" button as an explicit recovery path for large configs.
384- - Re-test the selected device path when either selector changes.
385- - Batch expensive graph renders behind the debounce.
388+ Implemented behaviour:
386389
387- Example pattern:
390+ - The firewall textarea has a debounced ` input ` handler.
391+ - The "Parse Firewall" button remains available.
392+ - The selected device path re-tests when either selector changes.
393+ - Parse/render changes are persisted automatically when ` localStorage ` is available.
388394
389- ``` javascript
390- function debounce (fn , delay ) {
391- let timeoutId = null ;
392-
393- return function debounced (... args ) {
394- window .clearTimeout (timeoutId);
395- timeoutId = window .setTimeout (() => fn .apply (this , args), delay);
396- };
397- }
395+ Relevant functions:
398396
399- const autoParse = debounce (() => {
400- parseAndRender ();
401- saveToLocalStorage ();
402- }, 500 );
403- ```
397+ - ` debounce() ` .
398+ - ` autoParse ` .
399+ - ` bindReactiveControls() ` .
400+ - ` handleTestSelectorChange() ` .
401+ - ` renderCurrentTestResult() ` .
404402
405403### Phase C: Project Metadata
406404
407- Add a small footer with repository and version metadata .
405+ Status: implemented .
408406
409- Include :
407+ The page footer shows :
410408
411409- GitHub repository link.
412410- Current year from ` new Date().getFullYear() ` .
413- - Optional static version or build date.
414411
415- Implementation notes :
412+ Relevant functions and elements :
416413
417- - Add footer markup before ` </body> ` .
418- - Add footer styling to the inline stylesheet.
419- - Add ` setCurrentYear() ` and call it from ` main() ` .
420- - Use the real repository URL rather than a placeholder.
414+ - ` .site-footer ` .
415+ - ` #currentYear ` .
416+ - ` setCurrentYear() ` .
421417
422418### Phase D: Rule Engine Accuracy
423419
0 commit comments