@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
package/ts_web/appstate.ts
CHANGED
|
@@ -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', '
|
|
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';
|
|
@@ -251,6 +251,34 @@ export const routeManagementStatePart = await appState.getStatePart<IRouteManage
|
|
|
251
251
|
'soft'
|
|
252
252
|
);
|
|
253
253
|
|
|
254
|
+
// ============================================================================
|
|
255
|
+
// Users State (read-only list of OpsServer user accounts)
|
|
256
|
+
// ============================================================================
|
|
257
|
+
|
|
258
|
+
export interface IUser {
|
|
259
|
+
id: string;
|
|
260
|
+
username: string;
|
|
261
|
+
role: string;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export interface IUsersState {
|
|
265
|
+
users: IUser[];
|
|
266
|
+
isLoading: boolean;
|
|
267
|
+
error: string | null;
|
|
268
|
+
lastUpdated: number;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export const usersStatePart = await appState.getStatePart<IUsersState>(
|
|
272
|
+
'users',
|
|
273
|
+
{
|
|
274
|
+
users: [],
|
|
275
|
+
isLoading: false,
|
|
276
|
+
error: null,
|
|
277
|
+
lastUpdated: 0,
|
|
278
|
+
},
|
|
279
|
+
'soft',
|
|
280
|
+
);
|
|
281
|
+
|
|
254
282
|
// Actions for state management
|
|
255
283
|
interface IActionContext {
|
|
256
284
|
identity: interfaces.data.IIdentity | null;
|
|
@@ -437,8 +465,9 @@ export const setActiveViewAction = uiStatePart.createAction<string>(async (state
|
|
|
437
465
|
}, 100);
|
|
438
466
|
}
|
|
439
467
|
|
|
440
|
-
// If switching to
|
|
441
|
-
|
|
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') {
|
|
442
471
|
setTimeout(() => {
|
|
443
472
|
certificateStatePart.dispatchAction(fetchCertificateOverviewAction, null);
|
|
444
473
|
}, 100);
|
|
@@ -1527,6 +1556,403 @@ export const deleteTargetAction = profilesTargetsStatePart.createAction<{
|
|
|
1527
1556
|
}
|
|
1528
1557
|
});
|
|
1529
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
|
+
|
|
1530
1956
|
// ============================================================================
|
|
1531
1957
|
// Route Management Actions
|
|
1532
1958
|
// ============================================================================
|
|
@@ -1756,6 +2182,35 @@ export const fetchApiTokensAction = routeManagementStatePart.createAction(async
|
|
|
1756
2182
|
}
|
|
1757
2183
|
});
|
|
1758
2184
|
|
|
2185
|
+
// Users (read-only list)
|
|
2186
|
+
export const fetchUsersAction = usersStatePart.createAction(async (statePartArg): Promise<IUsersState> => {
|
|
2187
|
+
const context = getActionContext();
|
|
2188
|
+
const currentState = statePartArg.getState()!;
|
|
2189
|
+
if (!context.identity) return currentState;
|
|
2190
|
+
|
|
2191
|
+
try {
|
|
2192
|
+
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
|
2193
|
+
interfaces.requests.IReq_ListUsers
|
|
2194
|
+
>('/typedrequest', 'listUsers');
|
|
2195
|
+
|
|
2196
|
+
const response = await request.fire({
|
|
2197
|
+
identity: context.identity,
|
|
2198
|
+
});
|
|
2199
|
+
|
|
2200
|
+
return {
|
|
2201
|
+
...currentState,
|
|
2202
|
+
users: response.users,
|
|
2203
|
+
error: null,
|
|
2204
|
+
lastUpdated: Date.now(),
|
|
2205
|
+
};
|
|
2206
|
+
} catch (error) {
|
|
2207
|
+
return {
|
|
2208
|
+
...currentState,
|
|
2209
|
+
error: error instanceof Error ? error.message : 'Failed to fetch users',
|
|
2210
|
+
};
|
|
2211
|
+
}
|
|
2212
|
+
});
|
|
2213
|
+
|
|
1759
2214
|
export async function createApiToken(name: string, scopes: interfaces.data.TApiTokenScope[], expiresInDays?: number | null) {
|
|
1760
2215
|
const context = getActionContext();
|
|
1761
2216
|
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
|
@@ -2019,8 +2474,8 @@ async function dispatchCombinedRefreshActionInner() {
|
|
|
2019
2474
|
}
|
|
2020
2475
|
}
|
|
2021
2476
|
|
|
2022
|
-
// Refresh certificate data if on
|
|
2023
|
-
if (currentView === 'certificates') {
|
|
2477
|
+
// Refresh certificate data if on Domains > Certificates subview
|
|
2478
|
+
if (currentView === 'domains' && currentSubview === 'certificates') {
|
|
2024
2479
|
try {
|
|
2025
2480
|
await certificateStatePart.dispatchAction(fetchCertificateOverviewAction, null);
|
|
2026
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="
|
|
103
|
+
<dees-heading level="3">API Tokens</dees-heading>
|
|
104
104
|
|
|
105
105
|
<div class="apiTokensContainer">
|
|
106
106
|
<dees-table
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import * as appstate from '../../appstate.js';
|
|
2
|
+
import { viewHostCss } from '../shared/css.js';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
DeesElement,
|
|
6
|
+
css,
|
|
7
|
+
cssManager,
|
|
8
|
+
customElement,
|
|
9
|
+
html,
|
|
10
|
+
state,
|
|
11
|
+
type TemplateResult,
|
|
12
|
+
} from '@design.estate/dees-element';
|
|
13
|
+
|
|
14
|
+
@customElement('ops-view-users')
|
|
15
|
+
export class OpsViewUsers extends DeesElement {
|
|
16
|
+
@state() accessor usersState: appstate.IUsersState = {
|
|
17
|
+
users: [],
|
|
18
|
+
isLoading: false,
|
|
19
|
+
error: null,
|
|
20
|
+
lastUpdated: 0,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
@state() accessor loginState: appstate.ILoginState = {
|
|
24
|
+
identity: null,
|
|
25
|
+
isLoggedIn: false,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
constructor() {
|
|
29
|
+
super();
|
|
30
|
+
const usersSub = appstate.usersStatePart
|
|
31
|
+
.select((s) => s)
|
|
32
|
+
.subscribe((usersState) => {
|
|
33
|
+
this.usersState = usersState;
|
|
34
|
+
});
|
|
35
|
+
this.rxSubscriptions.push(usersSub);
|
|
36
|
+
|
|
37
|
+
const loginSub = appstate.loginStatePart
|
|
38
|
+
.select((s) => s)
|
|
39
|
+
.subscribe((loginState) => {
|
|
40
|
+
this.loginState = loginState;
|
|
41
|
+
// Re-fetch users when user logs in (fixes race condition where
|
|
42
|
+
// the view is created before authentication completes)
|
|
43
|
+
if (loginState.isLoggedIn) {
|
|
44
|
+
appstate.usersStatePart.dispatchAction(appstate.fetchUsersAction, null);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
this.rxSubscriptions.push(loginSub);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public static styles = [
|
|
51
|
+
cssManager.defaultStyles,
|
|
52
|
+
viewHostCss,
|
|
53
|
+
css`
|
|
54
|
+
.usersContainer {
|
|
55
|
+
display: flex;
|
|
56
|
+
flex-direction: column;
|
|
57
|
+
gap: 24px;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.roleBadge {
|
|
61
|
+
display: inline-flex;
|
|
62
|
+
align-items: center;
|
|
63
|
+
padding: 3px 10px;
|
|
64
|
+
border-radius: 12px;
|
|
65
|
+
font-size: 12px;
|
|
66
|
+
font-weight: 600;
|
|
67
|
+
letter-spacing: 0.02em;
|
|
68
|
+
text-transform: uppercase;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.roleBadge.admin {
|
|
72
|
+
background: ${cssManager.bdTheme('#fef3c7', '#451a03')};
|
|
73
|
+
color: ${cssManager.bdTheme('#92400e', '#fbbf24')};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.roleBadge.user {
|
|
77
|
+
background: ${cssManager.bdTheme('#e0f2fe', '#0c4a6e')};
|
|
78
|
+
color: ${cssManager.bdTheme('#075985', '#7dd3fc')};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.sessionBadge {
|
|
82
|
+
display: inline-flex;
|
|
83
|
+
align-items: center;
|
|
84
|
+
padding: 3px 10px;
|
|
85
|
+
border-radius: 12px;
|
|
86
|
+
font-size: 12px;
|
|
87
|
+
font-weight: 600;
|
|
88
|
+
letter-spacing: 0.02em;
|
|
89
|
+
text-transform: uppercase;
|
|
90
|
+
background: ${cssManager.bdTheme('#dcfce7', '#14532d')};
|
|
91
|
+
color: ${cssManager.bdTheme('#166534', '#4ade80')};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.userIdCell {
|
|
95
|
+
font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, monospace;
|
|
96
|
+
font-size: 11px;
|
|
97
|
+
color: ${cssManager.bdTheme('#6b7280', '#9ca3af')};
|
|
98
|
+
}
|
|
99
|
+
`,
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
public render(): TemplateResult {
|
|
103
|
+
const { users } = this.usersState;
|
|
104
|
+
const currentUserId = this.loginState.identity?.userId;
|
|
105
|
+
|
|
106
|
+
return html`
|
|
107
|
+
<dees-heading level="3">Users</dees-heading>
|
|
108
|
+
|
|
109
|
+
<div class="usersContainer">
|
|
110
|
+
<dees-table
|
|
111
|
+
.heading1=${'Users'}
|
|
112
|
+
.heading2=${'OpsServer user accounts'}
|
|
113
|
+
.data=${users}
|
|
114
|
+
.dataName=${'user'}
|
|
115
|
+
.searchable=${true}
|
|
116
|
+
.showColumnFilters=${true}
|
|
117
|
+
.displayFunction=${(user: appstate.IUser) => ({
|
|
118
|
+
ID: html`<span class="userIdCell">${user.id}</span>`,
|
|
119
|
+
Username: user.username,
|
|
120
|
+
Role: this.renderRoleBadge(user.role),
|
|
121
|
+
Session: user.id === currentUserId
|
|
122
|
+
? html`<span class="sessionBadge">current</span>`
|
|
123
|
+
: '',
|
|
124
|
+
})}
|
|
125
|
+
></dees-table>
|
|
126
|
+
</div>
|
|
127
|
+
`;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
private renderRoleBadge(role: string): TemplateResult {
|
|
131
|
+
const cls = role === 'admin' ? 'admin' : 'user';
|
|
132
|
+
return html`<span class="roleBadge ${cls}">${role}</span>`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async firstUpdated() {
|
|
136
|
+
if (this.loginState.isLoggedIn) {
|
|
137
|
+
await appstate.usersStatePart.dispatchAction(appstate.fetchUsersAction, null);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|