@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.
Files changed (136) hide show
  1. package/dist_serve/bundle.js +1705 -1365
  2. package/dist_ts/00_commitinfo_data.js +1 -1
  3. package/dist_ts/classes.dcrouter.d.ts +2 -5
  4. package/dist_ts/classes.dcrouter.js +41 -10
  5. package/dist_ts/db/documents/classes.dns-provider.doc.d.ts +22 -0
  6. package/dist_ts/db/documents/classes.dns-provider.doc.js +134 -0
  7. package/dist_ts/db/documents/classes.dns-record.doc.d.ts +21 -0
  8. package/dist_ts/db/documents/classes.dns-record.doc.js +143 -0
  9. package/dist_ts/db/documents/classes.domain.doc.d.ts +22 -0
  10. package/dist_ts/db/documents/classes.domain.doc.js +146 -0
  11. package/dist_ts/db/documents/index.d.ts +3 -0
  12. package/dist_ts/db/documents/index.js +5 -1
  13. package/dist_ts/dns/index.d.ts +2 -0
  14. package/dist_ts/dns/index.js +3 -0
  15. package/dist_ts/dns/manager.dns.d.ts +227 -0
  16. package/dist_ts/dns/manager.dns.js +747 -0
  17. package/dist_ts/dns/providers/cloudflare.provider.d.ts +21 -0
  18. package/dist_ts/dns/providers/cloudflare.provider.js +106 -0
  19. package/dist_ts/dns/providers/factory.d.ts +23 -0
  20. package/dist_ts/dns/providers/factory.js +38 -0
  21. package/dist_ts/dns/providers/index.d.ts +3 -0
  22. package/dist_ts/dns/providers/index.js +4 -0
  23. package/dist_ts/dns/providers/interfaces.d.ts +54 -0
  24. package/dist_ts/dns/providers/interfaces.js +2 -0
  25. package/dist_ts/opsserver/classes.opsserver.d.ts +3 -0
  26. package/dist_ts/opsserver/classes.opsserver.js +7 -1
  27. package/dist_ts/opsserver/handlers/config.handler.js +11 -2
  28. package/dist_ts/opsserver/handlers/dns-provider.handler.d.ts +16 -0
  29. package/dist_ts/opsserver/handlers/dns-provider.handler.js +119 -0
  30. package/dist_ts/opsserver/handlers/dns-record.handler.d.ts +13 -0
  31. package/dist_ts/opsserver/handlers/dns-record.handler.js +98 -0
  32. package/dist_ts/opsserver/handlers/domain.handler.d.ts +13 -0
  33. package/dist_ts/opsserver/handlers/domain.handler.js +124 -0
  34. package/dist_ts/opsserver/handlers/index.d.ts +3 -0
  35. package/dist_ts/opsserver/handlers/index.js +4 -1
  36. package/dist_ts_interfaces/data/dns-provider.d.ts +112 -0
  37. package/dist_ts_interfaces/data/dns-provider.js +27 -0
  38. package/dist_ts_interfaces/data/dns-record.d.ts +40 -0
  39. package/dist_ts_interfaces/data/dns-record.js +2 -0
  40. package/dist_ts_interfaces/data/domain.d.ts +34 -0
  41. package/dist_ts_interfaces/data/domain.js +2 -0
  42. package/dist_ts_interfaces/data/index.d.ts +3 -0
  43. package/dist_ts_interfaces/data/index.js +4 -1
  44. package/dist_ts_interfaces/data/route-management.d.ts +1 -1
  45. package/dist_ts_interfaces/requests/dns-providers.d.ts +117 -0
  46. package/dist_ts_interfaces/requests/dns-providers.js +2 -0
  47. package/dist_ts_interfaces/requests/dns-records.d.ts +89 -0
  48. package/dist_ts_interfaces/requests/dns-records.js +2 -0
  49. package/dist_ts_interfaces/requests/domains.d.ts +118 -0
  50. package/dist_ts_interfaces/requests/domains.js +2 -0
  51. package/dist_ts_interfaces/requests/index.d.ts +3 -0
  52. package/dist_ts_interfaces/requests/index.js +4 -1
  53. package/dist_ts_web/00_commitinfo_data.js +1 -1
  54. package/dist_ts_web/appstate.d.ts +72 -0
  55. package/dist_ts_web/appstate.js +308 -6
  56. package/dist_ts_web/elements/access/ops-view-apitokens.js +1 -1
  57. package/dist_ts_web/elements/access/ops-view-users.js +1 -1
  58. package/dist_ts_web/elements/domains/dns-provider-form.d.ts +58 -0
  59. package/dist_ts_web/elements/domains/dns-provider-form.js +268 -0
  60. package/dist_ts_web/elements/domains/index.d.ts +5 -0
  61. package/dist_ts_web/elements/domains/index.js +6 -0
  62. package/dist_ts_web/elements/{ops-view-certificates.d.ts → domains/ops-view-certificates.d.ts} +1 -1
  63. package/dist_ts_web/elements/{ops-view-certificates.js → domains/ops-view-certificates.js} +5 -5
  64. package/dist_ts_web/elements/domains/ops-view-dns.d.ts +17 -0
  65. package/dist_ts_web/elements/domains/ops-view-dns.js +304 -0
  66. package/dist_ts_web/elements/domains/ops-view-domains.d.ts +18 -0
  67. package/dist_ts_web/elements/domains/ops-view-domains.js +361 -0
  68. package/dist_ts_web/elements/domains/ops-view-providers.d.ts +21 -0
  69. package/dist_ts_web/elements/domains/ops-view-providers.js +316 -0
  70. package/dist_ts_web/elements/email/ops-view-email-security.js +1 -1
  71. package/dist_ts_web/elements/email/ops-view-emails.js +1 -1
  72. package/dist_ts_web/elements/index.d.ts +1 -1
  73. package/dist_ts_web/elements/index.js +2 -2
  74. package/dist_ts_web/elements/network/ops-view-network-activity.js +1 -1
  75. package/dist_ts_web/elements/network/ops-view-networktargets.js +1 -1
  76. package/dist_ts_web/elements/network/ops-view-remoteingress.js +1 -1
  77. package/dist_ts_web/elements/network/ops-view-routes.js +1 -1
  78. package/dist_ts_web/elements/network/ops-view-sourceprofiles.js +1 -1
  79. package/dist_ts_web/elements/network/ops-view-targetprofiles.js +1 -1
  80. package/dist_ts_web/elements/network/ops-view-vpn.js +1 -1
  81. package/dist_ts_web/elements/ops-dashboard.js +14 -5
  82. package/dist_ts_web/elements/ops-view-logs.js +1 -1
  83. package/dist_ts_web/elements/overview/ops-view-config.js +3 -3
  84. package/dist_ts_web/elements/overview/ops-view-overview.js +1 -1
  85. package/dist_ts_web/elements/security/ops-view-security-authentication.js +1 -1
  86. package/dist_ts_web/elements/security/ops-view-security-blocked.js +1 -1
  87. package/dist_ts_web/elements/security/ops-view-security-overview.js +1 -1
  88. package/dist_ts_web/router.d.ts +1 -1
  89. package/dist_ts_web/router.js +4 -2
  90. package/package.json +2 -2
  91. package/ts/00_commitinfo_data.ts +1 -1
  92. package/ts/classes.dcrouter.ts +46 -17
  93. package/ts/db/documents/classes.dns-provider.doc.ts +63 -0
  94. package/ts/db/documents/classes.dns-record.doc.ts +62 -0
  95. package/ts/db/documents/classes.domain.doc.ts +66 -0
  96. package/ts/db/documents/index.ts +5 -0
  97. package/ts/dns/index.ts +2 -0
  98. package/ts/dns/manager.dns.ts +869 -0
  99. package/ts/dns/providers/cloudflare.provider.ts +131 -0
  100. package/ts/dns/providers/factory.ts +48 -0
  101. package/ts/dns/providers/index.ts +3 -0
  102. package/ts/dns/providers/interfaces.ts +67 -0
  103. package/ts/opsserver/classes.opsserver.ts +6 -0
  104. package/ts/opsserver/handlers/config.handler.ts +10 -1
  105. package/ts/opsserver/handlers/dns-provider.handler.ts +159 -0
  106. package/ts/opsserver/handlers/dns-record.handler.ts +127 -0
  107. package/ts/opsserver/handlers/domain.handler.ts +161 -0
  108. package/ts/opsserver/handlers/index.ts +4 -1
  109. package/ts_web/00_commitinfo_data.ts +1 -1
  110. package/ts_web/appstate.ts +403 -5
  111. package/ts_web/elements/access/ops-view-apitokens.ts +1 -1
  112. package/ts_web/elements/access/ops-view-users.ts +1 -1
  113. package/ts_web/elements/domains/dns-provider-form.ts +216 -0
  114. package/ts_web/elements/domains/index.ts +5 -0
  115. package/ts_web/elements/{ops-view-certificates.ts → domains/ops-view-certificates.ts} +4 -4
  116. package/ts_web/elements/domains/ops-view-dns.ts +273 -0
  117. package/ts_web/elements/domains/ops-view-domains.ts +335 -0
  118. package/ts_web/elements/domains/ops-view-providers.ts +284 -0
  119. package/ts_web/elements/email/ops-view-email-security.ts +1 -1
  120. package/ts_web/elements/email/ops-view-emails.ts +1 -1
  121. package/ts_web/elements/index.ts +1 -1
  122. package/ts_web/elements/network/ops-view-network-activity.ts +1 -1
  123. package/ts_web/elements/network/ops-view-networktargets.ts +1 -1
  124. package/ts_web/elements/network/ops-view-remoteingress.ts +1 -1
  125. package/ts_web/elements/network/ops-view-routes.ts +1 -1
  126. package/ts_web/elements/network/ops-view-sourceprofiles.ts +1 -1
  127. package/ts_web/elements/network/ops-view-targetprofiles.ts +1 -1
  128. package/ts_web/elements/network/ops-view-vpn.ts +1 -1
  129. package/ts_web/elements/ops-dashboard.ts +14 -4
  130. package/ts_web/elements/ops-view-logs.ts +1 -1
  131. package/ts_web/elements/overview/ops-view-config.ts +2 -2
  132. package/ts_web/elements/overview/ops-view-overview.ts +1 -1
  133. package/ts_web/elements/security/ops-view-security-authentication.ts +1 -1
  134. package/ts_web/elements/security/ops-view-security-blocked.ts +1 -1
  135. package/ts_web/elements/security/ops-view-security-overview.ts +1 -1
  136. package/ts_web/router.ts +3 -1
@@ -0,0 +1,161 @@
1
+ import * as plugins from '../../plugins.js';
2
+ import type { OpsServer } from '../classes.opsserver.js';
3
+ import * as interfaces from '../../../ts_interfaces/index.js';
4
+
5
+ /**
6
+ * CRUD handlers for DomainDoc.
7
+ */
8
+ export class DomainHandler {
9
+ public typedrouter = new plugins.typedrequest.TypedRouter();
10
+
11
+ constructor(private opsServerRef: OpsServer) {
12
+ this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
13
+ this.registerHandlers();
14
+ }
15
+
16
+ private async requireAuth(
17
+ request: { identity?: interfaces.data.IIdentity; apiToken?: string },
18
+ requiredScope?: interfaces.data.TApiTokenScope,
19
+ ): Promise<string> {
20
+ if (request.identity?.jwt) {
21
+ try {
22
+ const isAdmin = await this.opsServerRef.adminHandler.adminIdentityGuard.exec({
23
+ identity: request.identity,
24
+ });
25
+ if (isAdmin) return request.identity.userId;
26
+ } catch { /* fall through */ }
27
+ }
28
+
29
+ if (request.apiToken) {
30
+ const tokenManager = this.opsServerRef.dcRouterRef.apiTokenManager;
31
+ if (tokenManager) {
32
+ const token = await tokenManager.validateToken(request.apiToken);
33
+ if (token) {
34
+ if (!requiredScope || tokenManager.hasScope(token, requiredScope)) {
35
+ return token.createdBy;
36
+ }
37
+ throw new plugins.typedrequest.TypedResponseError('insufficient scope');
38
+ }
39
+ }
40
+ }
41
+
42
+ throw new plugins.typedrequest.TypedResponseError('unauthorized');
43
+ }
44
+
45
+ private registerHandlers(): void {
46
+ // Get all domains
47
+ this.typedrouter.addTypedHandler(
48
+ new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetDomains>(
49
+ 'getDomains',
50
+ async (dataArg) => {
51
+ await this.requireAuth(dataArg, 'domains:read');
52
+ const dnsManager = this.opsServerRef.dcRouterRef.dnsManager;
53
+ if (!dnsManager) return { domains: [] };
54
+ const docs = await dnsManager.listDomains();
55
+ return { domains: docs.map((d) => dnsManager.toPublicDomain(d)) };
56
+ },
57
+ ),
58
+ );
59
+
60
+ // Get single domain
61
+ this.typedrouter.addTypedHandler(
62
+ new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetDomain>(
63
+ 'getDomain',
64
+ async (dataArg) => {
65
+ await this.requireAuth(dataArg, 'domains:read');
66
+ const dnsManager = this.opsServerRef.dcRouterRef.dnsManager;
67
+ if (!dnsManager) return { domain: null };
68
+ const doc = await dnsManager.getDomain(dataArg.id);
69
+ return { domain: doc ? dnsManager.toPublicDomain(doc) : null };
70
+ },
71
+ ),
72
+ );
73
+
74
+ // Create manual domain
75
+ this.typedrouter.addTypedHandler(
76
+ new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_CreateDomain>(
77
+ 'createDomain',
78
+ async (dataArg) => {
79
+ const userId = await this.requireAuth(dataArg, 'domains:write');
80
+ const dnsManager = this.opsServerRef.dcRouterRef.dnsManager;
81
+ if (!dnsManager) return { success: false, message: 'DnsManager not initialized' };
82
+ try {
83
+ const id = await dnsManager.createManualDomain({
84
+ name: dataArg.name,
85
+ description: dataArg.description,
86
+ createdBy: userId,
87
+ });
88
+ return { success: true, id };
89
+ } catch (err: unknown) {
90
+ return { success: false, message: (err as Error).message };
91
+ }
92
+ },
93
+ ),
94
+ );
95
+
96
+ // Import domains from a provider
97
+ this.typedrouter.addTypedHandler(
98
+ new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ImportDomain>(
99
+ 'importDomain',
100
+ async (dataArg) => {
101
+ const userId = await this.requireAuth(dataArg, 'domains:write');
102
+ const dnsManager = this.opsServerRef.dcRouterRef.dnsManager;
103
+ if (!dnsManager) return { success: false, message: 'DnsManager not initialized' };
104
+ try {
105
+ const importedIds = await dnsManager.importDomainsFromProvider({
106
+ providerId: dataArg.providerId,
107
+ domainNames: dataArg.domainNames,
108
+ createdBy: userId,
109
+ });
110
+ return { success: true, importedIds };
111
+ } catch (err: unknown) {
112
+ return { success: false, message: (err as Error).message };
113
+ }
114
+ },
115
+ ),
116
+ );
117
+
118
+ // Update domain metadata
119
+ this.typedrouter.addTypedHandler(
120
+ new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_UpdateDomain>(
121
+ 'updateDomain',
122
+ async (dataArg) => {
123
+ await this.requireAuth(dataArg, 'domains:write');
124
+ const dnsManager = this.opsServerRef.dcRouterRef.dnsManager;
125
+ if (!dnsManager) return { success: false, message: 'DnsManager not initialized' };
126
+ const ok = await dnsManager.updateDomain(dataArg.id, {
127
+ description: dataArg.description,
128
+ });
129
+ return ok ? { success: true } : { success: false, message: 'Domain not found' };
130
+ },
131
+ ),
132
+ );
133
+
134
+ // Delete domain
135
+ this.typedrouter.addTypedHandler(
136
+ new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_DeleteDomain>(
137
+ 'deleteDomain',
138
+ async (dataArg) => {
139
+ await this.requireAuth(dataArg, 'domains:write');
140
+ const dnsManager = this.opsServerRef.dcRouterRef.dnsManager;
141
+ if (!dnsManager) return { success: false, message: 'DnsManager not initialized' };
142
+ const ok = await dnsManager.deleteDomain(dataArg.id);
143
+ return ok ? { success: true } : { success: false, message: 'Domain not found' };
144
+ },
145
+ ),
146
+ );
147
+
148
+ // Force-resync provider domain
149
+ this.typedrouter.addTypedHandler(
150
+ new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_SyncDomain>(
151
+ 'syncDomain',
152
+ async (dataArg) => {
153
+ await this.requireAuth(dataArg, 'domains:write');
154
+ const dnsManager = this.opsServerRef.dcRouterRef.dnsManager;
155
+ if (!dnsManager) return { success: false, message: 'DnsManager not initialized' };
156
+ return await dnsManager.syncDomain(dataArg.id);
157
+ },
158
+ ),
159
+ );
160
+ }
161
+ }
@@ -13,4 +13,7 @@ export * from './vpn.handler.js';
13
13
  export * from './source-profile.handler.js';
14
14
  export * from './target-profile.handler.js';
15
15
  export * from './network-target.handler.js';
16
- export * from './users.handler.js';
16
+ export * from './users.handler.js';
17
+ export * from './dns-provider.handler.js';
18
+ export * from './domain.handler.js';
19
+ export * from './dns-record.handler.js';
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/dcrouter',
6
- version: '13.5.0',
6
+ version: '13.7.1',
7
7
  description: 'A multifaceted routing service handling mail and SMS delivery functions.'
8
8
  }
@@ -117,7 +117,7 @@ export const configStatePart = await appState.getStatePart<IConfigState>(
117
117
  // Determine initial view from URL path
118
118
  const getInitialView = (): string => {
119
119
  const path = typeof window !== 'undefined' ? window.location.pathname : '/';
120
- const validViews = ['overview', 'network', 'email', 'logs', 'access', 'security', 'certificates'];
120
+ const validViews = ['overview', 'network', 'email', 'logs', 'access', 'security', 'domains'];
121
121
  const segments = path.split('/').filter(Boolean);
122
122
  const view = segments[0];
123
123
  return validViews.includes(view) ? view : 'overview';
@@ -465,8 +465,9 @@ export const setActiveViewAction = uiStatePart.createAction<string>(async (state
465
465
  }, 100);
466
466
  }
467
467
 
468
- // If switching to certificates view, ensure we fetch certificate data
469
- if (viewName === 'certificates' && currentState.activeView !== 'certificates') {
468
+ // If switching to the Domains group, ensure we fetch certificate data
469
+ // (Certificates is a subview of Domains).
470
+ if (viewName === 'domains' && currentState.activeView !== 'domains') {
470
471
  setTimeout(() => {
471
472
  certificateStatePart.dispatchAction(fetchCertificateOverviewAction, null);
472
473
  }, 100);
@@ -1555,6 +1556,403 @@ export const deleteTargetAction = profilesTargetsStatePart.createAction<{
1555
1556
  }
1556
1557
  });
1557
1558
 
1559
+ // ============================================================================
1560
+ // Domains State (DNS providers + domains + records)
1561
+ // ============================================================================
1562
+
1563
+ export interface IDomainsState {
1564
+ providers: interfaces.data.IDnsProviderPublic[];
1565
+ domains: interfaces.data.IDomain[];
1566
+ records: interfaces.data.IDnsRecord[];
1567
+ /** id of the currently-selected domain in the DNS records subview. */
1568
+ selectedDomainId: string | null;
1569
+ isLoading: boolean;
1570
+ error: string | null;
1571
+ lastUpdated: number;
1572
+ }
1573
+
1574
+ export const domainsStatePart = await appState.getStatePart<IDomainsState>(
1575
+ 'domains',
1576
+ {
1577
+ providers: [],
1578
+ domains: [],
1579
+ records: [],
1580
+ selectedDomainId: null,
1581
+ isLoading: false,
1582
+ error: null,
1583
+ lastUpdated: 0,
1584
+ },
1585
+ 'soft',
1586
+ );
1587
+
1588
+ export const fetchDomainsAndProvidersAction = domainsStatePart.createAction(
1589
+ async (statePartArg): Promise<IDomainsState> => {
1590
+ const context = getActionContext();
1591
+ const currentState = statePartArg.getState()!;
1592
+ if (!context.identity) return currentState;
1593
+
1594
+ try {
1595
+ const providersRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
1596
+ interfaces.requests.IReq_GetDnsProviders
1597
+ >('/typedrequest', 'getDnsProviders');
1598
+ const domainsRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
1599
+ interfaces.requests.IReq_GetDomains
1600
+ >('/typedrequest', 'getDomains');
1601
+
1602
+ const [providersResponse, domainsResponse] = await Promise.all([
1603
+ providersRequest.fire({ identity: context.identity }),
1604
+ domainsRequest.fire({ identity: context.identity }),
1605
+ ]);
1606
+
1607
+ return {
1608
+ ...currentState,
1609
+ providers: providersResponse.providers,
1610
+ domains: domainsResponse.domains,
1611
+ isLoading: false,
1612
+ error: null,
1613
+ lastUpdated: Date.now(),
1614
+ };
1615
+ } catch (error: unknown) {
1616
+ return {
1617
+ ...currentState,
1618
+ isLoading: false,
1619
+ error: error instanceof Error ? error.message : 'Failed to fetch domains/providers',
1620
+ };
1621
+ }
1622
+ },
1623
+ );
1624
+
1625
+ export const fetchDnsRecordsForDomainAction = domainsStatePart.createAction<{ domainId: string }>(
1626
+ async (statePartArg, dataArg): Promise<IDomainsState> => {
1627
+ const context = getActionContext();
1628
+ const currentState = statePartArg.getState()!;
1629
+ if (!context.identity) return currentState;
1630
+
1631
+ try {
1632
+ const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
1633
+ interfaces.requests.IReq_GetDnsRecords
1634
+ >('/typedrequest', 'getDnsRecords');
1635
+ const response = await request.fire({
1636
+ identity: context.identity,
1637
+ domainId: dataArg.domainId,
1638
+ });
1639
+ return {
1640
+ ...currentState,
1641
+ records: response.records,
1642
+ selectedDomainId: dataArg.domainId,
1643
+ error: null,
1644
+ };
1645
+ } catch (error: unknown) {
1646
+ return {
1647
+ ...currentState,
1648
+ error: error instanceof Error ? error.message : 'Failed to fetch DNS records',
1649
+ };
1650
+ }
1651
+ },
1652
+ );
1653
+
1654
+ export const createDnsProviderAction = domainsStatePart.createAction<{
1655
+ name: string;
1656
+ type: interfaces.data.TDnsProviderType;
1657
+ credentials: interfaces.data.TDnsProviderCredentials;
1658
+ }>(async (statePartArg, dataArg, actionContext): Promise<IDomainsState> => {
1659
+ const context = getActionContext();
1660
+ try {
1661
+ const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
1662
+ interfaces.requests.IReq_CreateDnsProvider
1663
+ >('/typedrequest', 'createDnsProvider');
1664
+ const response = await request.fire({
1665
+ identity: context.identity!,
1666
+ name: dataArg.name,
1667
+ type: dataArg.type,
1668
+ credentials: dataArg.credentials,
1669
+ });
1670
+ if (!response.success) {
1671
+ return {
1672
+ ...statePartArg.getState()!,
1673
+ error: response.message || 'Failed to create provider',
1674
+ };
1675
+ }
1676
+ return await actionContext!.dispatch(fetchDomainsAndProvidersAction, null);
1677
+ } catch (error: unknown) {
1678
+ return {
1679
+ ...statePartArg.getState()!,
1680
+ error: error instanceof Error ? error.message : 'Failed to create provider',
1681
+ };
1682
+ }
1683
+ });
1684
+
1685
+ export const updateDnsProviderAction = domainsStatePart.createAction<{
1686
+ id: string;
1687
+ name?: string;
1688
+ credentials?: interfaces.data.TDnsProviderCredentials;
1689
+ }>(async (statePartArg, dataArg, actionContext): Promise<IDomainsState> => {
1690
+ const context = getActionContext();
1691
+ try {
1692
+ const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
1693
+ interfaces.requests.IReq_UpdateDnsProvider
1694
+ >('/typedrequest', 'updateDnsProvider');
1695
+ const response = await request.fire({
1696
+ identity: context.identity!,
1697
+ id: dataArg.id,
1698
+ name: dataArg.name,
1699
+ credentials: dataArg.credentials,
1700
+ });
1701
+ if (!response.success) {
1702
+ return {
1703
+ ...statePartArg.getState()!,
1704
+ error: response.message || 'Failed to update provider',
1705
+ };
1706
+ }
1707
+ return await actionContext!.dispatch(fetchDomainsAndProvidersAction, null);
1708
+ } catch (error: unknown) {
1709
+ return {
1710
+ ...statePartArg.getState()!,
1711
+ error: error instanceof Error ? error.message : 'Failed to update provider',
1712
+ };
1713
+ }
1714
+ });
1715
+
1716
+ export const deleteDnsProviderAction = domainsStatePart.createAction<{ id: string; force?: boolean }>(
1717
+ async (statePartArg, dataArg, actionContext): Promise<IDomainsState> => {
1718
+ const context = getActionContext();
1719
+ try {
1720
+ const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
1721
+ interfaces.requests.IReq_DeleteDnsProvider
1722
+ >('/typedrequest', 'deleteDnsProvider');
1723
+ const response = await request.fire({
1724
+ identity: context.identity!,
1725
+ id: dataArg.id,
1726
+ force: dataArg.force,
1727
+ });
1728
+ if (!response.success) {
1729
+ return {
1730
+ ...statePartArg.getState()!,
1731
+ error: response.message || 'Failed to delete provider',
1732
+ };
1733
+ }
1734
+ return await actionContext!.dispatch(fetchDomainsAndProvidersAction, null);
1735
+ } catch (error: unknown) {
1736
+ return {
1737
+ ...statePartArg.getState()!,
1738
+ error: error instanceof Error ? error.message : 'Failed to delete provider',
1739
+ };
1740
+ }
1741
+ },
1742
+ );
1743
+
1744
+ export const testDnsProviderAction = domainsStatePart.createAction<{ id: string }>(
1745
+ async (statePartArg, dataArg, actionContext): Promise<IDomainsState> => {
1746
+ const context = getActionContext();
1747
+ try {
1748
+ const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
1749
+ interfaces.requests.IReq_TestDnsProvider
1750
+ >('/typedrequest', 'testDnsProvider');
1751
+ await request.fire({ identity: context.identity!, id: dataArg.id });
1752
+ return await actionContext!.dispatch(fetchDomainsAndProvidersAction, null);
1753
+ } catch (error: unknown) {
1754
+ return {
1755
+ ...statePartArg.getState()!,
1756
+ error: error instanceof Error ? error.message : 'Failed to test provider',
1757
+ };
1758
+ }
1759
+ },
1760
+ );
1761
+
1762
+ /** One-shot fetch for the import-domain modal. Does NOT modify state. */
1763
+ export async function fetchProviderDomains(
1764
+ providerId: string,
1765
+ ): Promise<{ success: boolean; domains?: interfaces.data.IProviderDomainListing[]; message?: string }> {
1766
+ const context = getActionContext();
1767
+ if (!context.identity) return { success: false, message: 'Not authenticated' };
1768
+ const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
1769
+ interfaces.requests.IReq_ListProviderDomains
1770
+ >('/typedrequest', 'listProviderDomains');
1771
+ return await request.fire({ identity: context.identity, providerId });
1772
+ }
1773
+
1774
+ export const createManualDomainAction = domainsStatePart.createAction<{
1775
+ name: string;
1776
+ description?: string;
1777
+ }>(async (statePartArg, dataArg, actionContext): Promise<IDomainsState> => {
1778
+ const context = getActionContext();
1779
+ try {
1780
+ const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
1781
+ interfaces.requests.IReq_CreateDomain
1782
+ >('/typedrequest', 'createDomain');
1783
+ const response = await request.fire({
1784
+ identity: context.identity!,
1785
+ name: dataArg.name,
1786
+ description: dataArg.description,
1787
+ });
1788
+ if (!response.success) {
1789
+ return { ...statePartArg.getState()!, error: response.message || 'Failed to create domain' };
1790
+ }
1791
+ return await actionContext!.dispatch(fetchDomainsAndProvidersAction, null);
1792
+ } catch (error: unknown) {
1793
+ return {
1794
+ ...statePartArg.getState()!,
1795
+ error: error instanceof Error ? error.message : 'Failed to create domain',
1796
+ };
1797
+ }
1798
+ });
1799
+
1800
+ export const importDomainsFromProviderAction = domainsStatePart.createAction<{
1801
+ providerId: string;
1802
+ domainNames: string[];
1803
+ }>(async (statePartArg, dataArg, actionContext): Promise<IDomainsState> => {
1804
+ const context = getActionContext();
1805
+ try {
1806
+ const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
1807
+ interfaces.requests.IReq_ImportDomain
1808
+ >('/typedrequest', 'importDomain');
1809
+ const response = await request.fire({
1810
+ identity: context.identity!,
1811
+ providerId: dataArg.providerId,
1812
+ domainNames: dataArg.domainNames,
1813
+ });
1814
+ if (!response.success) {
1815
+ return { ...statePartArg.getState()!, error: response.message || 'Failed to import domains' };
1816
+ }
1817
+ return await actionContext!.dispatch(fetchDomainsAndProvidersAction, null);
1818
+ } catch (error: unknown) {
1819
+ return {
1820
+ ...statePartArg.getState()!,
1821
+ error: error instanceof Error ? error.message : 'Failed to import domains',
1822
+ };
1823
+ }
1824
+ });
1825
+
1826
+ export const deleteDomainAction = domainsStatePart.createAction<{ id: string }>(
1827
+ async (statePartArg, dataArg, actionContext): Promise<IDomainsState> => {
1828
+ const context = getActionContext();
1829
+ try {
1830
+ const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
1831
+ interfaces.requests.IReq_DeleteDomain
1832
+ >('/typedrequest', 'deleteDomain');
1833
+ const response = await request.fire({ identity: context.identity!, id: dataArg.id });
1834
+ if (!response.success) {
1835
+ return { ...statePartArg.getState()!, error: response.message || 'Failed to delete domain' };
1836
+ }
1837
+ return await actionContext!.dispatch(fetchDomainsAndProvidersAction, null);
1838
+ } catch (error: unknown) {
1839
+ return {
1840
+ ...statePartArg.getState()!,
1841
+ error: error instanceof Error ? error.message : 'Failed to delete domain',
1842
+ };
1843
+ }
1844
+ },
1845
+ );
1846
+
1847
+ export const syncDomainAction = domainsStatePart.createAction<{ id: string }>(
1848
+ async (statePartArg, dataArg, actionContext): Promise<IDomainsState> => {
1849
+ const context = getActionContext();
1850
+ try {
1851
+ const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
1852
+ interfaces.requests.IReq_SyncDomain
1853
+ >('/typedrequest', 'syncDomain');
1854
+ const response = await request.fire({ identity: context.identity!, id: dataArg.id });
1855
+ if (!response.success) {
1856
+ return { ...statePartArg.getState()!, error: response.message || 'Failed to sync domain' };
1857
+ }
1858
+ return await actionContext!.dispatch(fetchDomainsAndProvidersAction, null);
1859
+ } catch (error: unknown) {
1860
+ return {
1861
+ ...statePartArg.getState()!,
1862
+ error: error instanceof Error ? error.message : 'Failed to sync domain',
1863
+ };
1864
+ }
1865
+ },
1866
+ );
1867
+
1868
+ export const createDnsRecordAction = domainsStatePart.createAction<{
1869
+ domainId: string;
1870
+ name: string;
1871
+ type: interfaces.data.TDnsRecordType;
1872
+ value: string;
1873
+ ttl?: number;
1874
+ proxied?: boolean;
1875
+ }>(async (statePartArg, dataArg, actionContext): Promise<IDomainsState> => {
1876
+ const context = getActionContext();
1877
+ try {
1878
+ const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
1879
+ interfaces.requests.IReq_CreateDnsRecord
1880
+ >('/typedrequest', 'createDnsRecord');
1881
+ const response = await request.fire({
1882
+ identity: context.identity!,
1883
+ domainId: dataArg.domainId,
1884
+ name: dataArg.name,
1885
+ type: dataArg.type,
1886
+ value: dataArg.value,
1887
+ ttl: dataArg.ttl,
1888
+ proxied: dataArg.proxied,
1889
+ });
1890
+ if (!response.success) {
1891
+ return { ...statePartArg.getState()!, error: response.message || 'Failed to create record' };
1892
+ }
1893
+ return await actionContext!.dispatch(fetchDnsRecordsForDomainAction, { domainId: dataArg.domainId });
1894
+ } catch (error: unknown) {
1895
+ return {
1896
+ ...statePartArg.getState()!,
1897
+ error: error instanceof Error ? error.message : 'Failed to create record',
1898
+ };
1899
+ }
1900
+ });
1901
+
1902
+ export const updateDnsRecordAction = domainsStatePart.createAction<{
1903
+ id: string;
1904
+ domainId: string;
1905
+ name?: string;
1906
+ value?: string;
1907
+ ttl?: number;
1908
+ proxied?: boolean;
1909
+ }>(async (statePartArg, dataArg, actionContext): Promise<IDomainsState> => {
1910
+ const context = getActionContext();
1911
+ try {
1912
+ const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
1913
+ interfaces.requests.IReq_UpdateDnsRecord
1914
+ >('/typedrequest', 'updateDnsRecord');
1915
+ const response = await request.fire({
1916
+ identity: context.identity!,
1917
+ id: dataArg.id,
1918
+ name: dataArg.name,
1919
+ value: dataArg.value,
1920
+ ttl: dataArg.ttl,
1921
+ proxied: dataArg.proxied,
1922
+ });
1923
+ if (!response.success) {
1924
+ return { ...statePartArg.getState()!, error: response.message || 'Failed to update record' };
1925
+ }
1926
+ return await actionContext!.dispatch(fetchDnsRecordsForDomainAction, { domainId: dataArg.domainId });
1927
+ } catch (error: unknown) {
1928
+ return {
1929
+ ...statePartArg.getState()!,
1930
+ error: error instanceof Error ? error.message : 'Failed to update record',
1931
+ };
1932
+ }
1933
+ });
1934
+
1935
+ export const deleteDnsRecordAction = domainsStatePart.createAction<{ id: string; domainId: string }>(
1936
+ async (statePartArg, dataArg, actionContext): Promise<IDomainsState> => {
1937
+ const context = getActionContext();
1938
+ try {
1939
+ const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
1940
+ interfaces.requests.IReq_DeleteDnsRecord
1941
+ >('/typedrequest', 'deleteDnsRecord');
1942
+ const response = await request.fire({ identity: context.identity!, id: dataArg.id });
1943
+ if (!response.success) {
1944
+ return { ...statePartArg.getState()!, error: response.message || 'Failed to delete record' };
1945
+ }
1946
+ return await actionContext!.dispatch(fetchDnsRecordsForDomainAction, { domainId: dataArg.domainId });
1947
+ } catch (error: unknown) {
1948
+ return {
1949
+ ...statePartArg.getState()!,
1950
+ error: error instanceof Error ? error.message : 'Failed to delete record',
1951
+ };
1952
+ }
1953
+ },
1954
+ );
1955
+
1558
1956
  // ============================================================================
1559
1957
  // Route Management Actions
1560
1958
  // ============================================================================
@@ -2076,8 +2474,8 @@ async function dispatchCombinedRefreshActionInner() {
2076
2474
  }
2077
2475
  }
2078
2476
 
2079
- // Refresh certificate data if on certificates view
2080
- if (currentView === 'certificates') {
2477
+ // Refresh certificate data if on Domains > Certificates subview
2478
+ if (currentView === 'domains' && currentSubview === 'certificates') {
2081
2479
  try {
2082
2480
  await certificateStatePart.dispatchAction(fetchCertificateOverviewAction, null);
2083
2481
  } catch (error) {
@@ -100,7 +100,7 @@ export class OpsViewApiTokens extends DeesElement {
100
100
  const { apiTokens } = this.routeState;
101
101
 
102
102
  return html`
103
- <dees-heading level="hr">API Tokens</dees-heading>
103
+ <dees-heading level="3">API Tokens</dees-heading>
104
104
 
105
105
  <div class="apiTokensContainer">
106
106
  <dees-table
@@ -104,7 +104,7 @@ export class OpsViewUsers extends DeesElement {
104
104
  const currentUserId = this.loginState.identity?.userId;
105
105
 
106
106
  return html`
107
- <dees-heading level="2">Users</dees-heading>
107
+ <dees-heading level="3">Users</dees-heading>
108
108
 
109
109
  <div class="usersContainer">
110
110
  <dees-table