@serve.zone/dcrouter 16.2.1 → 16.4.0
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/deno.json +1 -1
- package/dist_serve/bundle.js +1190 -1100
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/config/helpers.http-redirects.d.ts +1 -1
- package/dist_ts/config/helpers.http-redirects.js +4 -3
- package/dist_ts_interfaces/data/route-management.d.ts +2 -0
- package/dist_ts_interfaces/data/route-management.js +6 -1
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/elements/access/ops-view-apitokens.js +3 -3
- package/dist_ts_web/elements/access/ops-view-gatewayclients.js +3 -3
- package/dist_ts_web/elements/network/ops-view-network-activity.js +6 -6
- package/dist_ts_web/elements/network/ops-view-redirects.d.ts +3 -0
- package/dist_ts_web/elements/network/ops-view-redirects.js +38 -21
- package/dist_ts_web/elements/network/ops-view-remoteingress.js +3 -3
- package/dist_ts_web/elements/network/ops-view-routes.d.ts +8 -7
- package/dist_ts_web/elements/network/ops-view-routes.js +331 -161
- package/dist_ts_web/elements/network/ops-view-special-forwards.d.ts +4 -1
- package/dist_ts_web/elements/network/ops-view-special-forwards.js +118 -168
- package/dist_ts_web/elements/network/ops-view-vpn.js +3 -3
- package/dist_ts_web/elements/network/route-view-filters.d.ts +6 -0
- package/dist_ts_web/elements/network/route-view-filters.js +29 -0
- package/dist_ts_web/elements/network/special-forward-dialog.d.ts +11 -0
- package/dist_ts_web/elements/network/special-forward-dialog.js +154 -0
- package/dist_ts_web/elements/security/ops-view-security-blocked.js +6 -6
- package/package.json +2 -2
- package/readme.md +1 -1
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/config/helpers.http-redirects.ts +6 -3
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/elements/access/ops-view-apitokens.ts +2 -2
- package/ts_web/elements/access/ops-view-gatewayclients.ts +2 -2
- package/ts_web/elements/network/ops-view-network-activity.ts +5 -5
- package/ts_web/elements/network/ops-view-redirects.ts +34 -19
- package/ts_web/elements/network/ops-view-remoteingress.ts +2 -2
- package/ts_web/elements/network/ops-view-routes.ts +371 -171
- package/ts_web/elements/network/ops-view-special-forwards.ts +115 -178
- package/ts_web/elements/network/ops-view-vpn.ts +2 -2
- package/ts_web/elements/network/route-view-filters.ts +43 -0
- package/ts_web/elements/network/special-forward-dialog.ts +174 -0
- package/ts_web/elements/security/ops-view-security-blocked.ts +5 -5
- package/ts_web/readme.md +4 -4
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
import * as appstate from '../../appstate.js';
|
|
2
2
|
import * as interfaces from '../../../dist_ts_interfaces/index.js';
|
|
3
3
|
import { viewHostCss } from '../shared/css.js';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
type DeesInputMultitoggle,
|
|
6
|
+
type IStatsTile,
|
|
7
|
+
} from '@design.estate/dees-catalog';
|
|
5
8
|
import './ops-view-redirects.js';
|
|
6
9
|
import './ops-view-special-forwards.js';
|
|
10
|
+
import {
|
|
11
|
+
getRouteOwner,
|
|
12
|
+
redirectMatchesOwner,
|
|
13
|
+
routeMatchesOwner,
|
|
14
|
+
type TRouteOwnerFilter,
|
|
15
|
+
} from './route-view-filters.js';
|
|
16
|
+
import {
|
|
17
|
+
letsEncryptHttp01ManagedRouteKind,
|
|
18
|
+
showLetsEncryptHttp01ForwardDialog,
|
|
19
|
+
} from './special-forward-dialog.js';
|
|
7
20
|
import type {
|
|
8
21
|
IRoutePathClassOption as ISzRoutePathClassOption,
|
|
9
22
|
IRouteSourcePolicyPreset as ISzRouteSourcePolicyPreset,
|
|
@@ -21,9 +34,10 @@ import {
|
|
|
21
34
|
type TemplateResult,
|
|
22
35
|
} from '@design.estate/dees-element';
|
|
23
36
|
|
|
24
|
-
type TRoutesSection = '
|
|
25
|
-
|
|
26
|
-
|
|
37
|
+
type TRoutesSection = 'all' | 'standard' | 'redirects' | 'special';
|
|
38
|
+
|
|
39
|
+
const routeTypeOptions = ['All', 'Standard', 'Redirect', 'Special'];
|
|
40
|
+
const routeOwnerOptions = ['All', 'User', 'Gateway Client', 'System'];
|
|
27
41
|
|
|
28
42
|
// TLS dropdown options shared by create and edit dialogs
|
|
29
43
|
const tlsModeOptions = [
|
|
@@ -327,34 +341,6 @@ export class OpsViewRoutes extends DeesElement {
|
|
|
327
341
|
@state() accessor activeSection: TRoutesSection = 'standard';
|
|
328
342
|
@state() accessor ownerFilter: TRouteOwnerFilter = 'all';
|
|
329
343
|
|
|
330
|
-
private readonly sectionTabs: IMenuItem[] = [
|
|
331
|
-
{
|
|
332
|
-
key: 'Standard Routes',
|
|
333
|
-
iconName: 'lucide:route',
|
|
334
|
-
action: () => this.setRouteView('standard'),
|
|
335
|
-
},
|
|
336
|
-
{
|
|
337
|
-
key: 'Special Forwards',
|
|
338
|
-
iconName: 'lucide:split',
|
|
339
|
-
action: () => this.setRouteView('special'),
|
|
340
|
-
},
|
|
341
|
-
{
|
|
342
|
-
key: 'Redirects',
|
|
343
|
-
iconName: 'lucide:cornerDownRight',
|
|
344
|
-
action: () => this.setRouteView('redirects'),
|
|
345
|
-
},
|
|
346
|
-
];
|
|
347
|
-
|
|
348
|
-
private readonly ownerTabs: IMenuItem[] = [
|
|
349
|
-
{ key: 'All', action: () => this.setRouteOwnerFilter('all') },
|
|
350
|
-
{ key: 'Operator', action: () => this.setRouteOwnerFilter('operator') },
|
|
351
|
-
{
|
|
352
|
-
key: 'Gateway Client',
|
|
353
|
-
action: () => this.setRouteOwnerFilter('gatewayClient'),
|
|
354
|
-
},
|
|
355
|
-
{ key: 'System', action: () => this.setRouteOwnerFilter('system') },
|
|
356
|
-
];
|
|
357
|
-
|
|
358
344
|
private readonly popStateHandler = () => {
|
|
359
345
|
const urlState = this.readUrlState();
|
|
360
346
|
this.activeSection = urlState.section;
|
|
@@ -406,8 +392,7 @@ export class OpsViewRoutes extends DeesElement {
|
|
|
406
392
|
.select((s) => s.isLoggedIn)
|
|
407
393
|
.subscribe((isLoggedIn) => {
|
|
408
394
|
if (isLoggedIn) {
|
|
409
|
-
|
|
410
|
-
appstate.profilesTargetsStatePart.dispatchAction(appstate.fetchProfilesAndTargetsAction, null);
|
|
395
|
+
void this.refreshData();
|
|
411
396
|
}
|
|
412
397
|
});
|
|
413
398
|
this.rxSubscriptions.push(loginSub);
|
|
@@ -433,6 +418,31 @@ export class OpsViewRoutes extends DeesElement {
|
|
|
433
418
|
gap: 24px;
|
|
434
419
|
}
|
|
435
420
|
|
|
421
|
+
.routeFilters {
|
|
422
|
+
display: flex;
|
|
423
|
+
align-items: flex-end;
|
|
424
|
+
gap: 16px;
|
|
425
|
+
flex-wrap: nowrap;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
.routeFilter {
|
|
429
|
+
flex: 0 1 auto;
|
|
430
|
+
min-width: 0;
|
|
431
|
+
max-width: 100%;
|
|
432
|
+
overflow-x: auto;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
@media (max-width: 760px) {
|
|
436
|
+
.routeFilters {
|
|
437
|
+
flex-direction: column;
|
|
438
|
+
align-items: stretch;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
.routeFilter {
|
|
442
|
+
width: 100%;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
436
446
|
.warnings-bar {
|
|
437
447
|
background: ${cssManager.bdTheme('rgba(255, 170, 0, 0.08)', 'rgba(255, 170, 0, 0.1)')};
|
|
438
448
|
border: 1px solid ${cssManager.bdTheme('rgba(255, 170, 0, 0.25)', 'rgba(255, 170, 0, 0.3)')};
|
|
@@ -467,70 +477,25 @@ export class OpsViewRoutes extends DeesElement {
|
|
|
467
477
|
|
|
468
478
|
public render(): TemplateResult {
|
|
469
479
|
const { mergedRoutes, warnings } = this.routeState;
|
|
470
|
-
|
|
471
|
-
const disabledCount = mergedRoutes.filter((mr) => !mr.enabled).length;
|
|
472
|
-
const configCount = mergedRoutes.filter((mr) => mr.origin !== 'api').length;
|
|
473
|
-
const operatorCount = mergedRoutes.filter((mr) => {
|
|
474
|
-
return mr.origin === 'api'
|
|
475
|
-
&& mr.metadata?.ownerType !== 'gatewayClient'
|
|
476
|
-
&& !mr.metadata?.managedRouteKind;
|
|
477
|
-
}).length;
|
|
478
|
-
|
|
479
|
-
const statsTiles: IStatsTile[] = [
|
|
480
|
-
{
|
|
481
|
-
id: 'totalRoutes',
|
|
482
|
-
title: 'Total Routes',
|
|
483
|
-
type: 'number',
|
|
484
|
-
value: mergedRoutes.length,
|
|
485
|
-
icon: 'lucide:route',
|
|
486
|
-
description: 'All configured routes',
|
|
487
|
-
color: '#3b82f6',
|
|
488
|
-
},
|
|
489
|
-
{
|
|
490
|
-
id: 'configRoutes',
|
|
491
|
-
title: 'System Routes',
|
|
492
|
-
type: 'number',
|
|
493
|
-
value: configCount,
|
|
494
|
-
icon: 'lucide:settings',
|
|
495
|
-
description: 'From config, email, and DNS',
|
|
496
|
-
color: '#8b5cf6',
|
|
497
|
-
},
|
|
498
|
-
{
|
|
499
|
-
id: 'operatorRoutes',
|
|
500
|
-
title: 'Operator Routes',
|
|
501
|
-
type: 'number',
|
|
502
|
-
value: operatorCount,
|
|
503
|
-
icon: 'lucide:code',
|
|
504
|
-
description: 'Created directly by operators',
|
|
505
|
-
color: '#0ea5e9',
|
|
506
|
-
},
|
|
507
|
-
{
|
|
508
|
-
id: 'disabled',
|
|
509
|
-
title: 'Disabled',
|
|
510
|
-
type: 'number',
|
|
511
|
-
value: disabledCount,
|
|
512
|
-
icon: 'lucide:pauseCircle',
|
|
513
|
-
description: 'Currently disabled routes',
|
|
514
|
-
color: disabledCount > 0 ? '#ef4444' : '#6b7280',
|
|
515
|
-
},
|
|
516
|
-
];
|
|
517
|
-
|
|
518
480
|
const standardRoutes = mergedRoutes.filter((route) => !route.metadata?.managedRouteKind);
|
|
519
|
-
const
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
return true;
|
|
529
|
-
}
|
|
481
|
+
const filteredStandardRoutes = standardRoutes.filter((route) => {
|
|
482
|
+
return routeMatchesOwner(route, this.ownerFilter);
|
|
483
|
+
});
|
|
484
|
+
const filteredSpecialRoutes = mergedRoutes.filter((route) => {
|
|
485
|
+
return route.metadata?.managedRouteKind === letsEncryptHttp01ManagedRouteKind
|
|
486
|
+
&& routeMatchesOwner(route, this.ownerFilter);
|
|
487
|
+
});
|
|
488
|
+
const filteredRedirects = (this.routeState.httpRedirects || []).filter((redirect) => {
|
|
489
|
+
return redirectMatchesOwner(redirect, mergedRoutes, this.ownerFilter);
|
|
530
490
|
});
|
|
491
|
+
const statsTiles = this.getStatsTiles(
|
|
492
|
+
filteredStandardRoutes,
|
|
493
|
+
filteredSpecialRoutes,
|
|
494
|
+
filteredRedirects,
|
|
495
|
+
);
|
|
531
496
|
|
|
532
497
|
// Map filtered routes to sz-route-list-view format
|
|
533
|
-
const szRoutes =
|
|
498
|
+
const szRoutes = filteredStandardRoutes.map((mr) => {
|
|
534
499
|
const tags = [...(mr.route.tags || [])];
|
|
535
500
|
tags.push(mr.origin);
|
|
536
501
|
if (!mr.enabled) tags.push('disabled');
|
|
@@ -549,73 +514,256 @@ export class OpsViewRoutes extends DeesElement {
|
|
|
549
514
|
<dees-heading level="3">Route Management</dees-heading>
|
|
550
515
|
|
|
551
516
|
<div class="routesContainer">
|
|
552
|
-
<
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
517
|
+
<div class="routeFilters">
|
|
518
|
+
<div class="routeFilter">
|
|
519
|
+
<dees-input-multitoggle
|
|
520
|
+
id="routeTypeFilter"
|
|
521
|
+
.label=${'Type'}
|
|
522
|
+
.options=${routeTypeOptions}
|
|
523
|
+
.value=${this.getSelectedRouteTypeOption()}
|
|
524
|
+
></dees-input-multitoggle>
|
|
525
|
+
</div>
|
|
526
|
+
<div class="routeFilter">
|
|
527
|
+
<dees-input-multitoggle
|
|
528
|
+
id="routeOwnerFilter"
|
|
529
|
+
.label=${'Owner'}
|
|
530
|
+
.options=${routeOwnerOptions}
|
|
531
|
+
.value=${this.getSelectedOwnerOption()}
|
|
532
|
+
></dees-input-multitoggle>
|
|
533
|
+
</div>
|
|
534
|
+
</div>
|
|
535
|
+
|
|
562
536
|
<dees-statsgrid
|
|
537
|
+
id="routeStats"
|
|
563
538
|
.tiles=${statsTiles}
|
|
564
|
-
.gridActions=${
|
|
565
|
-
{
|
|
566
|
-
name: 'Add Route',
|
|
567
|
-
iconName: 'lucide:plus',
|
|
568
|
-
action: () => this.showCreateRouteDialog(),
|
|
569
|
-
},
|
|
570
|
-
{
|
|
571
|
-
name: 'Refresh',
|
|
572
|
-
iconName: 'lucide:refreshCw',
|
|
573
|
-
action: () => this.refreshData(),
|
|
574
|
-
},
|
|
575
|
-
]}
|
|
539
|
+
.gridActions=${this.getGridActions()}
|
|
576
540
|
></dees-statsgrid>
|
|
577
541
|
|
|
578
|
-
|
|
579
|
-
.tabs=${this.ownerTabs}
|
|
580
|
-
.selectedTab=${this.getSelectedOwnerTab()}
|
|
581
|
-
></dees-appui-tabs>
|
|
582
|
-
|
|
583
|
-
${warnings.length > 0
|
|
542
|
+
${this.activeSection === 'all' || this.activeSection === 'standard'
|
|
584
543
|
? html`
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
544
|
+
${this.activeSection === 'all'
|
|
545
|
+
? html`<dees-heading level="4">Standard Routes</dees-heading>`
|
|
546
|
+
: ''}
|
|
547
|
+
|
|
548
|
+
${warnings.length > 0
|
|
549
|
+
? html`
|
|
550
|
+
<div class="warnings-bar">
|
|
551
|
+
${warnings.map(
|
|
552
|
+
(warning) => html`
|
|
553
|
+
<div class="warning-item">
|
|
554
|
+
<span class="warning-icon">⚠</span>
|
|
555
|
+
<span>${warning.message}</span>
|
|
556
|
+
</div>
|
|
557
|
+
`,
|
|
558
|
+
)}
|
|
591
559
|
</div>
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
</div>
|
|
595
|
-
`
|
|
596
|
-
: ''}
|
|
560
|
+
`
|
|
561
|
+
: ''}
|
|
597
562
|
|
|
598
|
-
${szRoutes.length > 0
|
|
599
|
-
? html`
|
|
600
563
|
<sz-route-list-view
|
|
601
564
|
.routes=${szRoutes}
|
|
602
|
-
.showActionsFilter=${(route: { id?: string; name?: string }) =>
|
|
603
|
-
|
|
604
|
-
@route-
|
|
605
|
-
@route-
|
|
565
|
+
.showActionsFilter=${(route: { id?: string; name?: string }) =>
|
|
566
|
+
this.canEditRoute(route)}
|
|
567
|
+
@route-click=${(event: CustomEvent) => this.handleRouteClick(event)}
|
|
568
|
+
@route-edit=${(event: CustomEvent) => this.handleRouteEdit(event)}
|
|
569
|
+
@route-delete=${(event: CustomEvent) => this.handleRouteDelete(event)}
|
|
606
570
|
></sz-route-list-view>
|
|
571
|
+
${szRoutes.length === 0
|
|
572
|
+
? html`
|
|
573
|
+
<div class="empty-state">
|
|
574
|
+
<p>No standard routes match these filters</p>
|
|
575
|
+
<p>Choose another ownership filter or add a User Route.</p>
|
|
576
|
+
</div>
|
|
577
|
+
`
|
|
578
|
+
: ''}
|
|
579
|
+
`
|
|
580
|
+
: ''}
|
|
581
|
+
|
|
582
|
+
${this.activeSection === 'all' || this.activeSection === 'special'
|
|
583
|
+
? html`
|
|
584
|
+
<ops-view-special-forwards
|
|
585
|
+
.embedded=${true}
|
|
586
|
+
.ownerFilter=${this.ownerFilter}
|
|
587
|
+
></ops-view-special-forwards>
|
|
588
|
+
`
|
|
589
|
+
: ''}
|
|
590
|
+
|
|
591
|
+
${this.activeSection === 'all' || this.activeSection === 'redirects'
|
|
592
|
+
? html`
|
|
593
|
+
<ops-view-redirects
|
|
594
|
+
.embedded=${true}
|
|
595
|
+
.ownerFilter=${this.ownerFilter}
|
|
596
|
+
></ops-view-redirects>
|
|
607
597
|
`
|
|
608
|
-
:
|
|
609
|
-
<div class="empty-state">
|
|
610
|
-
<p>No ${this.getOwnerFilterLabel().toLowerCase()} routes</p>
|
|
611
|
-
<p>Choose another ownership filter or add an operator route.</p>
|
|
612
|
-
</div>
|
|
613
|
-
`}
|
|
614
|
-
`}
|
|
598
|
+
: ''}
|
|
615
599
|
</div>
|
|
616
600
|
`;
|
|
617
601
|
}
|
|
618
602
|
|
|
603
|
+
private getStatsTiles(
|
|
604
|
+
standardRoutes: interfaces.data.IMergedRoute[],
|
|
605
|
+
specialRoutes: interfaces.data.IMergedRoute[],
|
|
606
|
+
redirects: interfaces.data.IHttpRedirectInfo[],
|
|
607
|
+
): IStatsTile[] {
|
|
608
|
+
if (this.activeSection === 'all') {
|
|
609
|
+
const disabledCount = [...standardRoutes, ...specialRoutes]
|
|
610
|
+
.filter((route) => !route.enabled).length;
|
|
611
|
+
return [
|
|
612
|
+
{
|
|
613
|
+
id: 'allStandardRoutes',
|
|
614
|
+
title: 'Standard',
|
|
615
|
+
type: 'number',
|
|
616
|
+
value: standardRoutes.length,
|
|
617
|
+
icon: 'lucide:route',
|
|
618
|
+
description: 'Matching standard routes',
|
|
619
|
+
color: '#3b82f6',
|
|
620
|
+
},
|
|
621
|
+
{
|
|
622
|
+
id: 'allSpecialRoutes',
|
|
623
|
+
title: 'Special',
|
|
624
|
+
type: 'number',
|
|
625
|
+
value: specialRoutes.length,
|
|
626
|
+
icon: 'lucide:wandSparkles',
|
|
627
|
+
description: 'Matching managed special forwards',
|
|
628
|
+
color: '#8b5cf6',
|
|
629
|
+
},
|
|
630
|
+
{
|
|
631
|
+
id: 'allRedirects',
|
|
632
|
+
title: 'Redirects',
|
|
633
|
+
type: 'number',
|
|
634
|
+
value: redirects.length,
|
|
635
|
+
icon: 'lucide:cornerDownRight',
|
|
636
|
+
description: 'Matching derived redirects',
|
|
637
|
+
color: '#0ea5e9',
|
|
638
|
+
},
|
|
639
|
+
{
|
|
640
|
+
id: 'allDisabledRoutes',
|
|
641
|
+
title: 'Disabled',
|
|
642
|
+
type: 'number',
|
|
643
|
+
value: disabledCount,
|
|
644
|
+
icon: 'lucide:circlePause',
|
|
645
|
+
description: 'Disabled Standard and Special routes',
|
|
646
|
+
color: disabledCount > 0 ? '#ef4444' : '#6b7280',
|
|
647
|
+
},
|
|
648
|
+
];
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
if (this.activeSection === 'redirects') {
|
|
652
|
+
const activeCount = redirects.filter((redirect) => redirect.status === 'active').length;
|
|
653
|
+
const coveredCount = redirects.filter((redirect) => redirect.status === 'covered').length;
|
|
654
|
+
const skippedCount = redirects.filter((redirect) => redirect.status === 'skipped').length;
|
|
655
|
+
return [
|
|
656
|
+
{
|
|
657
|
+
id: 'matchingRedirects',
|
|
658
|
+
title: 'Matching',
|
|
659
|
+
type: 'number',
|
|
660
|
+
value: redirects.length,
|
|
661
|
+
icon: 'lucide:cornerDownRight',
|
|
662
|
+
description: 'Redirects in this owner scope',
|
|
663
|
+
color: '#3b82f6',
|
|
664
|
+
},
|
|
665
|
+
{
|
|
666
|
+
id: 'activeRedirects',
|
|
667
|
+
title: 'Active',
|
|
668
|
+
type: 'number',
|
|
669
|
+
value: activeCount,
|
|
670
|
+
icon: 'lucide:circleCheck',
|
|
671
|
+
description: 'Generated at runtime',
|
|
672
|
+
color: '#22c55e',
|
|
673
|
+
},
|
|
674
|
+
{
|
|
675
|
+
id: 'coveredRedirects',
|
|
676
|
+
title: 'Covered',
|
|
677
|
+
type: 'number',
|
|
678
|
+
value: coveredCount,
|
|
679
|
+
icon: 'lucide:shieldCheck',
|
|
680
|
+
description: 'Handled by explicit HTTP routes',
|
|
681
|
+
color: '#8b5cf6',
|
|
682
|
+
},
|
|
683
|
+
{
|
|
684
|
+
id: 'skippedRedirects',
|
|
685
|
+
title: 'Skipped',
|
|
686
|
+
type: 'number',
|
|
687
|
+
value: skippedCount,
|
|
688
|
+
icon: 'lucide:triangleAlert',
|
|
689
|
+
description: 'Overlaps explicit HTTP routes',
|
|
690
|
+
color: skippedCount > 0 ? '#f59e0b' : '#6b7280',
|
|
691
|
+
},
|
|
692
|
+
];
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
const routes = this.activeSection === 'special' ? specialRoutes : standardRoutes;
|
|
696
|
+
const enabledCount = routes.filter((route) => route.enabled).length;
|
|
697
|
+
const disabledCount = routes.length - enabledCount;
|
|
698
|
+
const readOnlyCount = routes.filter((route) => getRouteOwner(route) !== 'operator').length;
|
|
699
|
+
const scopeName = this.activeSection === 'special' ? 'Special' : 'Standard';
|
|
700
|
+
return [
|
|
701
|
+
{
|
|
702
|
+
id: `matching${scopeName}Routes`,
|
|
703
|
+
title: 'Matching',
|
|
704
|
+
type: 'number',
|
|
705
|
+
value: routes.length,
|
|
706
|
+
icon: 'lucide:route',
|
|
707
|
+
description: `${scopeName} routes in this owner scope`,
|
|
708
|
+
color: '#3b82f6',
|
|
709
|
+
},
|
|
710
|
+
{
|
|
711
|
+
id: `enabled${scopeName}Routes`,
|
|
712
|
+
title: 'Enabled',
|
|
713
|
+
type: 'number',
|
|
714
|
+
value: enabledCount,
|
|
715
|
+
icon: 'lucide:circleCheck',
|
|
716
|
+
description: 'Currently active',
|
|
717
|
+
color: '#22c55e',
|
|
718
|
+
},
|
|
719
|
+
{
|
|
720
|
+
id: `disabled${scopeName}Routes`,
|
|
721
|
+
title: 'Disabled',
|
|
722
|
+
type: 'number',
|
|
723
|
+
value: disabledCount,
|
|
724
|
+
icon: 'lucide:circlePause',
|
|
725
|
+
description: 'Configured but inactive',
|
|
726
|
+
color: disabledCount > 0 ? '#f59e0b' : '#6b7280',
|
|
727
|
+
},
|
|
728
|
+
{
|
|
729
|
+
id: `readOnly${scopeName}Routes`,
|
|
730
|
+
title: 'Read-only',
|
|
731
|
+
type: 'number',
|
|
732
|
+
value: readOnlyCount,
|
|
733
|
+
icon: 'lucide:lockKeyhole',
|
|
734
|
+
description: 'Managed by gateway clients or system',
|
|
735
|
+
color: '#8b5cf6',
|
|
736
|
+
},
|
|
737
|
+
];
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
private getGridActions() {
|
|
741
|
+
const canCreateUserRoutes = this.ownerFilter === 'all' || this.ownerFilter === 'operator';
|
|
742
|
+
return [
|
|
743
|
+
...(canCreateUserRoutes && (this.activeSection === 'standard' || this.activeSection === 'all')
|
|
744
|
+
? [{
|
|
745
|
+
name: 'Add User Route',
|
|
746
|
+
iconName: 'lucide:plus',
|
|
747
|
+
action: () => this.showCreateRouteDialog(),
|
|
748
|
+
}]
|
|
749
|
+
: []),
|
|
750
|
+
...(canCreateUserRoutes && (this.activeSection === 'special' || this.activeSection === 'all')
|
|
751
|
+
? [{
|
|
752
|
+
name: 'Add User Special Forward',
|
|
753
|
+
iconName: 'lucide:wandSparkles',
|
|
754
|
+
action: () => showLetsEncryptHttp01ForwardDialog({
|
|
755
|
+
targets: this.profilesTargetsState.targets,
|
|
756
|
+
}),
|
|
757
|
+
}]
|
|
758
|
+
: []),
|
|
759
|
+
{
|
|
760
|
+
name: 'Refresh',
|
|
761
|
+
iconName: 'lucide:refreshCw',
|
|
762
|
+
action: () => this.refreshData(),
|
|
763
|
+
},
|
|
764
|
+
];
|
|
765
|
+
}
|
|
766
|
+
|
|
619
767
|
private async handleRouteClick(e: CustomEvent) {
|
|
620
768
|
const clickedRoute = e.detail;
|
|
621
769
|
if (!clickedRoute) return;
|
|
@@ -953,7 +1101,7 @@ export class OpsViewRoutes extends DeesElement {
|
|
|
953
1101
|
];
|
|
954
1102
|
|
|
955
1103
|
const createModal = await DeesModal.createAndShow({
|
|
956
|
-
heading: 'Add Route',
|
|
1104
|
+
heading: 'Add User Route',
|
|
957
1105
|
content: html`
|
|
958
1106
|
<dees-form>
|
|
959
1107
|
<dees-input-text .key=${'name'} .label=${'Route Name'} .required=${true}></dees-input-text>
|
|
@@ -1103,32 +1251,63 @@ export class OpsViewRoutes extends DeesElement {
|
|
|
1103
1251
|
}
|
|
1104
1252
|
}
|
|
1105
1253
|
|
|
1106
|
-
private refreshData() {
|
|
1107
|
-
|
|
1254
|
+
private async refreshData(): Promise<void> {
|
|
1255
|
+
await Promise.all([
|
|
1256
|
+
appstate.routeManagementStatePart.dispatchAction(appstate.fetchMergedRoutesAction, null),
|
|
1257
|
+
appstate.routeManagementStatePart.dispatchAction(appstate.fetchHttpRedirectsAction, null),
|
|
1258
|
+
appstate.profilesTargetsStatePart.dispatchAction(
|
|
1259
|
+
appstate.fetchProfilesAndTargetsAction,
|
|
1260
|
+
null,
|
|
1261
|
+
),
|
|
1262
|
+
]);
|
|
1108
1263
|
}
|
|
1109
1264
|
|
|
1110
|
-
private
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1265
|
+
private getSelectedRouteTypeOption(): string {
|
|
1266
|
+
switch (this.activeSection) {
|
|
1267
|
+
case 'all':
|
|
1268
|
+
return 'All';
|
|
1269
|
+
case 'redirects':
|
|
1270
|
+
return 'Redirect';
|
|
1271
|
+
case 'special':
|
|
1272
|
+
return 'Special';
|
|
1273
|
+
default:
|
|
1274
|
+
return 'Standard';
|
|
1275
|
+
}
|
|
1117
1276
|
}
|
|
1118
1277
|
|
|
1119
|
-
private
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1278
|
+
private getSelectedOwnerOption(): string {
|
|
1279
|
+
switch (this.ownerFilter) {
|
|
1280
|
+
case 'operator':
|
|
1281
|
+
return 'User';
|
|
1282
|
+
case 'gatewayClient':
|
|
1283
|
+
return 'Gateway Client';
|
|
1284
|
+
case 'system':
|
|
1285
|
+
return 'System';
|
|
1286
|
+
default:
|
|
1287
|
+
return 'All';
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
private handleRouteTypeChange(toggle: DeesInputMultitoggle): void {
|
|
1292
|
+
const section: TRoutesSection = toggle.selectedOption === 'All'
|
|
1293
|
+
? 'all'
|
|
1294
|
+
: toggle.selectedOption === 'Redirect'
|
|
1295
|
+
? 'redirects'
|
|
1296
|
+
: toggle.selectedOption === 'Special'
|
|
1297
|
+
? 'special'
|
|
1298
|
+
: 'standard';
|
|
1299
|
+
this.setRouteView(section);
|
|
1128
1300
|
}
|
|
1129
1301
|
|
|
1130
|
-
private
|
|
1131
|
-
|
|
1302
|
+
private handleRouteOwnerChange(toggle: DeesInputMultitoggle): void {
|
|
1303
|
+
const owner: TRouteOwnerFilter = toggle.selectedOption === 'User'
|
|
1304
|
+
? 'operator'
|
|
1305
|
+
: toggle.selectedOption === 'Gateway Client'
|
|
1306
|
+
? 'gatewayClient'
|
|
1307
|
+
: toggle.selectedOption === 'System'
|
|
1308
|
+
? 'system'
|
|
1309
|
+
: 'all';
|
|
1310
|
+
this.setRouteOwnerFilter(owner);
|
|
1132
1311
|
}
|
|
1133
1312
|
|
|
1134
1313
|
private setRouteView(section: TRoutesSection): void {
|
|
@@ -1147,7 +1326,11 @@ export class OpsViewRoutes extends DeesElement {
|
|
|
1147
1326
|
const params = new URL(window.location.href).searchParams;
|
|
1148
1327
|
const tabParam = params.get('tab');
|
|
1149
1328
|
const ownerParam = params.get('owner');
|
|
1150
|
-
const section: TRoutesSection =
|
|
1329
|
+
const section: TRoutesSection = (
|
|
1330
|
+
tabParam === 'all'
|
|
1331
|
+
|| tabParam === 'special'
|
|
1332
|
+
|| tabParam === 'redirects'
|
|
1333
|
+
)
|
|
1151
1334
|
? tabParam
|
|
1152
1335
|
: 'standard';
|
|
1153
1336
|
const owner: TRouteOwnerFilter = (
|
|
@@ -1214,12 +1397,29 @@ export class OpsViewRoutes extends DeesElement {
|
|
|
1214
1397
|
}
|
|
1215
1398
|
|
|
1216
1399
|
private isSystemManagedRoute(merged: interfaces.data.IMergedRoute): boolean {
|
|
1217
|
-
return merged
|
|
1218
|
-
|| merged.metadata?.ownerType === 'gatewayClient'
|
|
1400
|
+
return getRouteOwner(merged) !== 'operator'
|
|
1219
1401
|
|| Boolean(merged.metadata?.managedRouteKind);
|
|
1220
1402
|
}
|
|
1221
1403
|
|
|
1222
1404
|
async firstUpdated() {
|
|
1223
|
-
|
|
1405
|
+
const typeToggle = this.shadowRoot?.querySelector(
|
|
1406
|
+
'#routeTypeFilter',
|
|
1407
|
+
) as DeesInputMultitoggle | null;
|
|
1408
|
+
const ownerToggle = this.shadowRoot?.querySelector(
|
|
1409
|
+
'#routeOwnerFilter',
|
|
1410
|
+
) as DeesInputMultitoggle | null;
|
|
1411
|
+
|
|
1412
|
+
if (typeToggle) {
|
|
1413
|
+
this.rxSubscriptions.push(typeToggle.changeSubject.subscribe((toggle) => {
|
|
1414
|
+
this.handleRouteTypeChange(toggle);
|
|
1415
|
+
}));
|
|
1416
|
+
}
|
|
1417
|
+
if (ownerToggle) {
|
|
1418
|
+
this.rxSubscriptions.push(ownerToggle.changeSubject.subscribe((toggle) => {
|
|
1419
|
+
this.handleRouteOwnerChange(toggle);
|
|
1420
|
+
}));
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
await this.refreshData();
|
|
1224
1424
|
}
|
|
1225
1425
|
}
|