@serve.zone/dcrouter 13.5.0 → 13.7.1
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 +1705 -1365
- 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 +3 -0
- package/dist_ts/opsserver/classes.opsserver.js +7 -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 +3 -0
- package/dist_ts/opsserver/handlers/index.js +4 -1
- 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 +3 -0
- package/dist_ts_interfaces/requests/index.js +4 -1
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/appstate.d.ts +72 -0
- package/dist_ts_web/appstate.js +308 -6
- package/dist_ts_web/elements/access/ops-view-apitokens.js +1 -1
- package/dist_ts_web/elements/access/ops-view-users.js +1 -1
- 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 +1 -1
- 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 +14 -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 +4 -2
- 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 +6 -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 +4 -1
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/appstate.ts +403 -5
- package/ts_web/elements/access/ops-view-apitokens.ts +1 -1
- package/ts_web/elements/access/ops-view-users.ts +1 -1
- 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 +1 -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 +14 -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 +3 -1
|
@@ -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 -->
|
|
@@ -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
|
|
@@ -174,7 +174,7 @@ export class OpsViewRemoteIngress extends DeesElement {
|
|
|
174
174
|
];
|
|
175
175
|
|
|
176
176
|
return html`
|
|
177
|
-
<dees-heading level="
|
|
177
|
+
<dees-heading level="3">Remote Ingress</dees-heading>
|
|
178
178
|
|
|
179
179
|
${this.riState.newEdgeId ? html`
|
|
180
180
|
<div class="secretDialog">
|
|
@@ -200,7 +200,7 @@ export class OpsViewRoutes extends DeesElement {
|
|
|
200
200
|
});
|
|
201
201
|
|
|
202
202
|
return html`
|
|
203
|
-
<dees-heading level="
|
|
203
|
+
<dees-heading level="3">Route Management</dees-heading>
|
|
204
204
|
|
|
205
205
|
<div class="routesContainer">
|
|
206
206
|
<dees-statsgrid
|
|
@@ -64,7 +64,7 @@ export class OpsViewSourceProfiles extends DeesElement {
|
|
|
64
64
|
];
|
|
65
65
|
|
|
66
66
|
return html`
|
|
67
|
-
<dees-heading level="
|
|
67
|
+
<dees-heading level="3">Source Profiles</dees-heading>
|
|
68
68
|
<div class="profilesContainer">
|
|
69
69
|
<dees-statsgrid .tiles=${statsTiles}></dees-statsgrid>
|
|
70
70
|
<dees-table
|