@clear-capabilities/agentic-security-scanner 0.147.0 → 0.147.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +127 -0
- package/dist/1122.index.js +79 -2
- package/dist/3180.index.js +73 -1
- package/dist/5051.index.js +77 -6
- package/dist/frontend/index.html +21 -0
- package/dist/frontend/src/app.js +176 -0
- package/dist/frontend/src/components/evidence-inspector.js +141 -0
- package/dist/frontend/src/components/filter-rail.js +119 -0
- package/dist/frontend/src/components/query-bar.js +126 -0
- package/dist/frontend/src/data/flagship-graph.js +1460 -0
- package/dist/frontend/src/export-entry.js +36 -0
- package/dist/frontend/src/lib/api-client.js +92 -0
- package/dist/frontend/src/lib/contrast.js +34 -0
- package/dist/frontend/src/lib/dom.js +24 -0
- package/dist/frontend/src/lib/escape-html.js +16 -0
- package/dist/frontend/src/lib/flow-path.js +40 -0
- package/dist/frontend/src/lib/focus-controls.js +149 -0
- package/dist/frontend/src/lib/protection-visual.js +46 -0
- package/dist/frontend/src/lib/query-language.js +240 -0
- package/dist/frontend/src/lib/row-filters.js +43 -0
- package/dist/frontend/src/lib/state.js +84 -0
- package/dist/frontend/src/main.js +83 -0
- package/dist/frontend/src/shell.js +184 -0
- package/dist/frontend/src/views/architecture-view.js +798 -0
- package/dist/frontend/src/views/inventory-view.js +292 -0
- package/dist/frontend/src/views/privacy-view.js +172 -0
- package/dist/frontend/src/views/trace-view.js +206 -0
- package/dist/frontend/styles/architecture-view.css +93 -0
- package/dist/frontend/styles/filter-rail.css +34 -0
- package/dist/frontend/styles/inspector.css +69 -0
- package/dist/frontend/styles/inventory-view.css +74 -0
- package/dist/frontend/styles/privacy-view.css +86 -0
- package/dist/frontend/styles/query-bar.css +107 -0
- package/dist/frontend/styles/shell.css +155 -0
- package/dist/frontend/styles/tokens.css +128 -0
- package/dist/frontend/styles/trace-view.css +95 -0
- package/package.json +2 -2
- package/src/server/static-assets.js +11 -6
- package/src/shared/frontend-root.js +52 -0
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,133 @@
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
|
|
14
|
+
## 0.147.5 — Fix: `bench:provenance:check`'s cold-memory sample size was unreliable on GitHub Actions
|
|
15
|
+
|
|
16
|
+
`v0.147.4`'s tag was pushed but its hosted release-gate run failed before
|
|
17
|
+
reaching `npm publish` — the `dataflow-watch` fix held (the full test suite
|
|
18
|
+
passed cleanly), but `bench/provenance/runner.mjs`'s `bench:provenance:check`
|
|
19
|
+
failed: "only 8/20 iterations produced a usable sample (need >= 10)" for
|
|
20
|
+
`cold.memOverheadRatio`. This is the SAME failure `v0.147.3`'s release run
|
|
21
|
+
also hit (7/20 that time) — two consecutive, reproducible failures on
|
|
22
|
+
GitHub's runners specifically, while this bench passes cleanly (~20/20)
|
|
23
|
+
every time locally, so this is a real platform gap, not one-off noise.
|
|
24
|
+
|
|
25
|
+
Root cause: `cold.memOverheadRatio` is a per-iteration ratio against the
|
|
26
|
+
without-provenance arm's own peak-RSS delta, and `measure()` skips the
|
|
27
|
+
ratio entirely for an iteration where that delta is `0`
|
|
28
|
+
(`if (without.rssDeltaBytes > 0) { ... }`) — the without-provenance scan is
|
|
29
|
+
only ~40-60ms, short enough that the 10ms-interval peak-RSS poller can
|
|
30
|
+
genuinely observe no net growth for it, especially under a shared runner's
|
|
31
|
+
different GC pacing. `BASELINE.json` itself already records this as
|
|
32
|
+
expected (`memOverheadRatio.n: 19`, one dropped iteration out of 20 even on
|
|
33
|
+
the machine that captured it) — GitHub Actions just drops far more of them
|
|
34
|
+
(a ~35-40% survival rate against RELIABLE_N=10 at N=20 leaves no margin).
|
|
35
|
+
|
|
36
|
+
Raises `N` from 20 to 60 in `bench/provenance/runner.mjs` — the same
|
|
37
|
+
already-established remedy this exact file used once before for a related
|
|
38
|
+
small-sample fragility (doubling 10→20), extended further given the new,
|
|
39
|
+
worse-than-expected survival rate. `RELIABLE_N` (10) and the regression
|
|
40
|
+
thresholds are unchanged; `BASELINE.json`'s reference values were not
|
|
41
|
+
touched (the measured overhead is well within budget — this was purely a
|
|
42
|
+
sample-count reliability failure, not a regression). Verified locally:
|
|
43
|
+
passes with real margin (cold memory p95 11.42x vs a 24.38x limit), at a
|
|
44
|
+
real but bounded cost — this one bench step now takes ~2:23 instead of
|
|
45
|
+
~40-60s, a release-time-only cost. `v0.147.4` was never published to any
|
|
46
|
+
registry (confirmed via `npm view`). Carries the identical fixes from
|
|
47
|
+
0.147.1-0.147.4 forward.
|
|
48
|
+
|
|
49
|
+
## 0.147.4 — Fix: close a real fs.watch startup race in `dataflow watch` (test-only; no product behavior change)
|
|
50
|
+
|
|
51
|
+
`v0.147.3`'s tag was pushed but its hosted release-gate run failed before
|
|
52
|
+
reaching `npm publish` — again `cli/dataflow-watch-2`, but this time raising
|
|
53
|
+
the wait timeout (0.147.3's own fix) did NOT help: it timed out at the
|
|
54
|
+
*full* 90000ms budget with zero rescan output, which the real rescan
|
|
55
|
+
pipeline (~1-2s once triggered) rules out as "merely slow." Root cause: `bin/
|
|
56
|
+
agentic-security.js`'s `cmdDataflowWatch` prints its "watching ... Ctrl-C to
|
|
57
|
+
stop" banner — the exact string these tests poll for as the "watcher is
|
|
58
|
+
live" signal — *before* calling `watchProject()`, which is where
|
|
59
|
+
`fs.watch()` actually arms its (possibly recursive, whole-tree-walking on
|
|
60
|
+
Linux) inotify descriptors. A test file-write landing in that gap produces
|
|
61
|
+
an event inotify never delivers at all — a permanent miss, not a delay —
|
|
62
|
+
which a longer timeout cannot fix by construction.
|
|
63
|
+
|
|
64
|
+
Fixed with a 1000ms settle delay in the test, inserted between seeing the
|
|
65
|
+
startup banner and writing the trigger file, in both `dataflow-watch-1` and
|
|
66
|
+
`dataflow-watch-2` (`test/cli/dataflow-watch.test.js`). No product code
|
|
67
|
+
changed — `cmdDataflowWatch`'s banner-before-watchProject ordering is real,
|
|
68
|
+
pre-existing behavior, unrelated to anything in 0.147.1-0.147.3, and left
|
|
69
|
+
alone here rather than reordered under release time pressure without full
|
|
70
|
+
confidence in every caller depending on today's ordering. `v0.147.3` was
|
|
71
|
+
never published to any registry (confirmed via `npm view`). Carries the
|
|
72
|
+
identical fixes from 0.147.1-0.147.3 forward.
|
|
73
|
+
|
|
74
|
+
## 0.147.3 — CI fix: raise dataflow-watch's live-subprocess wait timeout (no functional change from 0.147.2)
|
|
75
|
+
|
|
76
|
+
Same story as 0.147.2, different test: `v0.147.2`'s hosted release-gate run
|
|
77
|
+
also failed before reaching `npm publish` — `test/cli/dataflow-watch.test.js`'s
|
|
78
|
+
`cli/dataflow-watch-2` polls a real subprocess's live stderr for a
|
|
79
|
+
`DRIFT POLICY VIOLATION` line after triggering a file-watch-driven rescan,
|
|
80
|
+
with a 30000ms budget. That step completes in ~1.4s on a normal machine (a
|
|
81
|
+
~20x margin) but still timed out on this run — this repo's watch mode uses
|
|
82
|
+
Node's native `fs.watch(..., { recursive: true })`
|
|
83
|
+
(`src/posture/watch-mode.js`), documented as slower/less reliable under
|
|
84
|
+
Linux CI runners' inotify handling than on a dev machine. `v0.147.2` was
|
|
85
|
+
never published to any registry (confirmed via `npm view`).
|
|
86
|
+
|
|
87
|
+
Raises the wait budget from 30000ms to a shared `WAIT_TIMEOUT_MS = 90000`
|
|
88
|
+
across both real-subprocess tests in that file (the ones that depend on the
|
|
89
|
+
file-watch path — an unrelated fast-fail test in the same file was left
|
|
90
|
+
untouched). No product code changed; this carries the identical fixes from
|
|
91
|
+
0.147.1 and 0.147.2 forward.
|
|
92
|
+
|
|
93
|
+
## 0.147.2 — CI fix: raise the headless-Chrome render timeout (no functional change from 0.147.1)
|
|
94
|
+
|
|
95
|
+
`v0.147.1`'s tag was pushed but its hosted release-gate run failed before
|
|
96
|
+
ever reaching `npm publish` — `scripts/export-image.mjs`'s `exportPng` test
|
|
97
|
+
hit its own hardcoded 15000ms Chrome render timeout on a slower draw of the
|
|
98
|
+
GitHub Actions runner (the same render took 7983ms on the immediately
|
|
99
|
+
preceding v0.147.0 release run — a ~2x runner-speed swing well within
|
|
100
|
+
normal CI variance, not a regression). `v0.147.1` was never published to
|
|
101
|
+
any registry; this release carries the identical `explore`/`dataflow
|
|
102
|
+
export --format html` fix described below under 0.147.1, plus this one
|
|
103
|
+
timeout change: `RENDER_TIMEOUT_MS` default raised from 15000ms to 30000ms
|
|
104
|
+
in `scripts/export-image.mjs`, giving roughly 4x headroom over the observed
|
|
105
|
+
good-case render time. No other code changed.
|
|
106
|
+
|
|
107
|
+
## 0.147.1 — Fix: `explore` and `dataflow export --format html` were broken for every real npm/npx install
|
|
108
|
+
|
|
109
|
+
Both commands located the Data Flow Explorer frontend (`frontend/`, a
|
|
110
|
+
repo-root sibling of `scanner/` in this monorepo) via a fixed relative path
|
|
111
|
+
computed from each module's own `import.meta.url`
|
|
112
|
+
(`path.resolve(HERE, '../../../frontend')` in `static-assets.js`,
|
|
113
|
+
`'../../frontend'` in `generate-html-report.mjs`). That math was correct
|
|
114
|
+
only for the unbundled dev checkout. Once `npm run build` (ncc) split those
|
|
115
|
+
modules into their own `dist/*.index.js` chunks, `import.meta.url` inside
|
|
116
|
+
them reflected `dist/`'s own, shallower location, and the same fixed
|
|
117
|
+
relative math resolved *outside the installed package entirely* — and
|
|
118
|
+
`frontend/` was never part of the published npm tarball to begin with. The
|
|
119
|
+
practical effect: `agentic-security explore .` 404'd on `GET /` for every
|
|
120
|
+
real `npx`/`npm install` user (`{"error":"not found"}`, never an
|
|
121
|
+
index.html), and `dataflow export . --format html` failed outright
|
|
122
|
+
(`ENOENT: no such file or directory, scandir '.../frontend/styles'`). Both
|
|
123
|
+
only ever worked when run from a full monorepo clone.
|
|
124
|
+
|
|
125
|
+
Fixed by shipping the frontend's servable files inside the published
|
|
126
|
+
package: `scanner/scripts/copy-frontend.mjs` (new) copies `frontend/`'s
|
|
127
|
+
allowlisted files (`index.html`, `src/**/*.js`, top-level `styles/*.css` —
|
|
128
|
+
the exact same allowlist `static-assets.js` already enforced for serving)
|
|
129
|
+
into `scanner/dist/frontend/` as part of `npm run build`; a new shared
|
|
130
|
+
resolver, `scanner/src/shared/frontend-root.js`, replaces both hardcoded
|
|
131
|
+
relative paths with a search-upward strategy that finds the frontend
|
|
132
|
+
correctly regardless of bundling depth. Verified by packing a real tarball
|
|
133
|
+
(`npm pack`) and installing it into an isolated directory with no monorepo
|
|
134
|
+
present: `explore`'s `GET /` now returns 200 with the real `index.html`
|
|
135
|
+
(previously 404), static JS/CSS assets and the token-authenticated
|
|
136
|
+
`/api/v1/graph` endpoint both 200, and `dataflow export --format html`
|
|
137
|
+
exits 0 and writes a real self-contained report (previously exited 2).
|
|
138
|
+
`dpia`/`ropa` export formats were already unaffected (a separate,
|
|
139
|
+
already-self-contained code path) and are unchanged.
|
|
140
|
+
|
|
14
141
|
## 0.147.0 — Documentation overhaul: a cohesive assurance-platform story, verified against the shipped code
|
|
15
142
|
|
|
16
143
|
The 0.144.0 Assurance Hardening release shipped a real 3-state ship verdict,
|
package/dist/1122.index.js
CHANGED
|
@@ -232,7 +232,12 @@ function export_image_validTimeoutMs(raw, fallback) {
|
|
|
232
232
|
if (!Number.isSafeInteger(n) || n < 0 || Object.is(n, -0)) return fallback;
|
|
233
233
|
return n;
|
|
234
234
|
}
|
|
235
|
-
|
|
235
|
+
// 15000 (the original default) left no headroom: the v0.147.0 release run
|
|
236
|
+
// took 7983ms for this same render on a good draw of the CI runner, and the
|
|
237
|
+
// v0.147.1 run timed out at ~15000ms on a slower draw — a 2x runner-speed
|
|
238
|
+
// swing entirely within normal GitHub Actions variance, not a regression.
|
|
239
|
+
// 30000 gives ~4x headroom over the observed good-case time.
|
|
240
|
+
const RENDER_TIMEOUT_MS = export_image_validTimeoutMs(process.env.AGENTIC_SECURITY_CHROME_RENDER_TIMEOUT_MS, 30000);
|
|
236
241
|
|
|
237
242
|
function _writeTempHtml(graph, opts) {
|
|
238
243
|
const html = (0,generate_html_report.generateHtmlReport)(graph, opts);
|
|
@@ -592,6 +597,8 @@ function bundleFrontendModules(entryAbsPath) {
|
|
|
592
597
|
|
|
593
598
|
// EXTERNAL MODULE: ./src/lineage/export-json.js
|
|
594
599
|
var export_json = __webpack_require__(859);
|
|
600
|
+
// EXTERNAL MODULE: ./src/shared/frontend-root.js
|
|
601
|
+
var frontend_root = __webpack_require__(1185);
|
|
595
602
|
;// CONCATENATED MODULE: ./scripts/generate-html-report.mjs
|
|
596
603
|
// generate-html-report.mjs — Milestone 4, sub-project Self-contained
|
|
597
604
|
// HTML report. Assembles ONE offline-safe HTML document: inline CSS,
|
|
@@ -605,8 +612,14 @@ var export_json = __webpack_require__(859);
|
|
|
605
612
|
|
|
606
613
|
|
|
607
614
|
|
|
615
|
+
|
|
608
616
|
const HERE = external_node_path_.dirname((0,external_node_url_.fileURLToPath)(import.meta.url));
|
|
609
|
-
|
|
617
|
+
// See src/shared/frontend-root.js: a hardcoded `../../frontend` resolved
|
|
618
|
+
// correctly for this file's unbundled dev location (scanner/scripts/) but
|
|
619
|
+
// broke for the published package, whose build now copies frontend/ into
|
|
620
|
+
// scanner/dist/frontend/ — a different relative depth from this same
|
|
621
|
+
// module once ncc bundles it into a dist/ chunk.
|
|
622
|
+
const FRONTEND_ROOT = (0,frontend_root/* resolveFrontendRoot */.D)(HERE);
|
|
610
623
|
const STYLES_DIR = external_node_path_.join(FRONTEND_ROOT, 'styles');
|
|
611
624
|
const ENTRY_PATH = external_node_path_.join(FRONTEND_ROOT, 'src', 'export-entry.js');
|
|
612
625
|
|
|
@@ -697,6 +710,70 @@ ${bundledJs}
|
|
|
697
710
|
}
|
|
698
711
|
|
|
699
712
|
|
|
713
|
+
/***/ }),
|
|
714
|
+
|
|
715
|
+
/***/ 1185:
|
|
716
|
+
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
|
|
717
|
+
|
|
718
|
+
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
719
|
+
/* harmony export */ D: () => (/* binding */ resolveFrontendRoot)
|
|
720
|
+
/* harmony export */ });
|
|
721
|
+
/* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3024);
|
|
722
|
+
/* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(6760);
|
|
723
|
+
// frontend-root.js — locates the Data Flow Explorer's `frontend/` assets
|
|
724
|
+
// (index.html/src/styles) regardless of whether this code is running:
|
|
725
|
+
// - unbundled, straight out of scanner/src/ or scanner/scripts/ (dev/test —
|
|
726
|
+
// frontend/ is a monorepo sibling of scanner/, 2-3 levels up), or
|
|
727
|
+
// - bundled by `npm run build` (ncc splits a dynamic import() into its own
|
|
728
|
+
// chunk file physically written into scanner/dist/, so import.meta.url
|
|
729
|
+
// inside that chunk reflects dist/'s own location, not the original
|
|
730
|
+
// source file's — a fixed relative-levels-up path that was correct for
|
|
731
|
+
// one depth breaks silently at the other), or
|
|
732
|
+
// - installed from the published npm package, where the build copies
|
|
733
|
+
// frontend/'s servable files into scanner/dist/frontend/ (a sibling of
|
|
734
|
+
// the chunk file itself, i.e. 0 levels up) — see scripts/copy-frontend.mjs.
|
|
735
|
+
//
|
|
736
|
+
// Rather than hardcode one of those depths (the bug this file fixes: every
|
|
737
|
+
// consumer used to hardcode the dev-only depth), search upward from the
|
|
738
|
+
// caller's own directory and take the first candidate that actually has a
|
|
739
|
+
// frontend/index.html on disk. Never guessed silently past that — a caller
|
|
740
|
+
// with no match anywhere gets a clear, actionable error instead of a
|
|
741
|
+
// downstream ENOENT/404 with no indication why.
|
|
742
|
+
|
|
743
|
+
|
|
744
|
+
|
|
745
|
+
|
|
746
|
+
const MAX_LEVELS_UP = 4;
|
|
747
|
+
|
|
748
|
+
/**
|
|
749
|
+
* @param {string} startDir - `path.dirname(fileURLToPath(import.meta.url))`
|
|
750
|
+
* of the CALLING module (not this file) — each caller's own bundled/
|
|
751
|
+
* unbundled location determines which candidate depth resolves.
|
|
752
|
+
* @returns {string} absolute path to a real `frontend/` directory containing
|
|
753
|
+
* `index.html`.
|
|
754
|
+
* @throws if no candidate directory up to MAX_LEVELS_UP contains one.
|
|
755
|
+
*/
|
|
756
|
+
function resolveFrontendRoot(startDir) {
|
|
757
|
+
const tried = [];
|
|
758
|
+
for (let up = 0; up <= MAX_LEVELS_UP; up++) {
|
|
759
|
+
const candidate = node_path__WEBPACK_IMPORTED_MODULE_1__.resolve(startDir, ...Array(up).fill('..'), 'frontend');
|
|
760
|
+
tried.push(candidate);
|
|
761
|
+
// Every caller invokes resolveFrontendRoot() once, at module-load time,
|
|
762
|
+
// to compute a top-level FRONTEND_ROOT const (see static-assets.js /
|
|
763
|
+
// generate-html-report.mjs) — never per-request inside the explore
|
|
764
|
+
// server's request handler. At most MAX_LEVELS_UP+1 (5) sync stat calls
|
|
765
|
+
// at process startup is not a request-path DoS surface.
|
|
766
|
+
if (node_fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(node_path__WEBPACK_IMPORTED_MODULE_1__.join(candidate, 'index.html'))) return candidate; // agentic-security-ignore: dos-sync-io
|
|
767
|
+
}
|
|
768
|
+
throw new Error(
|
|
769
|
+
`resolveFrontendRoot: no frontend/index.html found searching up from ${startDir}. ` +
|
|
770
|
+
`Tried: ${tried.join(', ')}. If you're running from a source checkout, run \`npm run build\` ` +
|
|
771
|
+
`first (it copies frontend/ into scanner/dist/frontend/); if you're running the published ` +
|
|
772
|
+
`package, this indicates a packaging defect — report it.`
|
|
773
|
+
);
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
|
|
700
777
|
/***/ })
|
|
701
778
|
|
|
702
779
|
};
|
package/dist/3180.index.js
CHANGED
|
@@ -197,6 +197,8 @@ function bundleFrontendModules(entryAbsPath) {
|
|
|
197
197
|
|
|
198
198
|
// EXTERNAL MODULE: ./src/lineage/export-json.js
|
|
199
199
|
var export_json = __webpack_require__(859);
|
|
200
|
+
// EXTERNAL MODULE: ./src/shared/frontend-root.js
|
|
201
|
+
var frontend_root = __webpack_require__(1185);
|
|
200
202
|
;// CONCATENATED MODULE: ./scripts/generate-html-report.mjs
|
|
201
203
|
// generate-html-report.mjs — Milestone 4, sub-project Self-contained
|
|
202
204
|
// HTML report. Assembles ONE offline-safe HTML document: inline CSS,
|
|
@@ -210,8 +212,14 @@ var export_json = __webpack_require__(859);
|
|
|
210
212
|
|
|
211
213
|
|
|
212
214
|
|
|
215
|
+
|
|
213
216
|
const HERE = external_node_path_.dirname((0,external_node_url_.fileURLToPath)(import.meta.url));
|
|
214
|
-
|
|
217
|
+
// See src/shared/frontend-root.js: a hardcoded `../../frontend` resolved
|
|
218
|
+
// correctly for this file's unbundled dev location (scanner/scripts/) but
|
|
219
|
+
// broke for the published package, whose build now copies frontend/ into
|
|
220
|
+
// scanner/dist/frontend/ — a different relative depth from this same
|
|
221
|
+
// module once ncc bundles it into a dist/ chunk.
|
|
222
|
+
const FRONTEND_ROOT = (0,frontend_root/* resolveFrontendRoot */.D)(HERE);
|
|
215
223
|
const STYLES_DIR = external_node_path_.join(FRONTEND_ROOT, 'styles');
|
|
216
224
|
const ENTRY_PATH = external_node_path_.join(FRONTEND_ROOT, 'src', 'export-entry.js');
|
|
217
225
|
|
|
@@ -302,6 +310,70 @@ ${bundledJs}
|
|
|
302
310
|
}
|
|
303
311
|
|
|
304
312
|
|
|
313
|
+
/***/ }),
|
|
314
|
+
|
|
315
|
+
/***/ 1185:
|
|
316
|
+
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
|
|
317
|
+
|
|
318
|
+
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
319
|
+
/* harmony export */ D: () => (/* binding */ resolveFrontendRoot)
|
|
320
|
+
/* harmony export */ });
|
|
321
|
+
/* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3024);
|
|
322
|
+
/* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(6760);
|
|
323
|
+
// frontend-root.js — locates the Data Flow Explorer's `frontend/` assets
|
|
324
|
+
// (index.html/src/styles) regardless of whether this code is running:
|
|
325
|
+
// - unbundled, straight out of scanner/src/ or scanner/scripts/ (dev/test —
|
|
326
|
+
// frontend/ is a monorepo sibling of scanner/, 2-3 levels up), or
|
|
327
|
+
// - bundled by `npm run build` (ncc splits a dynamic import() into its own
|
|
328
|
+
// chunk file physically written into scanner/dist/, so import.meta.url
|
|
329
|
+
// inside that chunk reflects dist/'s own location, not the original
|
|
330
|
+
// source file's — a fixed relative-levels-up path that was correct for
|
|
331
|
+
// one depth breaks silently at the other), or
|
|
332
|
+
// - installed from the published npm package, where the build copies
|
|
333
|
+
// frontend/'s servable files into scanner/dist/frontend/ (a sibling of
|
|
334
|
+
// the chunk file itself, i.e. 0 levels up) — see scripts/copy-frontend.mjs.
|
|
335
|
+
//
|
|
336
|
+
// Rather than hardcode one of those depths (the bug this file fixes: every
|
|
337
|
+
// consumer used to hardcode the dev-only depth), search upward from the
|
|
338
|
+
// caller's own directory and take the first candidate that actually has a
|
|
339
|
+
// frontend/index.html on disk. Never guessed silently past that — a caller
|
|
340
|
+
// with no match anywhere gets a clear, actionable error instead of a
|
|
341
|
+
// downstream ENOENT/404 with no indication why.
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
const MAX_LEVELS_UP = 4;
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* @param {string} startDir - `path.dirname(fileURLToPath(import.meta.url))`
|
|
350
|
+
* of the CALLING module (not this file) — each caller's own bundled/
|
|
351
|
+
* unbundled location determines which candidate depth resolves.
|
|
352
|
+
* @returns {string} absolute path to a real `frontend/` directory containing
|
|
353
|
+
* `index.html`.
|
|
354
|
+
* @throws if no candidate directory up to MAX_LEVELS_UP contains one.
|
|
355
|
+
*/
|
|
356
|
+
function resolveFrontendRoot(startDir) {
|
|
357
|
+
const tried = [];
|
|
358
|
+
for (let up = 0; up <= MAX_LEVELS_UP; up++) {
|
|
359
|
+
const candidate = node_path__WEBPACK_IMPORTED_MODULE_1__.resolve(startDir, ...Array(up).fill('..'), 'frontend');
|
|
360
|
+
tried.push(candidate);
|
|
361
|
+
// Every caller invokes resolveFrontendRoot() once, at module-load time,
|
|
362
|
+
// to compute a top-level FRONTEND_ROOT const (see static-assets.js /
|
|
363
|
+
// generate-html-report.mjs) — never per-request inside the explore
|
|
364
|
+
// server's request handler. At most MAX_LEVELS_UP+1 (5) sync stat calls
|
|
365
|
+
// at process startup is not a request-path DoS surface.
|
|
366
|
+
if (node_fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(node_path__WEBPACK_IMPORTED_MODULE_1__.join(candidate, 'index.html'))) return candidate; // agentic-security-ignore: dos-sync-io
|
|
367
|
+
}
|
|
368
|
+
throw new Error(
|
|
369
|
+
`resolveFrontendRoot: no frontend/index.html found searching up from ${startDir}. ` +
|
|
370
|
+
`Tried: ${tried.join(', ')}. If you're running from a source checkout, run \`npm run build\` ` +
|
|
371
|
+
`first (it copies frontend/ into scanner/dist/frontend/); if you're running the published ` +
|
|
372
|
+
`package, this indicates a packaging defect — report it.`
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
|
|
305
377
|
/***/ })
|
|
306
378
|
|
|
307
379
|
};
|
package/dist/5051.index.js
CHANGED
|
@@ -25,6 +25,8 @@ var security = __webpack_require__(6944);
|
|
|
25
25
|
var routes = __webpack_require__(4268);
|
|
26
26
|
// EXTERNAL MODULE: external "node:url"
|
|
27
27
|
var external_node_url_ = __webpack_require__(3136);
|
|
28
|
+
// EXTERNAL MODULE: ./src/shared/frontend-root.js
|
|
29
|
+
var frontend_root = __webpack_require__(1185);
|
|
28
30
|
;// CONCATENATED MODULE: ./src/server/static-assets.js
|
|
29
31
|
// static-assets.js — Milestone 3, sub-project Wire.
|
|
30
32
|
//
|
|
@@ -43,13 +45,18 @@ var external_node_url_ = __webpack_require__(3136);
|
|
|
43
45
|
|
|
44
46
|
|
|
45
47
|
|
|
46
|
-
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
//
|
|
48
|
+
|
|
49
|
+
// Located relative to this module's own directory (path.dirname(
|
|
50
|
+
// fileURLToPath(import.meta.url)), the same pattern scanner/src/mcp/server.js
|
|
51
|
+
// uses), but via resolveFrontendRoot's search-upward strategy rather than a
|
|
52
|
+
// single hardcoded relative depth — a fixed `../../../frontend` resolved
|
|
53
|
+
// correctly for this file's unbundled dev location but broke (silently, at
|
|
54
|
+
// runtime, for every real npx/npm user) once `npm run build` split this
|
|
55
|
+
// module into its own dist/ chunk, whose import.meta.url reflects dist/'s
|
|
56
|
+
// own shallower location. See src/shared/frontend-root.js for the full story
|
|
57
|
+
// and scripts/copy-frontend.mjs for the build-time copy this now finds.
|
|
51
58
|
const _here = external_node_path_.dirname((0,external_node_url_.fileURLToPath)(import.meta.url));
|
|
52
|
-
const FRONTEND_ROOT =
|
|
59
|
+
const FRONTEND_ROOT = (0,frontend_root/* resolveFrontendRoot */.D)(_here);
|
|
53
60
|
|
|
54
61
|
const CONTENT_TYPE_MAP = Object.freeze({
|
|
55
62
|
'.html': 'text/html; charset=utf-8',
|
|
@@ -765,6 +772,70 @@ function isValidHost(hostHeader, expectedPort) {
|
|
|
765
772
|
const CSP_HEADER_VALUE = "default-src 'none'; frame-ancestors 'none'";
|
|
766
773
|
|
|
767
774
|
|
|
775
|
+
/***/ }),
|
|
776
|
+
|
|
777
|
+
/***/ 1185:
|
|
778
|
+
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
|
|
779
|
+
|
|
780
|
+
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
781
|
+
/* harmony export */ D: () => (/* binding */ resolveFrontendRoot)
|
|
782
|
+
/* harmony export */ });
|
|
783
|
+
/* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3024);
|
|
784
|
+
/* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(6760);
|
|
785
|
+
// frontend-root.js — locates the Data Flow Explorer's `frontend/` assets
|
|
786
|
+
// (index.html/src/styles) regardless of whether this code is running:
|
|
787
|
+
// - unbundled, straight out of scanner/src/ or scanner/scripts/ (dev/test —
|
|
788
|
+
// frontend/ is a monorepo sibling of scanner/, 2-3 levels up), or
|
|
789
|
+
// - bundled by `npm run build` (ncc splits a dynamic import() into its own
|
|
790
|
+
// chunk file physically written into scanner/dist/, so import.meta.url
|
|
791
|
+
// inside that chunk reflects dist/'s own location, not the original
|
|
792
|
+
// source file's — a fixed relative-levels-up path that was correct for
|
|
793
|
+
// one depth breaks silently at the other), or
|
|
794
|
+
// - installed from the published npm package, where the build copies
|
|
795
|
+
// frontend/'s servable files into scanner/dist/frontend/ (a sibling of
|
|
796
|
+
// the chunk file itself, i.e. 0 levels up) — see scripts/copy-frontend.mjs.
|
|
797
|
+
//
|
|
798
|
+
// Rather than hardcode one of those depths (the bug this file fixes: every
|
|
799
|
+
// consumer used to hardcode the dev-only depth), search upward from the
|
|
800
|
+
// caller's own directory and take the first candidate that actually has a
|
|
801
|
+
// frontend/index.html on disk. Never guessed silently past that — a caller
|
|
802
|
+
// with no match anywhere gets a clear, actionable error instead of a
|
|
803
|
+
// downstream ENOENT/404 with no indication why.
|
|
804
|
+
|
|
805
|
+
|
|
806
|
+
|
|
807
|
+
|
|
808
|
+
const MAX_LEVELS_UP = 4;
|
|
809
|
+
|
|
810
|
+
/**
|
|
811
|
+
* @param {string} startDir - `path.dirname(fileURLToPath(import.meta.url))`
|
|
812
|
+
* of the CALLING module (not this file) — each caller's own bundled/
|
|
813
|
+
* unbundled location determines which candidate depth resolves.
|
|
814
|
+
* @returns {string} absolute path to a real `frontend/` directory containing
|
|
815
|
+
* `index.html`.
|
|
816
|
+
* @throws if no candidate directory up to MAX_LEVELS_UP contains one.
|
|
817
|
+
*/
|
|
818
|
+
function resolveFrontendRoot(startDir) {
|
|
819
|
+
const tried = [];
|
|
820
|
+
for (let up = 0; up <= MAX_LEVELS_UP; up++) {
|
|
821
|
+
const candidate = node_path__WEBPACK_IMPORTED_MODULE_1__.resolve(startDir, ...Array(up).fill('..'), 'frontend');
|
|
822
|
+
tried.push(candidate);
|
|
823
|
+
// Every caller invokes resolveFrontendRoot() once, at module-load time,
|
|
824
|
+
// to compute a top-level FRONTEND_ROOT const (see static-assets.js /
|
|
825
|
+
// generate-html-report.mjs) — never per-request inside the explore
|
|
826
|
+
// server's request handler. At most MAX_LEVELS_UP+1 (5) sync stat calls
|
|
827
|
+
// at process startup is not a request-path DoS surface.
|
|
828
|
+
if (node_fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(node_path__WEBPACK_IMPORTED_MODULE_1__.join(candidate, 'index.html'))) return candidate; // agentic-security-ignore: dos-sync-io
|
|
829
|
+
}
|
|
830
|
+
throw new Error(
|
|
831
|
+
`resolveFrontendRoot: no frontend/index.html found searching up from ${startDir}. ` +
|
|
832
|
+
`Tried: ${tried.join(', ')}. If you're running from a source checkout, run \`npm run build\` ` +
|
|
833
|
+
`first (it copies frontend/ into scanner/dist/frontend/); if you're running the published ` +
|
|
834
|
+
`package, this indicates a packaging defect — report it.`
|
|
835
|
+
);
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
|
|
768
839
|
/***/ })
|
|
769
840
|
|
|
770
841
|
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en" data-theme="dark">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Data Flow Explorer — Illustrative demo data</title>
|
|
7
|
+
<link rel="stylesheet" href="styles/tokens.css" />
|
|
8
|
+
<link rel="stylesheet" href="styles/shell.css" />
|
|
9
|
+
<link rel="stylesheet" href="styles/architecture-view.css" />
|
|
10
|
+
<link rel="stylesheet" href="styles/inspector.css" />
|
|
11
|
+
<link rel="stylesheet" href="styles/privacy-view.css" />
|
|
12
|
+
<link rel="stylesheet" href="styles/trace-view.css" />
|
|
13
|
+
<link rel="stylesheet" href="styles/filter-rail.css" />
|
|
14
|
+
<link rel="stylesheet" href="styles/inventory-view.css" />
|
|
15
|
+
<link rel="stylesheet" href="styles/query-bar.css" />
|
|
16
|
+
</head>
|
|
17
|
+
<body>
|
|
18
|
+
<div id="app-root"></div>
|
|
19
|
+
<script type="module" src="./src/main.js"></script>
|
|
20
|
+
</body>
|
|
21
|
+
</html>
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { mountShell, buildContextRailText } from './shell.js';
|
|
2
|
+
import { computeArchitectureViewModel, renderArchitectureView, renderFlowSummary } from './views/architecture-view.js';
|
|
3
|
+
import { computePrivacyViewModel, renderPrivacyView } from './views/privacy-view.js';
|
|
4
|
+
import { computeTraceViewModel, renderTraceView } from './views/trace-view.js';
|
|
5
|
+
import { computeInventoryViewModel, renderInventoryView } from './views/inventory-view.js';
|
|
6
|
+
import { computeInspectorViewModel, renderInspector } from './components/evidence-inspector.js';
|
|
7
|
+
import { computeFilterFacets, renderFilterRail } from './components/filter-rail.js';
|
|
8
|
+
import { computeQueryBarViewModel, renderQueryBar, compileQuerySafely } from './components/query-bar.js';
|
|
9
|
+
import {
|
|
10
|
+
showUpstream, showDownstream, showAllPaths, showShortestPath,
|
|
11
|
+
showExternalPathsOnly, showUnprotectedPathsOnly, showAliases, showDisconnected,
|
|
12
|
+
} from './lib/focus-controls.js';
|
|
13
|
+
import { el, clear } from './lib/dom.js';
|
|
14
|
+
|
|
15
|
+
// Milestone 3, sub-project M3-UX-Query, Task 4's own resolution of the task
|
|
16
|
+
// brief's Step 4 open design question: a focus control's own {nodeIds,
|
|
17
|
+
// edgeIds} result (from lib/focus-controls.js) has no single canonical
|
|
18
|
+
// `selectedId` to thread through the existing shell.js state mechanism, and
|
|
19
|
+
// is real-but-transient UI state (not meaningfully shareable — it holds
|
|
20
|
+
// Sets, which don't serialize to the URL hash cleanly), matching the same
|
|
21
|
+
// precedent A11y's own `inspectorOverlayOpen` and M3-Render's own
|
|
22
|
+
// `currentViewport` already established: module-local state, not persisted.
|
|
23
|
+
// Lives here (app.js), the one place that already orchestrates every view's
|
|
24
|
+
// own compute/render call, rather than inside architecture-view.js itself.
|
|
25
|
+
let currentFocusSelection = null;
|
|
26
|
+
|
|
27
|
+
const FOCUS_CONTROLS = [
|
|
28
|
+
{ id: 'upstream', label: 'Show upstream', needsNode: true, run: (graph, anchor) => showUpstream(graph, anchor.nodeId) },
|
|
29
|
+
{ id: 'downstream', label: 'Show downstream', needsNode: true, run: (graph, anchor) => showDownstream(graph, anchor.nodeId) },
|
|
30
|
+
{ id: 'all-paths', label: 'Show all paths', needsNode: true, run: (graph, anchor) => showAllPaths(graph, anchor.nodeId) },
|
|
31
|
+
{ id: 'shortest-path', label: 'Show shortest path', needsEdge: true, run: (graph, anchor) => showShortestPath(graph, anchor.edge.from, anchor.edge.to) },
|
|
32
|
+
{ id: 'external-only', label: 'Show external paths only', run: (graph) => showExternalPathsOnly(graph) },
|
|
33
|
+
{ id: 'unprotected-only', label: 'Show unprotected paths only', run: (graph) => showUnprotectedPathsOnly(graph) },
|
|
34
|
+
{ id: 'aliases', label: 'Show aliases', needsNode: true, run: (graph, anchor) => showAliases(graph, anchor.nodeId) },
|
|
35
|
+
{ id: 'disconnected', label: 'Show disconnected', run: (graph) => showDisconnected(graph) },
|
|
36
|
+
// resetToOverview is deliberately NOT a lib/focus-controls.js function
|
|
37
|
+
// (per that file's own header note) — it is implemented here directly:
|
|
38
|
+
// clear the focus override AND the underlying single selection.
|
|
39
|
+
{ id: 'reset', label: 'Reset to application overview', isReset: true },
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
// Determines which node/edge a focus control acts on for the CURRENT
|
|
43
|
+
// selection. A directly-selected NODE is its own anchor. A directly-
|
|
44
|
+
// selected EDGE carries both endpoints, used only by "Show shortest path"
|
|
45
|
+
// (the one control that genuinely needs two nodes). A directly-selected
|
|
46
|
+
// FLOW has no single node id of its own, so its own source node is used as
|
|
47
|
+
// a reasonable "origin" anchor — a real, disclosed scoping choice, not an
|
|
48
|
+
// oversight (the query language and focus controls are both real DSLs over
|
|
49
|
+
// this graph, but a flow selection's own node-shaped controls have to pick
|
|
50
|
+
// SOME node, and the flow's source is the least arbitrary choice available).
|
|
51
|
+
function resolveFocusAnchor(graph, state) {
|
|
52
|
+
if (!state.selectedId) return null;
|
|
53
|
+
const node = graph.nodes.find((n) => n.id === state.selectedId);
|
|
54
|
+
if (node) return { nodeId: node.id, edge: null };
|
|
55
|
+
const edge = graph.edges.find((e) => e.id === state.selectedId);
|
|
56
|
+
if (edge) return { nodeId: edge.from, edge };
|
|
57
|
+
const flow = graph.flows.find((f) => f.id === state.selectedId);
|
|
58
|
+
if (flow) return { nodeId: flow.source, edge: null };
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Appends the focus-control button group into an already-populated context
|
|
63
|
+
// rail (the architecture-view.js's own renderFlowSummary(), or the shell's
|
|
64
|
+
// plain textContent fallback, has already run and populated it — this
|
|
65
|
+
// APPENDS, it never clears). Only rendered on Architecture View, since a
|
|
66
|
+
// focus selection's {nodeIds, edgeIds} only has a visible effect there
|
|
67
|
+
// (computeArchitectureViewModel's own new 3rd parameter). Gated on there
|
|
68
|
+
// being an active selection at all, matching the task brief's own wording
|
|
69
|
+
// ("offering the 9 named controls when a node/flow is selected").
|
|
70
|
+
function renderFocusControlMenu(graph, state, contextRailEl, shellApi, rerender) {
|
|
71
|
+
const anchor = resolveFocusAnchor(graph, state);
|
|
72
|
+
if (!anchor && !currentFocusSelection) return;
|
|
73
|
+
|
|
74
|
+
const buttons = FOCUS_CONTROLS.filter((control) => {
|
|
75
|
+
if (control.isReset) return true;
|
|
76
|
+
if (control.needsEdge) return Boolean(anchor?.edge);
|
|
77
|
+
if (control.needsNode) return Boolean(anchor?.nodeId);
|
|
78
|
+
return true; // graph-wide controls (external/unprotected/disconnected) need no anchor
|
|
79
|
+
}).map((control) =>
|
|
80
|
+
el(
|
|
81
|
+
'button',
|
|
82
|
+
{
|
|
83
|
+
class: 'focus-control-menu__button',
|
|
84
|
+
type: 'button',
|
|
85
|
+
'data-focus-control': control.id,
|
|
86
|
+
onClick: () => {
|
|
87
|
+
if (control.isReset) {
|
|
88
|
+
currentFocusSelection = null;
|
|
89
|
+
shellApi.setSelection(null); // notifies onStateChange, which re-invokes rerender itself
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
currentFocusSelection = control.run(graph, anchor);
|
|
93
|
+
rerender();
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
control.label,
|
|
97
|
+
),
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
contextRailEl.appendChild(el('div', { class: 'focus-control-menu' }, buttons));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function bootstrap(rootEl, graph) {
|
|
104
|
+
const shellApi = mountShell(rootEl, graph);
|
|
105
|
+
const filterFacets = computeFilterFacets(graph);
|
|
106
|
+
|
|
107
|
+
// Any NEW single-item selection made through an EXISTING selection path
|
|
108
|
+
// (a node/edge click on Architecture View, a Privacy/Trace/Inventory row
|
|
109
|
+
// click) must clear a stale focus-control override — otherwise a user
|
|
110
|
+
// could click "Show upstream" and then click an unrelated node, and see
|
|
111
|
+
// the OLD focus set still applied instead of the new plain selection.
|
|
112
|
+
function selectAndClearFocus(id) {
|
|
113
|
+
currentFocusSelection = null;
|
|
114
|
+
shellApi.setSelection(id);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function rerender() {
|
|
118
|
+
const state = shellApi.getState();
|
|
119
|
+
|
|
120
|
+
const queryBarViewModel = computeQueryBarViewModel(state);
|
|
121
|
+
let queryPredicate = () => true;
|
|
122
|
+
if (!queryBarViewModel.error) {
|
|
123
|
+
const compiled = compileQuerySafely(graph, state.filters?.query ?? '');
|
|
124
|
+
queryPredicate = compiled.predicate;
|
|
125
|
+
// A syntax-clean query can still fail at evaluation time (an
|
|
126
|
+
// unrecognized field name, thrown by query-language.js's own
|
|
127
|
+
// evaluateNode — see compileQuerySafely's own comment). Surface that
|
|
128
|
+
// the same way a syntax error is surfaced, rather than silently
|
|
129
|
+
// falling back to "no filter" with no visible explanation.
|
|
130
|
+
if (compiled.error) queryBarViewModel.error = compiled.error;
|
|
131
|
+
}
|
|
132
|
+
renderQueryBar(queryBarViewModel, shellApi.getQueryBarEl(), (nextQuery) => {
|
|
133
|
+
shellApi.setFilters({ ...(state.filters ?? {}), query: nextQuery });
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
if (state.view === 'architecture') {
|
|
137
|
+
const viewModel = computeArchitectureViewModel(graph, state, currentFocusSelection);
|
|
138
|
+
renderArchitectureView(viewModel, shellApi.getCanvasEl(), selectAndClearFocus);
|
|
139
|
+
const contextRailEl = shellApi.getContextRailEl();
|
|
140
|
+
if (viewModel.flowSummary) {
|
|
141
|
+
renderFlowSummary(viewModel.flowSummary, contextRailEl);
|
|
142
|
+
} else {
|
|
143
|
+
clear(contextRailEl);
|
|
144
|
+
contextRailEl.textContent = buildContextRailText(graph);
|
|
145
|
+
}
|
|
146
|
+
renderFocusControlMenu(graph, state, contextRailEl, shellApi, rerender);
|
|
147
|
+
} else if (state.view === 'privacy') {
|
|
148
|
+
const viewModel = computePrivacyViewModel(graph, state, queryPredicate);
|
|
149
|
+
renderPrivacyView(viewModel, shellApi.getCanvasEl(), selectAndClearFocus);
|
|
150
|
+
shellApi.getContextRailEl().textContent = buildContextRailText(graph);
|
|
151
|
+
} else if (state.view === 'trace') {
|
|
152
|
+
const viewModel = computeTraceViewModel(graph, state);
|
|
153
|
+
renderTraceView(viewModel, shellApi.getCanvasEl(), selectAndClearFocus);
|
|
154
|
+
shellApi.getContextRailEl().textContent = buildContextRailText(graph);
|
|
155
|
+
} else if (state.view === 'inventory') {
|
|
156
|
+
const viewModel = computeInventoryViewModel(graph, state, queryPredicate);
|
|
157
|
+
renderInventoryView(viewModel, shellApi.getCanvasEl(), selectAndClearFocus, (tableId) => shellApi.setTable(tableId));
|
|
158
|
+
shellApi.getContextRailEl().textContent = buildContextRailText(graph);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const inspectorViewModel = computeInspectorViewModel(graph, state.selectedId);
|
|
162
|
+
renderInspector(inspectorViewModel, shellApi.getInspectorEl());
|
|
163
|
+
|
|
164
|
+
if (state.view === 'privacy' || state.view === 'inventory') {
|
|
165
|
+
renderFilterRail(filterFacets, state.filters ?? {}, shellApi.getLeftRailEl(), (nextFilters) => shellApi.setFilters(nextFilters));
|
|
166
|
+
} else {
|
|
167
|
+
const railEl = shellApi.getLeftRailEl();
|
|
168
|
+
railEl.textContent = 'Filters apply to Privacy View and some Inventory tables.';
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
shellApi.onStateChange(rerender);
|
|
173
|
+
rerender();
|
|
174
|
+
|
|
175
|
+
return shellApi;
|
|
176
|
+
}
|