@serve.zone/dcrouter 13.4.2 → 13.7.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/dist_serve/bundle.js +1779 -1375
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.dcrouter.d.ts +2 -5
- package/dist_ts/classes.dcrouter.js +41 -10
- package/dist_ts/db/documents/classes.dns-provider.doc.d.ts +22 -0
- package/dist_ts/db/documents/classes.dns-provider.doc.js +134 -0
- package/dist_ts/db/documents/classes.dns-record.doc.d.ts +21 -0
- package/dist_ts/db/documents/classes.dns-record.doc.js +143 -0
- package/dist_ts/db/documents/classes.domain.doc.d.ts +22 -0
- package/dist_ts/db/documents/classes.domain.doc.js +146 -0
- package/dist_ts/db/documents/index.d.ts +3 -0
- package/dist_ts/db/documents/index.js +5 -1
- package/dist_ts/dns/index.d.ts +2 -0
- package/dist_ts/dns/index.js +3 -0
- package/dist_ts/dns/manager.dns.d.ts +227 -0
- package/dist_ts/dns/manager.dns.js +747 -0
- package/dist_ts/dns/providers/cloudflare.provider.d.ts +21 -0
- package/dist_ts/dns/providers/cloudflare.provider.js +106 -0
- package/dist_ts/dns/providers/factory.d.ts +23 -0
- package/dist_ts/dns/providers/factory.js +38 -0
- package/dist_ts/dns/providers/index.d.ts +3 -0
- package/dist_ts/dns/providers/index.js +4 -0
- package/dist_ts/dns/providers/interfaces.d.ts +54 -0
- package/dist_ts/dns/providers/interfaces.js +2 -0
- package/dist_ts/opsserver/classes.opsserver.d.ts +4 -0
- package/dist_ts/opsserver/classes.opsserver.js +9 -1
- package/dist_ts/opsserver/handlers/admin.handler.d.ts +9 -0
- package/dist_ts/opsserver/handlers/admin.handler.js +12 -1
- package/dist_ts/opsserver/handlers/config.handler.js +11 -2
- package/dist_ts/opsserver/handlers/dns-provider.handler.d.ts +16 -0
- package/dist_ts/opsserver/handlers/dns-provider.handler.js +119 -0
- package/dist_ts/opsserver/handlers/dns-record.handler.d.ts +13 -0
- package/dist_ts/opsserver/handlers/dns-record.handler.js +98 -0
- package/dist_ts/opsserver/handlers/domain.handler.d.ts +13 -0
- package/dist_ts/opsserver/handlers/domain.handler.js +124 -0
- package/dist_ts/opsserver/handlers/index.d.ts +4 -0
- package/dist_ts/opsserver/handlers/index.js +5 -1
- package/dist_ts/opsserver/handlers/users.handler.d.ts +12 -0
- package/dist_ts/opsserver/handlers/users.handler.js +24 -0
- package/dist_ts_interfaces/data/dns-provider.d.ts +112 -0
- package/dist_ts_interfaces/data/dns-provider.js +27 -0
- package/dist_ts_interfaces/data/dns-record.d.ts +40 -0
- package/dist_ts_interfaces/data/dns-record.js +2 -0
- package/dist_ts_interfaces/data/domain.d.ts +34 -0
- package/dist_ts_interfaces/data/domain.js +2 -0
- package/dist_ts_interfaces/data/index.d.ts +3 -0
- package/dist_ts_interfaces/data/index.js +4 -1
- package/dist_ts_interfaces/data/route-management.d.ts +1 -1
- package/dist_ts_interfaces/requests/dns-providers.d.ts +117 -0
- package/dist_ts_interfaces/requests/dns-providers.js +2 -0
- package/dist_ts_interfaces/requests/dns-records.d.ts +89 -0
- package/dist_ts_interfaces/requests/dns-records.js +2 -0
- package/dist_ts_interfaces/requests/domains.d.ts +118 -0
- package/dist_ts_interfaces/requests/domains.js +2 -0
- package/dist_ts_interfaces/requests/index.d.ts +4 -0
- package/dist_ts_interfaces/requests/index.js +5 -1
- package/dist_ts_interfaces/requests/users.d.ts +19 -0
- package/dist_ts_interfaces/requests/users.js +3 -0
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/appstate.d.ts +85 -0
- package/dist_ts_web/appstate.js +339 -6
- package/dist_ts_web/elements/access/index.d.ts +1 -0
- package/dist_ts_web/elements/access/index.js +2 -1
- package/dist_ts_web/elements/access/ops-view-apitokens.js +1 -1
- package/dist_ts_web/elements/access/ops-view-users.d.ts +11 -0
- package/dist_ts_web/elements/access/ops-view-users.js +190 -0
- package/dist_ts_web/elements/domains/dns-provider-form.d.ts +58 -0
- package/dist_ts_web/elements/domains/dns-provider-form.js +268 -0
- package/dist_ts_web/elements/domains/index.d.ts +5 -0
- package/dist_ts_web/elements/domains/index.js +6 -0
- package/dist_ts_web/elements/{ops-view-certificates.d.ts → domains/ops-view-certificates.d.ts} +1 -1
- package/dist_ts_web/elements/{ops-view-certificates.js → domains/ops-view-certificates.js} +5 -5
- package/dist_ts_web/elements/domains/ops-view-dns.d.ts +17 -0
- package/dist_ts_web/elements/domains/ops-view-dns.js +304 -0
- package/dist_ts_web/elements/domains/ops-view-domains.d.ts +18 -0
- package/dist_ts_web/elements/domains/ops-view-domains.js +361 -0
- package/dist_ts_web/elements/domains/ops-view-providers.d.ts +21 -0
- package/dist_ts_web/elements/domains/ops-view-providers.js +316 -0
- package/dist_ts_web/elements/email/ops-view-email-security.js +1 -1
- package/dist_ts_web/elements/email/ops-view-emails.js +1 -1
- package/dist_ts_web/elements/index.d.ts +1 -1
- package/dist_ts_web/elements/index.js +2 -2
- package/dist_ts_web/elements/network/ops-view-network-activity.js +6 -2
- package/dist_ts_web/elements/network/ops-view-networktargets.js +1 -1
- package/dist_ts_web/elements/network/ops-view-remoteingress.js +1 -1
- package/dist_ts_web/elements/network/ops-view-routes.js +1 -1
- package/dist_ts_web/elements/network/ops-view-sourceprofiles.js +1 -1
- package/dist_ts_web/elements/network/ops-view-targetprofiles.js +1 -1
- package/dist_ts_web/elements/network/ops-view-vpn.js +1 -1
- package/dist_ts_web/elements/ops-dashboard.js +16 -5
- package/dist_ts_web/elements/ops-view-logs.js +1 -1
- package/dist_ts_web/elements/overview/ops-view-config.js +3 -3
- package/dist_ts_web/elements/overview/ops-view-overview.js +1 -1
- package/dist_ts_web/elements/security/ops-view-security-authentication.js +1 -1
- package/dist_ts_web/elements/security/ops-view-security-blocked.js +1 -1
- package/dist_ts_web/elements/security/ops-view-security-overview.js +1 -1
- package/dist_ts_web/router.d.ts +1 -1
- package/dist_ts_web/router.js +5 -3
- package/package.json +2 -2
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.dcrouter.ts +46 -17
- package/ts/db/documents/classes.dns-provider.doc.ts +63 -0
- package/ts/db/documents/classes.dns-record.doc.ts +62 -0
- package/ts/db/documents/classes.domain.doc.ts +66 -0
- package/ts/db/documents/index.ts +5 -0
- package/ts/dns/index.ts +2 -0
- package/ts/dns/manager.dns.ts +869 -0
- package/ts/dns/providers/cloudflare.provider.ts +131 -0
- package/ts/dns/providers/factory.ts +48 -0
- package/ts/dns/providers/index.ts +3 -0
- package/ts/dns/providers/interfaces.ts +67 -0
- package/ts/opsserver/classes.opsserver.ts +8 -0
- package/ts/opsserver/handlers/admin.handler.ts +12 -0
- package/ts/opsserver/handlers/config.handler.ts +10 -1
- package/ts/opsserver/handlers/dns-provider.handler.ts +159 -0
- package/ts/opsserver/handlers/dns-record.handler.ts +127 -0
- package/ts/opsserver/handlers/domain.handler.ts +161 -0
- package/ts/opsserver/handlers/index.ts +5 -1
- package/ts/opsserver/handlers/users.handler.ts +30 -0
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/appstate.ts +460 -5
- package/ts_web/elements/access/index.ts +1 -0
- package/ts_web/elements/access/ops-view-apitokens.ts +1 -1
- package/ts_web/elements/access/ops-view-users.ts +140 -0
- package/ts_web/elements/domains/dns-provider-form.ts +216 -0
- package/ts_web/elements/domains/index.ts +5 -0
- package/ts_web/elements/{ops-view-certificates.ts → domains/ops-view-certificates.ts} +4 -4
- package/ts_web/elements/domains/ops-view-dns.ts +273 -0
- package/ts_web/elements/domains/ops-view-domains.ts +335 -0
- package/ts_web/elements/domains/ops-view-providers.ts +284 -0
- package/ts_web/elements/email/ops-view-email-security.ts +1 -1
- package/ts_web/elements/email/ops-view-emails.ts +1 -1
- package/ts_web/elements/index.ts +1 -1
- package/ts_web/elements/network/ops-view-network-activity.ts +5 -1
- package/ts_web/elements/network/ops-view-networktargets.ts +1 -1
- package/ts_web/elements/network/ops-view-remoteingress.ts +1 -1
- package/ts_web/elements/network/ops-view-routes.ts +1 -1
- package/ts_web/elements/network/ops-view-sourceprofiles.ts +1 -1
- package/ts_web/elements/network/ops-view-targetprofiles.ts +1 -1
- package/ts_web/elements/network/ops-view-vpn.ts +1 -1
- package/ts_web/elements/ops-dashboard.ts +16 -4
- package/ts_web/elements/ops-view-logs.ts +1 -1
- package/ts_web/elements/overview/ops-view-config.ts +2 -2
- package/ts_web/elements/overview/ops-view-overview.ts +1 -1
- package/ts_web/elements/security/ops-view-security-authentication.ts +1 -1
- package/ts_web/elements/security/ops-view-security-blocked.ts +1 -1
- package/ts_web/elements/security/ops-view-security-overview.ts +1 -1
- package/ts_web/router.ts +4 -2
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DeesElement,
|
|
3
|
+
html,
|
|
4
|
+
customElement,
|
|
5
|
+
type TemplateResult,
|
|
6
|
+
css,
|
|
7
|
+
state,
|
|
8
|
+
cssManager,
|
|
9
|
+
} from '@design.estate/dees-element';
|
|
10
|
+
import * as appstate from '../../appstate.js';
|
|
11
|
+
import * as interfaces from '../../../dist_ts_interfaces/index.js';
|
|
12
|
+
import { viewHostCss } from '../shared/css.js';
|
|
13
|
+
import { appRouter } from '../../router.js';
|
|
14
|
+
|
|
15
|
+
declare global {
|
|
16
|
+
interface HTMLElementTagNameMap {
|
|
17
|
+
'ops-view-domains': OpsViewDomains;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@customElement('ops-view-domains')
|
|
22
|
+
export class OpsViewDomains extends DeesElement {
|
|
23
|
+
@state()
|
|
24
|
+
accessor domainsState: appstate.IDomainsState = appstate.domainsStatePart.getState()!;
|
|
25
|
+
|
|
26
|
+
constructor() {
|
|
27
|
+
super();
|
|
28
|
+
const sub = appstate.domainsStatePart.select().subscribe((newState) => {
|
|
29
|
+
this.domainsState = newState;
|
|
30
|
+
});
|
|
31
|
+
this.rxSubscriptions.push(sub);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async connectedCallback() {
|
|
35
|
+
await super.connectedCallback();
|
|
36
|
+
await appstate.domainsStatePart.dispatchAction(appstate.fetchDomainsAndProvidersAction, null);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
public static styles = [
|
|
40
|
+
cssManager.defaultStyles,
|
|
41
|
+
viewHostCss,
|
|
42
|
+
css`
|
|
43
|
+
.domainsContainer {
|
|
44
|
+
display: flex;
|
|
45
|
+
flex-direction: column;
|
|
46
|
+
gap: 24px;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.sourceBadge {
|
|
50
|
+
display: inline-flex;
|
|
51
|
+
align-items: center;
|
|
52
|
+
padding: 3px 8px;
|
|
53
|
+
border-radius: 4px;
|
|
54
|
+
font-size: 11px;
|
|
55
|
+
font-weight: 500;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.sourceBadge.manual {
|
|
59
|
+
background: ${cssManager.bdTheme('#e0e7ff', '#1e1b4b')};
|
|
60
|
+
color: ${cssManager.bdTheme('#3730a3', '#a5b4fc')};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.sourceBadge.provider {
|
|
64
|
+
background: ${cssManager.bdTheme('#fef3c7', '#451a03')};
|
|
65
|
+
color: ${cssManager.bdTheme('#92400e', '#fde047')};
|
|
66
|
+
}
|
|
67
|
+
`,
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
public render(): TemplateResult {
|
|
71
|
+
const domains = this.domainsState.domains;
|
|
72
|
+
const providersById = new Map(this.domainsState.providers.map((p) => [p.id, p]));
|
|
73
|
+
|
|
74
|
+
return html`
|
|
75
|
+
<dees-heading level="3">Domains</dees-heading>
|
|
76
|
+
<div class="domainsContainer">
|
|
77
|
+
<dees-table
|
|
78
|
+
.heading1=${'Domains'}
|
|
79
|
+
.heading2=${'Domains under management — manual (authoritative) or imported from a provider'}
|
|
80
|
+
.data=${domains}
|
|
81
|
+
.showColumnFilters=${true}
|
|
82
|
+
.displayFunction=${(d: interfaces.data.IDomain) => ({
|
|
83
|
+
Name: d.name,
|
|
84
|
+
Source: this.renderSourceBadge(d, providersById),
|
|
85
|
+
Authoritative: d.authoritative ? 'yes' : 'no',
|
|
86
|
+
Nameservers: d.nameservers?.join(', ') || '-',
|
|
87
|
+
'Last Synced': d.lastSyncedAt
|
|
88
|
+
? new Date(d.lastSyncedAt).toLocaleString()
|
|
89
|
+
: '-',
|
|
90
|
+
})}
|
|
91
|
+
.dataActions=${[
|
|
92
|
+
{
|
|
93
|
+
name: 'Add Manual Domain',
|
|
94
|
+
iconName: 'lucide:plus',
|
|
95
|
+
type: ['header' as const],
|
|
96
|
+
actionFunc: async () => {
|
|
97
|
+
await this.showCreateManualDialog();
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
name: 'Import from Provider',
|
|
102
|
+
iconName: 'lucide:download',
|
|
103
|
+
type: ['header' as const],
|
|
104
|
+
actionFunc: async () => {
|
|
105
|
+
await this.showImportDialog();
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
name: 'Refresh',
|
|
110
|
+
iconName: 'lucide:rotateCw',
|
|
111
|
+
type: ['header' as const],
|
|
112
|
+
actionFunc: async () => {
|
|
113
|
+
await appstate.domainsStatePart.dispatchAction(
|
|
114
|
+
appstate.fetchDomainsAndProvidersAction,
|
|
115
|
+
null,
|
|
116
|
+
);
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
name: 'View Records',
|
|
121
|
+
iconName: 'lucide:list',
|
|
122
|
+
type: ['inRow', 'contextmenu'] as any,
|
|
123
|
+
actionFunc: async (actionData: any) => {
|
|
124
|
+
const domain = actionData.item as interfaces.data.IDomain;
|
|
125
|
+
await appstate.domainsStatePart.dispatchAction(
|
|
126
|
+
appstate.fetchDnsRecordsForDomainAction,
|
|
127
|
+
{ domainId: domain.id },
|
|
128
|
+
);
|
|
129
|
+
appRouter.navigateToView('domains', 'dns');
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
name: 'Sync Now',
|
|
134
|
+
iconName: 'lucide:rotateCw',
|
|
135
|
+
type: ['inRow', 'contextmenu'] as any,
|
|
136
|
+
actionFunc: async (actionData: any) => {
|
|
137
|
+
const domain = actionData.item as interfaces.data.IDomain;
|
|
138
|
+
if (domain.source !== 'provider') {
|
|
139
|
+
const { DeesToast } = await import('@design.estate/dees-catalog');
|
|
140
|
+
DeesToast.show({
|
|
141
|
+
message: 'Sync only applies to provider-managed domains',
|
|
142
|
+
type: 'warning',
|
|
143
|
+
duration: 3000,
|
|
144
|
+
});
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
await appstate.domainsStatePart.dispatchAction(appstate.syncDomainAction, {
|
|
148
|
+
id: domain.id,
|
|
149
|
+
});
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
name: 'Delete',
|
|
154
|
+
iconName: 'lucide:trash2',
|
|
155
|
+
type: ['inRow', 'contextmenu'] as any,
|
|
156
|
+
actionFunc: async (actionData: any) => {
|
|
157
|
+
const domain = actionData.item as interfaces.data.IDomain;
|
|
158
|
+
await this.deleteDomain(domain);
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
]}
|
|
162
|
+
></dees-table>
|
|
163
|
+
</div>
|
|
164
|
+
`;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
private renderSourceBadge(
|
|
168
|
+
d: interfaces.data.IDomain,
|
|
169
|
+
providersById: Map<string, interfaces.data.IDnsProviderPublic>,
|
|
170
|
+
): TemplateResult {
|
|
171
|
+
if (d.source === 'manual') {
|
|
172
|
+
return html`<span class="sourceBadge manual">Manual</span>`;
|
|
173
|
+
}
|
|
174
|
+
const provider = d.providerId ? providersById.get(d.providerId) : undefined;
|
|
175
|
+
return html`<span class="sourceBadge provider">${provider?.name || 'Provider'}</span>`;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
private async showCreateManualDialog() {
|
|
179
|
+
const { DeesModal } = await import('@design.estate/dees-catalog');
|
|
180
|
+
DeesModal.createAndShow({
|
|
181
|
+
heading: 'Add Manual Domain',
|
|
182
|
+
content: html`
|
|
183
|
+
<dees-form>
|
|
184
|
+
<dees-input-text .key=${'name'} .label=${'FQDN (e.g. example.com)'} .required=${true}></dees-input-text>
|
|
185
|
+
<dees-input-text .key=${'description'} .label=${'Description (optional)'}></dees-input-text>
|
|
186
|
+
</dees-form>
|
|
187
|
+
<p style="margin-top: 12px; font-size: 12px; opacity: 0.7;">
|
|
188
|
+
dcrouter will become the authoritative DNS server for this domain. You'll need to
|
|
189
|
+
delegate the domain's nameservers to dcrouter to make this effective.
|
|
190
|
+
</p>
|
|
191
|
+
`,
|
|
192
|
+
menuOptions: [
|
|
193
|
+
{ name: 'Cancel', action: async (modalArg: any) => modalArg.destroy() },
|
|
194
|
+
{
|
|
195
|
+
name: 'Create',
|
|
196
|
+
action: async (modalArg: any) => {
|
|
197
|
+
const form = modalArg.shadowRoot
|
|
198
|
+
?.querySelector('.content')
|
|
199
|
+
?.querySelector('dees-form');
|
|
200
|
+
if (!form) return;
|
|
201
|
+
const data = await form.collectFormData();
|
|
202
|
+
await appstate.domainsStatePart.dispatchAction(appstate.createManualDomainAction, {
|
|
203
|
+
name: String(data.name),
|
|
204
|
+
description: data.description ? String(data.description) : undefined,
|
|
205
|
+
});
|
|
206
|
+
modalArg.destroy();
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
],
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
private async showImportDialog() {
|
|
214
|
+
const providers = this.domainsState.providers;
|
|
215
|
+
if (providers.length === 0) {
|
|
216
|
+
const { DeesToast } = await import('@design.estate/dees-catalog');
|
|
217
|
+
DeesToast.show({
|
|
218
|
+
message: 'Add a DNS provider first (Domains > Providers)',
|
|
219
|
+
type: 'warning',
|
|
220
|
+
duration: 3500,
|
|
221
|
+
});
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const { DeesModal, DeesToast } = await import('@design.estate/dees-catalog');
|
|
226
|
+
DeesModal.createAndShow({
|
|
227
|
+
heading: 'Import Domains from Provider',
|
|
228
|
+
content: html`
|
|
229
|
+
<dees-form>
|
|
230
|
+
<dees-input-dropdown
|
|
231
|
+
.key=${'providerId'}
|
|
232
|
+
.label=${'Provider'}
|
|
233
|
+
.options=${providers.map((p) => ({ option: p.name, key: p.id }))}
|
|
234
|
+
.required=${true}
|
|
235
|
+
></dees-input-dropdown>
|
|
236
|
+
<dees-input-text
|
|
237
|
+
.key=${'domainNames'}
|
|
238
|
+
.label=${'Comma-separated FQDNs to import (e.g. example.com, foo.com)'}
|
|
239
|
+
.required=${true}
|
|
240
|
+
></dees-input-text>
|
|
241
|
+
</dees-form>
|
|
242
|
+
<p style="margin-top: 12px; font-size: 12px; opacity: 0.7;">
|
|
243
|
+
Tip: use "List Provider Domains" to see what's available before typing.
|
|
244
|
+
</p>
|
|
245
|
+
`,
|
|
246
|
+
menuOptions: [
|
|
247
|
+
{ name: 'Cancel', action: async (modalArg: any) => modalArg.destroy() },
|
|
248
|
+
{
|
|
249
|
+
name: 'List Provider Domains',
|
|
250
|
+
action: async (_modalArg: any) => {
|
|
251
|
+
const form = _modalArg.shadowRoot
|
|
252
|
+
?.querySelector('.content')
|
|
253
|
+
?.querySelector('dees-form');
|
|
254
|
+
if (!form) return;
|
|
255
|
+
const data = await form.collectFormData();
|
|
256
|
+
const providerKey = data.providerId?.key ?? data.providerId;
|
|
257
|
+
if (!providerKey) {
|
|
258
|
+
DeesToast.show({ message: 'Pick a provider first', type: 'warning', duration: 2500 });
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
const result = await appstate.fetchProviderDomains(String(providerKey));
|
|
262
|
+
if (!result.success) {
|
|
263
|
+
DeesToast.show({
|
|
264
|
+
message: result.message || 'Failed to fetch domains',
|
|
265
|
+
type: 'error',
|
|
266
|
+
duration: 4000,
|
|
267
|
+
});
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
const list = (result.domains ?? []).map((d) => d.name).join(', ');
|
|
271
|
+
DeesToast.show({
|
|
272
|
+
message: `Provider has: ${list || '(none)'}`,
|
|
273
|
+
type: 'info',
|
|
274
|
+
duration: 8000,
|
|
275
|
+
});
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
name: 'Import',
|
|
280
|
+
action: async (modalArg: any) => {
|
|
281
|
+
const form = modalArg.shadowRoot
|
|
282
|
+
?.querySelector('.content')
|
|
283
|
+
?.querySelector('dees-form');
|
|
284
|
+
if (!form) return;
|
|
285
|
+
const data = await form.collectFormData();
|
|
286
|
+
const providerKey = data.providerId?.key ?? data.providerId;
|
|
287
|
+
if (!providerKey) {
|
|
288
|
+
DeesToast.show({ message: 'Pick a provider', type: 'warning', duration: 2500 });
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
const names = String(data.domainNames || '')
|
|
292
|
+
.split(',')
|
|
293
|
+
.map((s) => s.trim())
|
|
294
|
+
.filter(Boolean);
|
|
295
|
+
if (names.length === 0) {
|
|
296
|
+
DeesToast.show({ message: 'Enter at least one FQDN', type: 'warning', duration: 2500 });
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
await appstate.domainsStatePart.dispatchAction(
|
|
300
|
+
appstate.importDomainsFromProviderAction,
|
|
301
|
+
{ providerId: String(providerKey), domainNames: names },
|
|
302
|
+
);
|
|
303
|
+
modalArg.destroy();
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
],
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
private async deleteDomain(domain: interfaces.data.IDomain) {
|
|
311
|
+
const { DeesModal } = await import('@design.estate/dees-catalog');
|
|
312
|
+
DeesModal.createAndShow({
|
|
313
|
+
heading: `Delete domain ${domain.name}?`,
|
|
314
|
+
content: html`
|
|
315
|
+
<p>
|
|
316
|
+
${domain.source === 'provider'
|
|
317
|
+
? 'This removes the domain and its cached records from dcrouter only. The zone at the provider is NOT touched.'
|
|
318
|
+
: 'This removes the domain and all of its DNS records from dcrouter. dcrouter will no longer answer queries for this domain after the next restart.'}
|
|
319
|
+
</p>
|
|
320
|
+
`,
|
|
321
|
+
menuOptions: [
|
|
322
|
+
{ name: 'Cancel', action: async (modalArg: any) => modalArg.destroy() },
|
|
323
|
+
{
|
|
324
|
+
name: 'Delete',
|
|
325
|
+
action: async (modalArg: any) => {
|
|
326
|
+
await appstate.domainsStatePart.dispatchAction(appstate.deleteDomainAction, {
|
|
327
|
+
id: domain.id,
|
|
328
|
+
});
|
|
329
|
+
modalArg.destroy();
|
|
330
|
+
},
|
|
331
|
+
},
|
|
332
|
+
],
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
}
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DeesElement,
|
|
3
|
+
html,
|
|
4
|
+
customElement,
|
|
5
|
+
type TemplateResult,
|
|
6
|
+
css,
|
|
7
|
+
state,
|
|
8
|
+
cssManager,
|
|
9
|
+
} from '@design.estate/dees-element';
|
|
10
|
+
import * as appstate from '../../appstate.js';
|
|
11
|
+
import * as interfaces from '../../../dist_ts_interfaces/index.js';
|
|
12
|
+
import { viewHostCss } from '../shared/css.js';
|
|
13
|
+
import './dns-provider-form.js';
|
|
14
|
+
import type { DnsProviderForm } from './dns-provider-form.js';
|
|
15
|
+
|
|
16
|
+
declare global {
|
|
17
|
+
interface HTMLElementTagNameMap {
|
|
18
|
+
'ops-view-providers': OpsViewProviders;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@customElement('ops-view-providers')
|
|
23
|
+
export class OpsViewProviders extends DeesElement {
|
|
24
|
+
@state()
|
|
25
|
+
accessor domainsState: appstate.IDomainsState = appstate.domainsStatePart.getState()!;
|
|
26
|
+
|
|
27
|
+
constructor() {
|
|
28
|
+
super();
|
|
29
|
+
const sub = appstate.domainsStatePart.select().subscribe((newState) => {
|
|
30
|
+
this.domainsState = newState;
|
|
31
|
+
});
|
|
32
|
+
this.rxSubscriptions.push(sub);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async connectedCallback() {
|
|
36
|
+
await super.connectedCallback();
|
|
37
|
+
await appstate.domainsStatePart.dispatchAction(appstate.fetchDomainsAndProvidersAction, null);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
public static styles = [
|
|
41
|
+
cssManager.defaultStyles,
|
|
42
|
+
viewHostCss,
|
|
43
|
+
css`
|
|
44
|
+
.providersContainer {
|
|
45
|
+
display: flex;
|
|
46
|
+
flex-direction: column;
|
|
47
|
+
gap: 24px;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.statusBadge {
|
|
51
|
+
display: inline-flex;
|
|
52
|
+
align-items: center;
|
|
53
|
+
padding: 3px 10px;
|
|
54
|
+
border-radius: 12px;
|
|
55
|
+
font-size: 12px;
|
|
56
|
+
font-weight: 600;
|
|
57
|
+
text-transform: uppercase;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.statusBadge.ok {
|
|
61
|
+
background: ${cssManager.bdTheme('#dcfce7', '#14532d')};
|
|
62
|
+
color: ${cssManager.bdTheme('#166534', '#4ade80')};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.statusBadge.error {
|
|
66
|
+
background: ${cssManager.bdTheme('#fef2f2', '#450a0a')};
|
|
67
|
+
color: ${cssManager.bdTheme('#991b1b', '#f87171')};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.statusBadge.untested {
|
|
71
|
+
background: ${cssManager.bdTheme('#f3f4f6', '#1f2937')};
|
|
72
|
+
color: ${cssManager.bdTheme('#4b5563', '#9ca3af')};
|
|
73
|
+
}
|
|
74
|
+
`,
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
public render(): TemplateResult {
|
|
78
|
+
const providers = this.domainsState.providers;
|
|
79
|
+
|
|
80
|
+
return html`
|
|
81
|
+
<dees-heading level="3">DNS Providers</dees-heading>
|
|
82
|
+
<div class="providersContainer">
|
|
83
|
+
<dees-table
|
|
84
|
+
.heading1=${'Providers'}
|
|
85
|
+
.heading2=${'External DNS provider accounts'}
|
|
86
|
+
.data=${providers}
|
|
87
|
+
.showColumnFilters=${true}
|
|
88
|
+
.displayFunction=${(p: interfaces.data.IDnsProviderPublic) => ({
|
|
89
|
+
Name: p.name,
|
|
90
|
+
Type: this.providerTypeLabel(p.type),
|
|
91
|
+
Status: this.renderStatusBadge(p.status),
|
|
92
|
+
'Last Tested': p.lastTestedAt ? new Date(p.lastTestedAt).toLocaleString() : 'never',
|
|
93
|
+
Error: p.lastError || '-',
|
|
94
|
+
})}
|
|
95
|
+
.dataActions=${[
|
|
96
|
+
{
|
|
97
|
+
name: 'Add Provider',
|
|
98
|
+
iconName: 'lucide:plus',
|
|
99
|
+
type: ['header' as const],
|
|
100
|
+
actionFunc: async () => {
|
|
101
|
+
await this.showCreateDialog();
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
name: 'Refresh',
|
|
106
|
+
iconName: 'lucide:rotateCw',
|
|
107
|
+
type: ['header' as const],
|
|
108
|
+
actionFunc: async () => {
|
|
109
|
+
await appstate.domainsStatePart.dispatchAction(
|
|
110
|
+
appstate.fetchDomainsAndProvidersAction,
|
|
111
|
+
null,
|
|
112
|
+
);
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
name: 'Test Connection',
|
|
117
|
+
iconName: 'lucide:plug',
|
|
118
|
+
type: ['inRow', 'contextmenu'] as any,
|
|
119
|
+
actionFunc: async (actionData: any) => {
|
|
120
|
+
const provider = actionData.item as interfaces.data.IDnsProviderPublic;
|
|
121
|
+
await this.testProvider(provider);
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
name: 'Edit',
|
|
126
|
+
iconName: 'lucide:pencil',
|
|
127
|
+
type: ['inRow', 'contextmenu'] as any,
|
|
128
|
+
actionFunc: async (actionData: any) => {
|
|
129
|
+
const provider = actionData.item as interfaces.data.IDnsProviderPublic;
|
|
130
|
+
await this.showEditDialog(provider);
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
name: 'Delete',
|
|
135
|
+
iconName: 'lucide:trash2',
|
|
136
|
+
type: ['inRow', 'contextmenu'] as any,
|
|
137
|
+
actionFunc: async (actionData: any) => {
|
|
138
|
+
const provider = actionData.item as interfaces.data.IDnsProviderPublic;
|
|
139
|
+
await this.deleteProvider(provider);
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
]}
|
|
143
|
+
></dees-table>
|
|
144
|
+
</div>
|
|
145
|
+
`;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private renderStatusBadge(status: interfaces.data.TDnsProviderStatus): TemplateResult {
|
|
149
|
+
return html`<span class="statusBadge ${status}">${status}</span>`;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
private providerTypeLabel(type: interfaces.data.TDnsProviderType): string {
|
|
153
|
+
return interfaces.data.getDnsProviderTypeDescriptor(type)?.displayName ?? type;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
private async showCreateDialog() {
|
|
157
|
+
const { DeesModal, DeesToast } = await import('@design.estate/dees-catalog');
|
|
158
|
+
const formEl = document.createElement('dns-provider-form') as DnsProviderForm;
|
|
159
|
+
DeesModal.createAndShow({
|
|
160
|
+
heading: 'Add DNS Provider',
|
|
161
|
+
content: html`${formEl}`,
|
|
162
|
+
menuOptions: [
|
|
163
|
+
{ name: 'Cancel', action: async (modalArg: any) => modalArg.destroy() },
|
|
164
|
+
{
|
|
165
|
+
name: 'Create',
|
|
166
|
+
action: async (modalArg: any) => {
|
|
167
|
+
const data = await formEl.collectData();
|
|
168
|
+
if (!data) return;
|
|
169
|
+
if (!data.name) {
|
|
170
|
+
DeesToast.show({ message: 'Name is required', type: 'warning', duration: 2500 });
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
if (!data.credentialsTouched) {
|
|
174
|
+
DeesToast.show({
|
|
175
|
+
message: 'Fill in the provider credentials',
|
|
176
|
+
type: 'warning',
|
|
177
|
+
duration: 2500,
|
|
178
|
+
});
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
await appstate.domainsStatePart.dispatchAction(appstate.createDnsProviderAction, {
|
|
182
|
+
name: data.name,
|
|
183
|
+
type: data.type,
|
|
184
|
+
credentials: data.credentials,
|
|
185
|
+
});
|
|
186
|
+
modalArg.destroy();
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
],
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
private async showEditDialog(provider: interfaces.data.IDnsProviderPublic) {
|
|
194
|
+
const { DeesModal } = await import('@design.estate/dees-catalog');
|
|
195
|
+
const formEl = document.createElement('dns-provider-form') as DnsProviderForm;
|
|
196
|
+
formEl.providerName = provider.name;
|
|
197
|
+
formEl.selectedType = provider.type;
|
|
198
|
+
formEl.lockType = true;
|
|
199
|
+
formEl.credentialsHint =
|
|
200
|
+
'Leave credential fields blank to keep the current values. Fill them to rotate.';
|
|
201
|
+
DeesModal.createAndShow({
|
|
202
|
+
heading: `Edit Provider: ${provider.name}`,
|
|
203
|
+
content: html`${formEl}`,
|
|
204
|
+
menuOptions: [
|
|
205
|
+
{ name: 'Cancel', action: async (modalArg: any) => modalArg.destroy() },
|
|
206
|
+
{
|
|
207
|
+
name: 'Save',
|
|
208
|
+
action: async (modalArg: any) => {
|
|
209
|
+
const data = await formEl.collectData();
|
|
210
|
+
if (!data) return;
|
|
211
|
+
await appstate.domainsStatePart.dispatchAction(appstate.updateDnsProviderAction, {
|
|
212
|
+
id: provider.id,
|
|
213
|
+
name: data.name || provider.name,
|
|
214
|
+
// Only send credentials if the user actually entered something —
|
|
215
|
+
// otherwise we keep the current secret untouched.
|
|
216
|
+
credentials: data.credentialsTouched ? data.credentials : undefined,
|
|
217
|
+
});
|
|
218
|
+
modalArg.destroy();
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
],
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
private async testProvider(provider: interfaces.data.IDnsProviderPublic) {
|
|
226
|
+
const { DeesToast } = await import('@design.estate/dees-catalog');
|
|
227
|
+
await appstate.domainsStatePart.dispatchAction(appstate.testDnsProviderAction, {
|
|
228
|
+
id: provider.id,
|
|
229
|
+
});
|
|
230
|
+
const updated = appstate.domainsStatePart
|
|
231
|
+
.getState()!
|
|
232
|
+
.providers.find((p) => p.id === provider.id);
|
|
233
|
+
if (updated?.status === 'ok') {
|
|
234
|
+
DeesToast.show({
|
|
235
|
+
message: `${provider.name}: connection OK`,
|
|
236
|
+
type: 'success',
|
|
237
|
+
duration: 3000,
|
|
238
|
+
});
|
|
239
|
+
} else {
|
|
240
|
+
DeesToast.show({
|
|
241
|
+
message: `${provider.name}: ${updated?.lastError || 'connection failed'}`,
|
|
242
|
+
type: 'error',
|
|
243
|
+
duration: 4000,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
private async deleteProvider(provider: interfaces.data.IDnsProviderPublic) {
|
|
249
|
+
const linkedDomains = this.domainsState.domains.filter((d) => d.providerId === provider.id);
|
|
250
|
+
const { DeesModal } = await import('@design.estate/dees-catalog');
|
|
251
|
+
|
|
252
|
+
const doDelete = async (force: boolean) => {
|
|
253
|
+
await appstate.domainsStatePart.dispatchAction(appstate.deleteDnsProviderAction, {
|
|
254
|
+
id: provider.id,
|
|
255
|
+
force,
|
|
256
|
+
});
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
if (linkedDomains.length > 0) {
|
|
260
|
+
DeesModal.createAndShow({
|
|
261
|
+
heading: `Provider in use`,
|
|
262
|
+
content: html`
|
|
263
|
+
<p>
|
|
264
|
+
Provider <strong>${provider.name}</strong> is referenced by ${linkedDomains.length}
|
|
265
|
+
domain(s). Deleting will also remove the imported domain(s) and their cached
|
|
266
|
+
records (the records at ${provider.type} are NOT touched).
|
|
267
|
+
</p>
|
|
268
|
+
`,
|
|
269
|
+
menuOptions: [
|
|
270
|
+
{ name: 'Cancel', action: async (modalArg: any) => modalArg.destroy() },
|
|
271
|
+
{
|
|
272
|
+
name: 'Force Delete',
|
|
273
|
+
action: async (modalArg: any) => {
|
|
274
|
+
await doDelete(true);
|
|
275
|
+
modalArg.destroy();
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
],
|
|
279
|
+
});
|
|
280
|
+
} else {
|
|
281
|
+
await doDelete(false);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
@@ -60,7 +60,7 @@ export class OpsViewEmails extends DeesElement {
|
|
|
60
60
|
|
|
61
61
|
public render() {
|
|
62
62
|
return html`
|
|
63
|
-
<dees-heading level="
|
|
63
|
+
<dees-heading level="3">Email Log</dees-heading>
|
|
64
64
|
<div class="viewContainer">
|
|
65
65
|
${this.currentView === 'detail' && this.selectedEmail
|
|
66
66
|
? html`
|
package/ts_web/elements/index.ts
CHANGED
|
@@ -285,7 +285,7 @@ export class OpsViewNetworkActivity extends DeesElement {
|
|
|
285
285
|
|
|
286
286
|
public render() {
|
|
287
287
|
return html`
|
|
288
|
-
<dees-heading level="
|
|
288
|
+
<dees-heading level="3">Network Activity</dees-heading>
|
|
289
289
|
|
|
290
290
|
<div class="networkContainer">
|
|
291
291
|
<!-- Stats Grid -->
|
|
@@ -347,6 +347,7 @@ export class OpsViewNetworkActivity extends DeesElement {
|
|
|
347
347
|
heading1="Recent Network Activity"
|
|
348
348
|
heading2="Recent network requests"
|
|
349
349
|
searchable
|
|
350
|
+
.showColumnFilters=${true}
|
|
350
351
|
.pagination=${true}
|
|
351
352
|
.paginationSize=${50}
|
|
352
353
|
dataName="request"
|
|
@@ -606,6 +607,8 @@ export class OpsViewNetworkActivity extends DeesElement {
|
|
|
606
607
|
}}
|
|
607
608
|
heading1="Top Connected IPs"
|
|
608
609
|
heading2="IPs with most active connections and bandwidth"
|
|
610
|
+
searchable
|
|
611
|
+
.showColumnFilters=${true}
|
|
609
612
|
.pagination=${false}
|
|
610
613
|
dataName="ip"
|
|
611
614
|
></dees-table>
|
|
@@ -656,6 +659,7 @@ export class OpsViewNetworkActivity extends DeesElement {
|
|
|
656
659
|
heading1="Backend Protocols"
|
|
657
660
|
heading2="Auto-detected backend protocols and connection pool health"
|
|
658
661
|
searchable
|
|
662
|
+
.showColumnFilters=${true}
|
|
659
663
|
.pagination=${false}
|
|
660
664
|
dataName="backend"
|
|
661
665
|
></dees-table>
|
|
@@ -64,7 +64,7 @@ export class OpsViewNetworkTargets extends DeesElement {
|
|
|
64
64
|
];
|
|
65
65
|
|
|
66
66
|
return html`
|
|
67
|
-
<dees-heading level="
|
|
67
|
+
<dees-heading level="3">Network Targets</dees-heading>
|
|
68
68
|
<div class="targetsContainer">
|
|
69
69
|
<dees-statsgrid .tiles=${statsTiles}></dees-statsgrid>
|
|
70
70
|
<dees-table
|