feat: configure inspectors, agent tools, write actions, redaction and… · HomeLight23/angular-devtools@af64a58 · GitHub
Skip to content

Commit af64a58

Browse files
authored
feat: configure inspectors, agent tools, write actions, redaction and limits (pangular-inspector#56)
Closes pangular-inspector#51 One NgDevtoolsConfig for the Express hub, the Vite plugin and createNgDevtools: turn inspectors off, make the agent read-only or hide tools, block panel and agent writes, add redaction names and set limits. The Dashboard shows the active config and the docs site has a Configuration page.
1 parent bee4234 commit af64a58

54 files changed

Lines changed: 2008 additions & 261 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎app/src/app.ts‎

Lines changed: 20 additions & 3 deletions

‎app/src/devtools-config.ts‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import type { DevframeRpcClient } from 'devframe/client';
2+
import {
3+
actionBlockedMessage,
4+
configFromConnection,
5+
type NgDevtoolsAction,
6+
type NgDevtoolsInspector,
7+
type ResolvedNgDevtoolsConfig,
8+
} from '@santoshyadavdev/ng-devtools/config';
9+
import type { Tab } from './types/tab.types';
10+
11+
export { actionBlockedMessage };
12+
13+
/** The config the server published in its connection info; everything is on without one. */
14+
export function panelConfig(client: DevframeRpcClient | null): ResolvedNgDevtoolsConfig {
15+
return configFromConnection(client?.connectionMeta);
16+
}
17+
18+
export function actionAllowed(client: DevframeRpcClient | null, action: NgDevtoolsAction): boolean {
19+
return panelConfig(client).actions[action];
20+
}
21+
22+
const TAB_INSPECTOR: Partial<Record<Tab, NgDevtoolsInspector>> = {
23+
components: 'components',
24+
routes: 'router',
25+
signals: 'signals',
26+
injectors: 'injectors',
27+
store: 'ngrx',
28+
forms: 'forms',
29+
pipes: 'pipes',
30+
network: 'http',
31+
analog: 'analog',
32+
};
33+
34+
export function tabEnabled(tab: Tab, config: ResolvedNgDevtoolsConfig): boolean {
35+
const inspector = TAB_INSPECTOR[tab];
36+
return !inspector || config.inspectors[inspector];
37+
}

‎app/src/pages/analog-inspector.ts‎

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
import type { DevframeRpcClient } from 'devframe/client';
1212
import { time } from '../format';
1313
import { rpcTry as call } from '../rpc';
14+
import { actionAllowed, actionBlockedMessage } from '../devtools-config';
1415
import { Select } from '../ui/select';
1516

1617
interface AnalogRoute {
@@ -619,8 +620,18 @@ function walk(routes: AnalogRoute[], depth = 0, out: { route: AnalogRoute; depth
619620
[value]="apiPath()"
620621
(input)="apiPath.set($any($event.target).value)"
621622
/>
622-
<button type="submit" class="btn primary">Send</button>
623+
<button
624+
type="submit"
625+
class="btn primary"
626+
[disabled]="!canCall()"
627+
[attr.aria-describedby]="canCall() ? null : 'analog-calls-off'"
628+
>
629+
Send
630+
</button>
623631
</div>
632+
@if (!canCall()) {
633+
<p id="analog-calls-off" class="muted">{{ callsOff }}</p>
634+
}
624635
@if (method() !== 'GET') {
625636
<label for="api-body">JSON body</label>
626637
<textarea
@@ -1819,6 +1830,8 @@ function walk(routes: AnalogRoute[], depth = 0, out: { route: AnalogRoute; depth
18191830
})
18201831
export class AnalogInspector {
18211832
rpc = input<DevframeRpcClient | null>(null);
1833+
readonly canCall = computed(() => actionAllowed(this.rpc(), 'analog'));
1834+
protected readonly callsOff = actionBlockedMessage('analog');
18221835

18231836
readonly kinds: Kind[] = ['all', 'page', 'load', 'fn', 'api'];
18241837
readonly methods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'];

‎app/src/pages/dashboard.ts‎

Lines changed: 80 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,12 @@ import {
99
signal,
1010
} from '@angular/core';
1111
import type { DevframeRpcClient } from 'devframe/client';
12+
import {
13+
summarizeNgDevtoolsConfig,
14+
type ResolvedNgDevtoolsConfig,
15+
} from '@santoshyadavdev/ng-devtools/config';
1216
import { hostPageId } from '../page-id';
17+
import { panelConfig, tabEnabled } from '../devtools-config';
1318
import { TabIcon } from './tab-icon';
1419

1520
type LoadState = 'loading' | 'ready' | 'error';
@@ -160,7 +165,7 @@ export function storeCard(rows: Row[]): Card {
160165
</section>
161166
162167
<div class="grid">
163-
@for (stat of stats; track stat.tab; let i = $index) {
168+
@for (stat of stats(); track stat.tab; let i = $index) {
164169
@let state = stateOf(stat.tab);
165170
<button
166171
type="button"
@@ -191,6 +196,22 @@ export function storeCard(rows: Row[]): Card {
191196
</button>
192197
}
193198
</div>
199+
200+
<section class="config" aria-labelledby="config-title">
201+
<h2 id="config-title">Configuration</h2>
202+
@if (configItems().length) {
203+
<dl>
204+
@for (item of configItems(); track item.label) {
205+
<div>
206+
<dt>{{ item.label }}</dt>
207+
<dd>{{ item.value }}</dd>
208+
</div>
209+
}
210+
</dl>
211+
} @else {
212+
<p>Defaults</p>
213+
}
214+
</section>
194215
`,
195216
styles: `
196217
@use 'mixins' as m;
@@ -384,6 +405,45 @@ export function storeCard(rows: Row[]): Card {
384405
font-size: 12px;
385406
line-height: 16px;
386407
}
408+
.config {
409+
margin-top: 16px;
410+
padding: 16px;
411+
border: 1px solid var(--border);
412+
border-radius: var(--radius);
413+
background: var(--surface);
414+
}
415+
.config h2 {
416+
@include m.label;
417+
margin: 0 0 8px;
418+
color: var(--text-2);
419+
}
420+
.config p,
421+
.config dl {
422+
margin: 0;
423+
color: var(--text);
424+
font-size: 13px;
425+
line-height: 1.5;
426+
}
427+
.config dl {
428+
display: grid;
429+
gap: 4px;
430+
}
431+
.config dl div {
432+
display: flex;
433+
flex-wrap: wrap;
434+
gap: 4px 8px;
435+
}
436+
.config dt {
437+
color: var(--text-2);
438+
}
439+
.config dt::after {
440+
content: ':';
441+
}
442+
.config dd {
443+
margin: 0;
444+
color: var(--text-strong);
445+
overflow-wrap: anywhere;
446+
}
387447
@keyframes shimmer {
388448
to {
389449
background-position: -200% 0;
@@ -424,7 +484,11 @@ export class Dashboard {
424484
navigate = output<StatTab>();
425485

426486
meta = signal<BuildMeta | null>(null);
427-
protected readonly stats = STATS;
487+
private readonly config = computed(() => panelConfig(this.rpc()));
488+
protected readonly stats = computed(() =>
489+
STATS.filter((stat) => tabEnabled(stat.tab, this.config())),
490+
);
491+
protected readonly configItems = computed(() => summarizeNgDevtoolsConfig(this.config()));
428492
protected readonly metaState = signal<LoadState>('loading');
429493
protected readonly states = signal<Partial<Record<StatTab, LoadState>>>({});
430494
private readonly rows = signal<Partial<Record<StatTab, Row[]>>>({});
@@ -510,13 +574,15 @@ export class Dashboard {
510574
this.metaState.set('ready');
511575
})
512576
.catch(() => this.metaState.set('error'));
513-
this.load(my.rpc.call('get-components'), 'components');
514-
this.load(my.rpc.call('get-routes'), 'routes');
515-
this.load(my.rpc.call('get-signals'), 'signals');
516-
this.load(my.rpc.call('get-providers'), 'injectors');
517-
this.load(my.rpc.call('get-ngrx-store'), 'store');
518-
this.load(my.rpc.call('get-pipes'), 'pipes');
519-
void this.watchLive(client);
577+
const config = panelConfig(client);
578+
const on = (tab: StatTab) => tabEnabled(tab, config);
579+
if (on('components')) this.load(my.rpc.call('get-components'), 'components');
580+
if (on('routes')) this.load(my.rpc.call('get-routes'), 'routes');
581+
if (on('signals')) this.load(my.rpc.call('get-signals'), 'signals');
582+
if (on('injectors')) this.load(my.rpc.call('get-providers'), 'injectors');
583+
if (on('store')) this.load(my.rpc.call('get-ngrx-store'), 'store');
584+
if (on('pipes')) this.load(my.rpc.call('get-pipes'), 'pipes');
585+
void this.watchLive(client, config);
520586
});
521587
this.destroyRef.onDestroy(() => this.unwatch());
522588
}
@@ -537,7 +603,7 @@ export class Dashboard {
537603
.catch(() => mark('error'));
538604
}
539605

540-
private async watchLive(client: DevframeRpcClient) {
606+
private async watchLive(client: DevframeRpcClient, config: ResolvedNgDevtoolsConfig) {
541607
this.unwatch();
542608
const rpc = client.scope('ng-devtools').rpc;
543609
const follow = async <T>(
@@ -554,8 +620,10 @@ export class Dashboard {
554620
}
555621
};
556622
await Promise.all([
557-
follow<InjectorSnapshot | null>('injector-tree', (value) => this.injectorTree.set(value)),
558-
follow<GraphSnapshot | null>('signal-graph', (value) => this.signalGraph.set(value)),
623+
config.inspectors.injectors &&
624+
follow<InjectorSnapshot | null>('injector-tree', (value) => this.injectorTree.set(value)),
625+
config.inspectors.signals &&
626+
follow<GraphSnapshot | null>('signal-graph', (value) => this.signalGraph.set(value)),
559627
]);
560628
}
561629

‎app/src/pages/forms-field-detail.ts‎

Lines changed: 33 additions & 3 deletions

0 commit comments

Comments
 (0)