fix(typescript): tighten TypeScript null safety and error handling across backend and ops UI

This commit is contained in:
2026-03-26 07:40:56 +00:00
parent 0195a21f30
commit 44f2a7f0a9
40 changed files with 414 additions and 451 deletions

View File

@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@serve.zone/dcrouter',
version: '11.10.5',
version: '11.10.6',
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
}

View File

@@ -240,7 +240,7 @@ interface IActionContext {
}
const getActionContext = (): IActionContext => {
const identity = loginStatePart.getState().identity;
const identity = loginStatePart.getState()!.identity;
// Treat expired JWTs as no identity — prevents stale persisted sessions from firing requests
if (identity && identity.expiresAt && identity.expiresAt < Date.now()) {
return { identity: null };
@@ -252,7 +252,7 @@ const getActionContext = (): IActionContext => {
export const loginAction = loginStatePart.createAction<{
username: string;
password: string;
}>(async (statePartArg, dataArg) => {
}>(async (statePartArg, dataArg): Promise<ILoginState> => {
const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_AdminLoginWithUsernameAndPassword
>('/typedrequest', 'adminLoginWithUsernameAndPassword');
@@ -269,10 +269,10 @@ export const loginAction = loginStatePart.createAction<{
isLoggedIn: true,
};
}
return statePartArg.getState();
} catch (error) {
return statePartArg.getState()!;
} catch (error: unknown) {
console.error('Login failed:', error);
return statePartArg.getState();
return statePartArg.getState()!;
}
});
@@ -300,9 +300,9 @@ export const logoutAction = loginStatePart.createAction(async (statePartArg) =>
});
// Fetch All Stats Action - Using combined endpoint for efficiency
export const fetchAllStatsAction = statsStatePart.createAction(async (statePartArg) => {
export const fetchAllStatsAction = statsStatePart.createAction(async (statePartArg): Promise<IStatsState> => {
const context = getActionContext();
const currentState = statePartArg.getState();
const currentState = statePartArg.getState()!;
if (!context.identity) return currentState;
try {
@@ -310,7 +310,7 @@ export const fetchAllStatsAction = statsStatePart.createAction(async (statePartA
const combinedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_GetCombinedMetrics
>('/typedrequest', 'getCombinedMetrics');
const combinedResponse = await combinedRequest.fire({
identity: context.identity,
sections: {
@@ -332,19 +332,19 @@ export const fetchAllStatsAction = statsStatePart.createAction(async (statePartA
isLoading: false,
error: null,
};
} catch (error) {
} catch (error: unknown) {
return {
...currentState,
isLoading: false,
error: error.message || 'Failed to fetch statistics',
error: (error as Error).message || 'Failed to fetch statistics',
};
}
});
// Fetch Configuration Action (read-only)
export const fetchConfigurationAction = configStatePart.createAction(async (statePartArg) => {
export const fetchConfigurationAction = configStatePart.createAction(async (statePartArg): Promise<IConfigState> => {
const context = getActionContext();
const currentState = statePartArg.getState();
const currentState = statePartArg.getState()!;
if (!context.identity) return currentState;
try {
@@ -361,11 +361,11 @@ export const fetchConfigurationAction = configStatePart.createAction(async (stat
isLoading: false,
error: null,
};
} catch (error) {
} catch (error: unknown) {
return {
...currentState,
isLoading: false,
error: error.message || 'Failed to fetch configuration',
error: (error as Error).message || 'Failed to fetch configuration',
};
}
});
@@ -375,9 +375,9 @@ export const fetchRecentLogsAction = logStatePart.createAction<{
limit?: number;
level?: 'debug' | 'info' | 'warn' | 'error';
category?: 'smtp' | 'dns' | 'security' | 'system' | 'email';
}>(async (statePartArg, dataArg) => {
}>(async (statePartArg, dataArg): Promise<ILogState> => {
const context = getActionContext();
if (!context.identity) return statePartArg.getState();
if (!context.identity) return statePartArg.getState()!;
const logsRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_GetRecentLogs
@@ -391,14 +391,14 @@ export const fetchRecentLogsAction = logStatePart.createAction<{
});
return {
...statePartArg.getState(),
...statePartArg.getState()!,
recentLogs: response.logs,
};
});
// Toggle Auto Refresh Action
export const toggleAutoRefreshAction = uiStatePart.createAction(async (statePartArg) => {
const currentState = statePartArg.getState();
export const toggleAutoRefreshAction = uiStatePart.createAction(async (statePartArg): Promise<IUiState> => {
const currentState = statePartArg.getState()!;
return {
...currentState,
autoRefresh: !currentState.autoRefresh,
@@ -406,9 +406,9 @@ export const toggleAutoRefreshAction = uiStatePart.createAction(async (statePart
});
// Set Active View Action
export const setActiveViewAction = uiStatePart.createAction<string>(async (statePartArg, viewName) => {
const currentState = statePartArg.getState();
export const setActiveViewAction = uiStatePart.createAction<string>(async (statePartArg, viewName): Promise<IUiState> => {
const currentState = statePartArg.getState()!;
// If switching to network view, ensure we fetch network data
if (viewName === 'network' && currentState.activeView !== 'network') {
setTimeout(() => {
@@ -451,9 +451,9 @@ export const setActiveViewAction = uiStatePart.createAction<string>(async (state
});
// Fetch Network Stats Action
export const fetchNetworkStatsAction = networkStatePart.createAction(async (statePartArg) => {
export const fetchNetworkStatsAction = networkStatePart.createAction(async (statePartArg): Promise<INetworkState> => {
const context = getActionContext();
const currentState = statePartArg.getState();
const currentState = statePartArg.getState()!;
if (!context.identity) return currentState;
try {
@@ -525,9 +525,9 @@ export const fetchNetworkStatsAction = networkStatePart.createAction(async (stat
// ============================================================================
// Fetch All Emails Action
export const fetchAllEmailsAction = emailOpsStatePart.createAction(async (statePartArg) => {
export const fetchAllEmailsAction = emailOpsStatePart.createAction(async (statePartArg): Promise<IEmailOpsState> => {
const context = getActionContext();
const currentState = statePartArg.getState();
const currentState = statePartArg.getState()!;
if (!context.identity) return currentState;
try {
@@ -558,9 +558,9 @@ export const fetchAllEmailsAction = emailOpsStatePart.createAction(async (stateP
// Certificate Actions
// ============================================================================
export const fetchCertificateOverviewAction = certificateStatePart.createAction(async (statePartArg) => {
export const fetchCertificateOverviewAction = certificateStatePart.createAction(async (statePartArg): Promise<ICertificateState> => {
const context = getActionContext();
const currentState = statePartArg.getState();
const currentState = statePartArg.getState()!;
if (!context.identity) return currentState;
try {
@@ -589,9 +589,9 @@ export const fetchCertificateOverviewAction = certificateStatePart.createAction(
});
export const reprovisionCertificateAction = certificateStatePart.createAction<string>(
async (statePartArg, domain, actionContext) => {
async (statePartArg, domain, actionContext): Promise<ICertificateState> => {
const context = getActionContext();
const currentState = statePartArg.getState();
const currentState = statePartArg.getState()!;
try {
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
@@ -599,13 +599,13 @@ export const reprovisionCertificateAction = certificateStatePart.createAction<st
>('/typedrequest', 'reprovisionCertificateDomain');
await request.fire({
identity: context.identity,
identity: context.identity!,
domain,
});
// Re-fetch overview after reprovisioning
return await actionContext.dispatch(fetchCertificateOverviewAction, null);
} catch (error) {
return await actionContext!.dispatch(fetchCertificateOverviewAction, null);
} catch (error: unknown) {
return {
...currentState,
error: error instanceof Error ? error.message : 'Failed to reprovision certificate',
@@ -615,9 +615,9 @@ export const reprovisionCertificateAction = certificateStatePart.createAction<st
);
export const deleteCertificateAction = certificateStatePart.createAction<string>(
async (statePartArg, domain, actionContext) => {
async (statePartArg, domain, actionContext): Promise<ICertificateState> => {
const context = getActionContext();
const currentState = statePartArg.getState();
const currentState = statePartArg.getState()!;
try {
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
@@ -625,13 +625,13 @@ export const deleteCertificateAction = certificateStatePart.createAction<string>
>('/typedrequest', 'deleteCertificate');
await request.fire({
identity: context.identity,
identity: context.identity!,
domain,
});
// Re-fetch overview after deletion
return await actionContext.dispatch(fetchCertificateOverviewAction, null);
} catch (error) {
return await actionContext!.dispatch(fetchCertificateOverviewAction, null);
} catch (error: unknown) {
return {
...currentState,
error: error instanceof Error ? error.message : 'Failed to delete certificate',
@@ -649,9 +649,9 @@ export const importCertificateAction = certificateStatePart.createAction<{
publicKey: string;
csr: string;
}>(
async (statePartArg, cert, actionContext) => {
async (statePartArg, cert, actionContext): Promise<ICertificateState> => {
const context = getActionContext();
const currentState = statePartArg.getState();
const currentState = statePartArg.getState()!;
try {
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
@@ -659,13 +659,13 @@ export const importCertificateAction = certificateStatePart.createAction<{
>('/typedrequest', 'importCertificate');
await request.fire({
identity: context.identity,
identity: context.identity!,
cert,
});
// Re-fetch overview after import
return await actionContext.dispatch(fetchCertificateOverviewAction, null);
} catch (error) {
return await actionContext!.dispatch(fetchCertificateOverviewAction, null);
} catch (error: unknown) {
return {
...currentState,
error: error instanceof Error ? error.message : 'Failed to import certificate',
@@ -681,7 +681,7 @@ export async function fetchCertificateExport(domain: string) {
>('/typedrequest', 'exportCertificate');
return request.fire({
identity: context.identity,
identity: context.identity!,
domain,
});
}
@@ -695,16 +695,16 @@ export async function fetchConnectionToken(edgeId: string) {
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_GetRemoteIngressConnectionToken
>('/typedrequest', 'getRemoteIngressConnectionToken');
return request.fire({ identity: context.identity, edgeId });
return request.fire({ identity: context.identity!, edgeId });
}
// ============================================================================
// Remote Ingress Actions
// ============================================================================
export const fetchRemoteIngressAction = remoteIngressStatePart.createAction(async (statePartArg) => {
export const fetchRemoteIngressAction = remoteIngressStatePart.createAction(async (statePartArg): Promise<IRemoteIngressState> => {
const context = getActionContext();
const currentState = statePartArg.getState();
const currentState = statePartArg.getState()!;
if (!context.identity) return currentState;
try {
@@ -743,9 +743,9 @@ export const createRemoteIngressAction = remoteIngressStatePart.createAction<{
listenPorts?: number[];
autoDerivePorts?: boolean;
tags?: string[];
}>(async (statePartArg, dataArg, actionContext) => {
}>(async (statePartArg, dataArg, actionContext): Promise<IRemoteIngressState> => {
const context = getActionContext();
const currentState = statePartArg.getState();
const currentState = statePartArg.getState()!;
try {
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
@@ -753,7 +753,7 @@ export const createRemoteIngressAction = remoteIngressStatePart.createAction<{
>('/typedrequest', 'createRemoteIngress');
const response = await request.fire({
identity: context.identity,
identity: context.identity!,
name: dataArg.name,
listenPorts: dataArg.listenPorts,
autoDerivePorts: dataArg.autoDerivePorts,
@@ -762,16 +762,16 @@ export const createRemoteIngressAction = remoteIngressStatePart.createAction<{
if (response.success) {
// Refresh the list
await actionContext.dispatch(fetchRemoteIngressAction, null);
await actionContext!.dispatch(fetchRemoteIngressAction, null);
return {
...statePartArg.getState(),
...statePartArg.getState()!,
newEdgeId: response.edge.id,
};
}
return currentState;
} catch (error) {
} catch (error: unknown) {
return {
...currentState,
error: error instanceof Error ? error.message : 'Failed to create edge',
@@ -780,9 +780,9 @@ export const createRemoteIngressAction = remoteIngressStatePart.createAction<{
});
export const deleteRemoteIngressAction = remoteIngressStatePart.createAction<string>(
async (statePartArg, edgeId, actionContext) => {
async (statePartArg, edgeId, actionContext): Promise<IRemoteIngressState> => {
const context = getActionContext();
const currentState = statePartArg.getState();
const currentState = statePartArg.getState()!;
try {
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
@@ -790,12 +790,12 @@ export const deleteRemoteIngressAction = remoteIngressStatePart.createAction<str
>('/typedrequest', 'deleteRemoteIngress');
await request.fire({
identity: context.identity,
identity: context.identity!,
id: edgeId,
});
return await actionContext.dispatch(fetchRemoteIngressAction, null);
} catch (error) {
return await actionContext!.dispatch(fetchRemoteIngressAction, null);
} catch (error: unknown) {
return {
...currentState,
error: error instanceof Error ? error.message : 'Failed to delete edge',
@@ -810,9 +810,9 @@ export const updateRemoteIngressAction = remoteIngressStatePart.createAction<{
listenPorts?: number[];
autoDerivePorts?: boolean;
tags?: string[];
}>(async (statePartArg, dataArg, actionContext) => {
}>(async (statePartArg, dataArg, actionContext): Promise<IRemoteIngressState> => {
const context = getActionContext();
const currentState = statePartArg.getState();
const currentState = statePartArg.getState()!;
try {
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
@@ -820,7 +820,7 @@ export const updateRemoteIngressAction = remoteIngressStatePart.createAction<{
>('/typedrequest', 'updateRemoteIngress');
await request.fire({
identity: context.identity,
identity: context.identity!,
id: dataArg.id,
name: dataArg.name,
listenPorts: dataArg.listenPorts,
@@ -828,8 +828,8 @@ export const updateRemoteIngressAction = remoteIngressStatePart.createAction<{
tags: dataArg.tags,
});
return await actionContext.dispatch(fetchRemoteIngressAction, null);
} catch (error) {
return await actionContext!.dispatch(fetchRemoteIngressAction, null);
} catch (error: unknown) {
return {
...currentState,
error: error instanceof Error ? error.message : 'Failed to update edge',
@@ -838,9 +838,9 @@ export const updateRemoteIngressAction = remoteIngressStatePart.createAction<{
});
export const regenerateRemoteIngressSecretAction = remoteIngressStatePart.createAction<string>(
async (statePartArg, edgeId) => {
async (statePartArg, edgeId): Promise<IRemoteIngressState> => {
const context = getActionContext();
const currentState = statePartArg.getState();
const currentState = statePartArg.getState()!;
try {
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
@@ -848,7 +848,7 @@ export const regenerateRemoteIngressSecretAction = remoteIngressStatePart.create
>('/typedrequest', 'regenerateRemoteIngressSecret');
const response = await request.fire({
identity: context.identity,
identity: context.identity!,
id: edgeId,
});
@@ -870,9 +870,9 @@ export const regenerateRemoteIngressSecretAction = remoteIngressStatePart.create
);
export const clearNewEdgeIdAction = remoteIngressStatePart.createAction(
async (statePartArg) => {
async (statePartArg): Promise<IRemoteIngressState> => {
return {
...statePartArg.getState(),
...statePartArg.getState()!,
newEdgeId: null,
};
}
@@ -881,9 +881,9 @@ export const clearNewEdgeIdAction = remoteIngressStatePart.createAction(
export const toggleRemoteIngressAction = remoteIngressStatePart.createAction<{
id: string;
enabled: boolean;
}>(async (statePartArg, dataArg, actionContext) => {
}>(async (statePartArg, dataArg, actionContext): Promise<IRemoteIngressState> => {
const context = getActionContext();
const currentState = statePartArg.getState();
const currentState = statePartArg.getState()!;
try {
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
@@ -891,13 +891,13 @@ export const toggleRemoteIngressAction = remoteIngressStatePart.createAction<{
>('/typedrequest', 'updateRemoteIngress');
await request.fire({
identity: context.identity,
identity: context.identity!,
id: dataArg.id,
enabled: dataArg.enabled,
});
return await actionContext.dispatch(fetchRemoteIngressAction, null);
} catch (error) {
return await actionContext!.dispatch(fetchRemoteIngressAction, null);
} catch (error: unknown) {
return {
...currentState,
error: error instanceof Error ? error.message : 'Failed to toggle edge',
@@ -909,9 +909,9 @@ export const toggleRemoteIngressAction = remoteIngressStatePart.createAction<{
// Route Management Actions
// ============================================================================
export const fetchMergedRoutesAction = routeManagementStatePart.createAction(async (statePartArg) => {
export const fetchMergedRoutesAction = routeManagementStatePart.createAction(async (statePartArg): Promise<IRouteManagementState> => {
const context = getActionContext();
const currentState = statePartArg.getState();
const currentState = statePartArg.getState()!;
if (!context.identity) return currentState;
try {
@@ -943,9 +943,9 @@ export const fetchMergedRoutesAction = routeManagementStatePart.createAction(asy
export const createRouteAction = routeManagementStatePart.createAction<{
route: any;
enabled?: boolean;
}>(async (statePartArg, dataArg, actionContext) => {
}>(async (statePartArg, dataArg, actionContext): Promise<IRouteManagementState> => {
const context = getActionContext();
const currentState = statePartArg.getState();
const currentState = statePartArg.getState()!;
try {
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
@@ -953,13 +953,13 @@ export const createRouteAction = routeManagementStatePart.createAction<{
>('/typedrequest', 'createRoute');
await request.fire({
identity: context.identity,
identity: context.identity!,
route: dataArg.route,
enabled: dataArg.enabled,
});
return await actionContext.dispatch(fetchMergedRoutesAction, null);
} catch (error) {
return await actionContext!.dispatch(fetchMergedRoutesAction, null);
} catch (error: unknown) {
return {
...currentState,
error: error instanceof Error ? error.message : 'Failed to create route',
@@ -968,9 +968,9 @@ export const createRouteAction = routeManagementStatePart.createAction<{
});
export const deleteRouteAction = routeManagementStatePart.createAction<string>(
async (statePartArg, routeId, actionContext) => {
async (statePartArg, routeId, actionContext): Promise<IRouteManagementState> => {
const context = getActionContext();
const currentState = statePartArg.getState();
const currentState = statePartArg.getState()!;
try {
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
@@ -978,12 +978,12 @@ export const deleteRouteAction = routeManagementStatePart.createAction<string>(
>('/typedrequest', 'deleteRoute');
await request.fire({
identity: context.identity,
identity: context.identity!,
id: routeId,
});
return await actionContext.dispatch(fetchMergedRoutesAction, null);
} catch (error) {
return await actionContext!.dispatch(fetchMergedRoutesAction, null);
} catch (error: unknown) {
return {
...currentState,
error: error instanceof Error ? error.message : 'Failed to delete route',
@@ -995,9 +995,9 @@ export const deleteRouteAction = routeManagementStatePart.createAction<string>(
export const toggleRouteAction = routeManagementStatePart.createAction<{
id: string;
enabled: boolean;
}>(async (statePartArg, dataArg, actionContext) => {
}>(async (statePartArg, dataArg, actionContext): Promise<IRouteManagementState> => {
const context = getActionContext();
const currentState = statePartArg.getState();
const currentState = statePartArg.getState()!;
try {
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
@@ -1005,13 +1005,13 @@ export const toggleRouteAction = routeManagementStatePart.createAction<{
>('/typedrequest', 'toggleRoute');
await request.fire({
identity: context.identity,
identity: context.identity!,
id: dataArg.id,
enabled: dataArg.enabled,
});
return await actionContext.dispatch(fetchMergedRoutesAction, null);
} catch (error) {
return await actionContext!.dispatch(fetchMergedRoutesAction, null);
} catch (error: unknown) {
return {
...currentState,
error: error instanceof Error ? error.message : 'Failed to toggle route',
@@ -1022,9 +1022,9 @@ export const toggleRouteAction = routeManagementStatePart.createAction<{
export const setRouteOverrideAction = routeManagementStatePart.createAction<{
routeName: string;
enabled: boolean;
}>(async (statePartArg, dataArg, actionContext) => {
}>(async (statePartArg, dataArg, actionContext): Promise<IRouteManagementState> => {
const context = getActionContext();
const currentState = statePartArg.getState();
const currentState = statePartArg.getState()!;
try {
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
@@ -1032,13 +1032,13 @@ export const setRouteOverrideAction = routeManagementStatePart.createAction<{
>('/typedrequest', 'setRouteOverride');
await request.fire({
identity: context.identity,
identity: context.identity!,
routeName: dataArg.routeName,
enabled: dataArg.enabled,
});
return await actionContext.dispatch(fetchMergedRoutesAction, null);
} catch (error) {
return await actionContext!.dispatch(fetchMergedRoutesAction, null);
} catch (error: unknown) {
return {
...currentState,
error: error instanceof Error ? error.message : 'Failed to set override',
@@ -1047,9 +1047,9 @@ export const setRouteOverrideAction = routeManagementStatePart.createAction<{
});
export const removeRouteOverrideAction = routeManagementStatePart.createAction<string>(
async (statePartArg, routeName, actionContext) => {
async (statePartArg, routeName, actionContext): Promise<IRouteManagementState> => {
const context = getActionContext();
const currentState = statePartArg.getState();
const currentState = statePartArg.getState()!;
try {
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
@@ -1057,12 +1057,12 @@ export const removeRouteOverrideAction = routeManagementStatePart.createAction<s
>('/typedrequest', 'removeRouteOverride');
await request.fire({
identity: context.identity,
identity: context.identity!,
routeName,
});
return await actionContext.dispatch(fetchMergedRoutesAction, null);
} catch (error) {
return await actionContext!.dispatch(fetchMergedRoutesAction, null);
} catch (error: unknown) {
return {
...currentState,
error: error instanceof Error ? error.message : 'Failed to remove override',
@@ -1075,9 +1075,9 @@ export const removeRouteOverrideAction = routeManagementStatePart.createAction<s
// API Token Actions
// ============================================================================
export const fetchApiTokensAction = routeManagementStatePart.createAction(async (statePartArg) => {
export const fetchApiTokensAction = routeManagementStatePart.createAction(async (statePartArg): Promise<IRouteManagementState> => {
const context = getActionContext();
const currentState = statePartArg.getState();
const currentState = statePartArg.getState()!;
if (!context.identity) return currentState;
try {
@@ -1108,7 +1108,7 @@ export async function createApiToken(name: string, scopes: interfaces.data.TApiT
>('/typedrequest', 'createApiToken');
return request.fire({
identity: context.identity,
identity: context.identity!,
name,
scopes,
expiresInDays,
@@ -1122,15 +1122,15 @@ export async function rollApiToken(id: string) {
>('/typedrequest', 'rollApiToken');
return request.fire({
identity: context.identity,
identity: context.identity!,
id,
});
}
export const revokeApiTokenAction = routeManagementStatePart.createAction<string>(
async (statePartArg, tokenId, actionContext) => {
async (statePartArg, tokenId, actionContext): Promise<IRouteManagementState> => {
const context = getActionContext();
const currentState = statePartArg.getState();
const currentState = statePartArg.getState()!;
try {
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
@@ -1138,12 +1138,12 @@ export const revokeApiTokenAction = routeManagementStatePart.createAction<string
>('/typedrequest', 'revokeApiToken');
await request.fire({
identity: context.identity,
identity: context.identity!,
id: tokenId,
});
return await actionContext.dispatch(fetchApiTokensAction, null);
} catch (error) {
return await actionContext!.dispatch(fetchApiTokensAction, null);
} catch (error: unknown) {
return {
...currentState,
error: error instanceof Error ? error.message : 'Failed to revoke token',
@@ -1155,9 +1155,9 @@ export const revokeApiTokenAction = routeManagementStatePart.createAction<string
export const toggleApiTokenAction = routeManagementStatePart.createAction<{
id: string;
enabled: boolean;
}>(async (statePartArg, dataArg, actionContext) => {
}>(async (statePartArg, dataArg, actionContext): Promise<IRouteManagementState> => {
const context = getActionContext();
const currentState = statePartArg.getState();
const currentState = statePartArg.getState()!;
try {
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
@@ -1165,13 +1165,13 @@ export const toggleApiTokenAction = routeManagementStatePart.createAction<{
>('/typedrequest', 'toggleApiToken');
await request.fire({
identity: context.identity,
identity: context.identity!,
id: dataArg.id,
enabled: dataArg.enabled,
});
return await actionContext.dispatch(fetchApiTokensAction, null);
} catch (error) {
return await actionContext!.dispatch(fetchApiTokensAction, null);
} catch (error: unknown) {
return {
...currentState,
error: error instanceof Error ? error.message : 'Failed to toggle token',
@@ -1191,13 +1191,13 @@ socketRouter.addTypedHandler(
new plugins.domtools.plugins.typedrequest.TypedHandler<interfaces.requests.IReq_PushLogEntry>(
'pushLogEntry',
async (dataArg) => {
const current = logStatePart.getState();
const current = logStatePart.getState()!;
const updated = [...current.recentLogs, dataArg.entry];
// Cap at 2000 entries
if (updated.length > 2000) {
updated.splice(0, updated.length - 2000);
}
logStatePart.setState({ ...current, recentLogs: updated });
logStatePart.setState({ ...current, recentLogs: updated } as ILogState);
return {};
}
)
@@ -1232,14 +1232,14 @@ async function disconnectSocket() {
async function dispatchCombinedRefreshAction() {
const context = getActionContext();
if (!context.identity) return;
const currentView = uiStatePart.getState().activeView;
const currentView = uiStatePart.getState()!.activeView;
try {
// Always fetch basic stats for dashboard widgets
const combinedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_GetCombinedMetrics
>('/typedrequest', 'getCombinedMetrics');
const combinedResponse = await combinedRequest.fire({
identity: context.identity,
sections: {
@@ -1252,12 +1252,13 @@ async function dispatchCombinedRefreshAction() {
});
// Update all stats from combined response
const currentStatsState = statsStatePart.getState()!;
statsStatePart.setState({
...statsStatePart.getState(),
serverStats: combinedResponse.metrics.server || statsStatePart.getState().serverStats,
emailStats: combinedResponse.metrics.email || statsStatePart.getState().emailStats,
dnsStats: combinedResponse.metrics.dns || statsStatePart.getState().dnsStats,
securityMetrics: combinedResponse.metrics.security || statsStatePart.getState().securityMetrics,
...currentStatsState,
serverStats: combinedResponse.metrics.server || currentStatsState.serverStats,
emailStats: combinedResponse.metrics.email || currentStatsState.emailStats,
dnsStats: combinedResponse.metrics.dns || currentStatsState.dnsStats,
securityMetrics: combinedResponse.metrics.security || currentStatsState.securityMetrics,
lastUpdated: Date.now(),
isLoading: false,
error: null,
@@ -1284,7 +1285,7 @@ async function dispatchCombinedRefreshAction() {
});
networkStatePart.setState({
...networkStatePart.getState(),
...networkStatePart.getState()!,
connections: connectionsResponse.connections,
connectionsByIP,
throughputRate: {
@@ -1297,14 +1298,15 @@ async function dispatchCombinedRefreshAction() {
throughputHistory: network.throughputHistory || [],
requestsPerSecond: network.requestsPerSecond || 0,
requestsTotal: network.requestsTotal || 0,
backends: network.backends || [],
lastUpdated: Date.now(),
isLoading: false,
error: null,
});
} catch (error) {
} catch (error: unknown) {
console.error('Failed to fetch connections:', error);
networkStatePart.setState({
...networkStatePart.getState(),
...networkStatePart.getState()!,
connections: [],
connectionsByIP,
throughputRate: {
@@ -1317,6 +1319,7 @@ async function dispatchCombinedRefreshAction() {
throughputHistory: network.throughputHistory || [],
requestsPerSecond: network.requestsPerSecond || 0,
requestsTotal: network.requestsTotal || 0,
backends: network.backends || [],
lastUpdated: Date.now(),
isLoading: false,
error: null,
@@ -1359,9 +1362,9 @@ let currentRefreshRate = 1000; // Track current refresh rate to avoid unnecessar
// Initialize auto-refresh when UI state is ready
(() => {
const startAutoRefresh = () => {
const uiState = uiStatePart.getState();
const loginState = loginStatePart.getState();
const uiState = uiStatePart.getState()!;
const loginState = loginStatePart.getState()!;
// Only start if conditions are met and not already running at the same rate
if (uiState.autoRefresh && loginState.isLoggedIn) {
// Check if we need to restart the interval (rate changed or not running)
@@ -1387,9 +1390,9 @@ let currentRefreshRate = 1000; // Track current refresh rate to avoid unnecessar
};
// Watch for relevant changes only
let previousAutoRefresh = uiStatePart.getState().autoRefresh;
let previousRefreshInterval = uiStatePart.getState().refreshInterval;
let previousIsLoggedIn = loginStatePart.getState().isLoggedIn;
let previousAutoRefresh = uiStatePart.getState()!.autoRefresh;
let previousRefreshInterval = uiStatePart.getState()!.refreshInterval;
let previousIsLoggedIn = loginStatePart.getState()!.isLoggedIn;
uiStatePart.state.subscribe((state) => {
// Only restart if relevant values changed
@@ -1420,7 +1423,7 @@ let currentRefreshRate = 1000; // Track current refresh rate to avoid unnecessar
startAutoRefresh();
// Connect TypedSocket if already logged in (e.g., persistent session)
if (loginStatePart.getState().isLoggedIn) {
if (loginStatePart.getState()!.isLoggedIn) {
connectSocket();
}
})();

View File

@@ -195,17 +195,18 @@ export class OpsDashboard extends DeesElement {
}
public async firstUpdated() {
const simpleLogin = this.shadowRoot.querySelector('dees-simple-login');
simpleLogin.addEventListener('login', (e: CustomEvent) => {
const simpleLogin = this.shadowRoot!.querySelector('dees-simple-login') as any;
simpleLogin.addEventListener('login', (e: Event) => {
// Handle logout event
this.login(e.detail.data.username, e.detail.data.password);
const detail = (e as CustomEvent).detail;
this.login(detail.data.username, detail.data.password);
});
// Handle view changes
const appDash = this.shadowRoot.querySelector('dees-simple-appdash');
const appDash = this.shadowRoot!.querySelector('dees-simple-appdash');
if (appDash) {
appDash.addEventListener('view-select', (e: CustomEvent) => {
const viewName = e.detail.view.name.toLowerCase();
appDash.addEventListener('view-select', (e: Event) => {
const viewName = (e as CustomEvent).detail.view.name.toLowerCase();
// Use router for navigation instead of direct state update
appRouter.navigateToView(viewName);
});
@@ -217,7 +218,7 @@ export class OpsDashboard extends DeesElement {
}
// Handle initial state - check if we have a stored session that's still valid
const loginState = appstate.loginStatePart.getState();
const loginState = appstate.loginStatePart.getState()!;
if (loginState.identity?.jwt) {
if (loginState.identity.expiresAt > Date.now()) {
// Client-side expiry looks valid — verify with server (keypair may have changed)
@@ -229,7 +230,7 @@ export class OpsDashboard extends DeesElement {
if (response.valid) {
// JWT confirmed valid by server
this.loginState = loginState;
await simpleLogin.switchToSlottedContent();
await (simpleLogin as any).switchToSlottedContent();
await appstate.statsStatePart.dispatchAction(appstate.fetchAllStatsAction, null);
await appstate.configStatePart.dispatchAction(appstate.fetchConfigurationAction, null);
} else {
@@ -250,8 +251,8 @@ export class OpsDashboard extends DeesElement {
private async login(username: string, password: string) {
const domtools = await this.domtoolsPromise;
console.log(`Attempting to login...`);
const simpleLogin = this.shadowRoot.querySelector('dees-simple-login');
const form = simpleLogin.shadowRoot.querySelector('dees-form');
const simpleLogin = this.shadowRoot!.querySelector('dees-simple-login') as any;
const form = simpleLogin.shadowRoot!.querySelector('dees-form') as any;
form.setStatus('pending', 'Logging in...');
const state = await appstate.loginStatePart.dispatchAction(appstate.loginAction, {
@@ -262,14 +263,14 @@ export class OpsDashboard extends DeesElement {
if (state.identity) {
console.log('Login successful');
this.loginState = state;
form.setStatus('success', 'Logged in!');
await simpleLogin.switchToSlottedContent();
form!.setStatus('success', 'Logged in!');
await simpleLogin!.switchToSlottedContent();
await appstate.statsStatePart.dispatchAction(appstate.fetchAllStatsAction, null);
await appstate.configStatePart.dispatchAction(appstate.fetchConfigurationAction, null);
} else {
form.setStatus('error', 'Login failed!');
form!.setStatus('error', 'Login failed!');
await domtools.convenience.smartdelay.delayFor(2000);
form.reset();
form!.reset();
}
}
}

View File

@@ -21,7 +21,7 @@ declare global {
@customElement('ops-view-certificates')
export class OpsViewCertificates extends DeesElement {
@state()
accessor certState: appstate.ICertificateState = appstate.certificateStatePart.getState();
accessor certState: appstate.ICertificateState = appstate.certificateStatePart.getState()!;
constructor() {
super();
@@ -264,10 +264,10 @@ export class OpsViewCertificates extends DeesElement {
{
name: 'Import',
iconName: 'lucide:upload',
action: async (modal) => {
action: async (modal: any) => {
const { DeesToast } = await import('@design.estate/dees-catalog');
try {
const form = modal.shadowRoot.querySelector('dees-form') as any;
const form = modal.shadowRoot!.querySelector('dees-form') as any;
const formData = await form.collectFormData();
const files = formData.certJsonFile;
if (!files || files.length === 0) {
@@ -287,8 +287,8 @@ export class OpsViewCertificates extends DeesElement {
);
DeesToast.show({ message: `Certificate imported for ${cert.domainName}`, type: 'success', duration: 3000 });
modal.destroy();
} catch (err) {
DeesToast.show({ message: `Import failed: ${err.message}`, type: 'error', duration: 4000 });
} catch (err: unknown) {
DeesToast.show({ message: `Import failed: ${(err as Error).message}`, type: 'error', duration: 4000 });
}
},
},
@@ -339,8 +339,8 @@ export class OpsViewCertificates extends DeesElement {
} else {
DeesToast.show({ message: response.message || 'Export failed', type: 'error', duration: 4000 });
}
} catch (err) {
DeesToast.show({ message: `Export failed: ${err.message}`, type: 'error', duration: 4000 });
} catch (err: unknown) {
DeesToast.show({ message: `Export failed: ${(err as Error).message}`, type: 'error', duration: 4000 });
}
},
},
@@ -363,7 +363,7 @@ export class OpsViewCertificates extends DeesElement {
{
name: 'Delete',
iconName: 'lucide:trash-2',
action: async (modal) => {
action: async (modal: any) => {
try {
await appstate.certificateStatePart.dispatchAction(
appstate.deleteCertificateAction,
@@ -371,8 +371,8 @@ export class OpsViewCertificates extends DeesElement {
);
DeesToast.show({ message: `Certificate deleted for ${cert.domain}`, type: 'success', duration: 3000 });
modal.destroy();
} catch (err) {
DeesToast.show({ message: `Delete failed: ${err.message}`, type: 'error', duration: 4000 });
} catch (err: unknown) {
DeesToast.show({ message: `Delete failed: ${(err as Error).message}`, type: 'error', duration: 4000 });
}
},
},

View File

@@ -102,7 +102,7 @@ export class OpsViewConfig extends DeesElement {
`;
}
private renderSystemSection(sys: appstate.IConfigState['config']['system']): TemplateResult {
private renderSystemSection(sys: NonNullable<appstate.IConfigState['config']>['system']): TemplateResult {
// Annotate proxy IPs with source hint when Remote Ingress is active
const ri = this.configState.config?.remoteIngress;
let proxyIpValues: string[] | null = sys.proxyIps.length > 0 ? [...sys.proxyIps] : null;
@@ -133,7 +133,7 @@ export class OpsViewConfig extends DeesElement {
`;
}
private renderSmartProxySection(proxy: appstate.IConfigState['config']['smartProxy']): TemplateResult {
private renderSmartProxySection(proxy: NonNullable<appstate.IConfigState['config']>['smartProxy']): TemplateResult {
const fields: IConfigField[] = [
{ key: 'Route Count', value: proxy.routeCount },
];
@@ -164,7 +164,7 @@ export class OpsViewConfig extends DeesElement {
`;
}
private renderEmailSection(email: appstate.IConfigState['config']['email']): TemplateResult {
private renderEmailSection(email: NonNullable<appstate.IConfigState['config']>['email']): TemplateResult {
const fields: IConfigField[] = [
{ key: 'Ports', value: email.ports.length > 0 ? email.ports.map(String) : null, type: 'pills' },
{ key: 'Hostname', value: email.hostname },
@@ -196,7 +196,7 @@ export class OpsViewConfig extends DeesElement {
`;
}
private renderDnsSection(dns: appstate.IConfigState['config']['dns']): TemplateResult {
private renderDnsSection(dns: NonNullable<appstate.IConfigState['config']>['dns']): TemplateResult {
const fields: IConfigField[] = [
{ key: 'Port', value: dns.port },
{ key: 'NS Domains', value: dns.nsDomains.length > 0 ? dns.nsDomains : null, type: 'pills' },
@@ -216,7 +216,7 @@ export class OpsViewConfig extends DeesElement {
`;
}
private renderTlsSection(tls: appstate.IConfigState['config']['tls']): TemplateResult {
private renderTlsSection(tls: NonNullable<appstate.IConfigState['config']>['tls']): TemplateResult {
const fields: IConfigField[] = [
{ key: 'Contact Email', value: tls.contactEmail },
{ key: 'Domain', value: tls.domain },
@@ -242,7 +242,7 @@ export class OpsViewConfig extends DeesElement {
`;
}
private renderCacheSection(cache: appstate.IConfigState['config']['cache']): TemplateResult {
private renderCacheSection(cache: NonNullable<appstate.IConfigState['config']>['cache']): TemplateResult {
const fields: IConfigField[] = [
{ key: 'Storage Path', value: cache.storagePath },
{ key: 'DB Name', value: cache.dbName },
@@ -267,7 +267,7 @@ export class OpsViewConfig extends DeesElement {
`;
}
private renderRadiusSection(radius: appstate.IConfigState['config']['radius']): TemplateResult {
private renderRadiusSection(radius: NonNullable<appstate.IConfigState['config']>['radius']): TemplateResult {
const fields: IConfigField[] = [
{ key: 'Auth Port', value: radius.authPort },
{ key: 'Accounting Port', value: radius.acctPort },
@@ -296,7 +296,7 @@ export class OpsViewConfig extends DeesElement {
`;
}
private renderRemoteIngressSection(ri: appstate.IConfigState['config']['remoteIngress']): TemplateResult {
private renderRemoteIngressSection(ri: NonNullable<appstate.IConfigState['config']>['remoteIngress']): TemplateResult {
const fields: IConfigField[] = [
{ key: 'Tunnel Port', value: ri.tunnelPort },
{ key: 'Hub Domain', value: ri.hubDomain },

View File

@@ -83,13 +83,13 @@ export class OpsViewEmails extends DeesElement {
private async handleEmailClick(e: CustomEvent<interfaces.requests.IEmail>) {
const emailSummary = e.detail;
try {
const context = appstate.loginStatePart.getState();
const context = appstate.loginStatePart.getState()!;
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_GetEmailDetail
>('/typedrequest', 'getEmailDetail');
const response = await request.fire({
identity: context.identity,
identity: context.identity!,
emailId: emailSummary.id,
});

View File

@@ -29,10 +29,10 @@ interface INetworkRequest {
@customElement('ops-view-network')
export class OpsViewNetwork extends DeesElement {
@state()
accessor statsState = appstate.statsStatePart.getState();
accessor statsState = appstate.statsStatePart.getState()!;
@state()
accessor networkState = appstate.networkStatePart.getState();
accessor networkState = appstate.networkStatePart.getState()!;
@state()

View File

@@ -21,7 +21,7 @@ declare global {
@customElement('ops-view-remoteingress')
export class OpsViewRemoteIngress extends DeesElement {
@state()
accessor riState: appstate.IRemoteIngressState = appstate.remoteIngressStatePart.getState();
accessor riState: appstate.IRemoteIngressState = appstate.remoteIngressStatePart.getState()!;
constructor() {
super();
@@ -184,7 +184,7 @@ export class OpsViewRemoteIngress extends DeesElement {
@click=${async () => {
const { DeesToast } = await import('@design.estate/dees-catalog');
try {
const response = await appstate.fetchConnectionToken(this.riState.newEdgeId);
const response = await appstate.fetchConnectionToken(this.riState.newEdgeId!);
if (response.success && response.token) {
if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
await navigator.clipboard.writeText(response.token);
@@ -202,8 +202,8 @@ export class OpsViewRemoteIngress extends DeesElement {
} else {
DeesToast.show({ message: response.message || 'Failed to get token', type: 'error', duration: 4000 });
}
} catch (err) {
DeesToast.show({ message: `Failed: ${err.message}`, type: 'error', duration: 4000 });
} catch (err: unknown) {
DeesToast.show({ message: `Failed: ${(err as Error).message}`, type: 'error', duration: 4000 });
}
}}
>Copy Connection Token</dees-button>
@@ -399,8 +399,8 @@ export class OpsViewRemoteIngress extends DeesElement {
} else {
DeesToast.show({ message: response.message || 'Failed to get token', type: 'error', duration: 4000 });
}
} catch (err) {
DeesToast.show({ message: `Failed: ${err.message}`, type: 'error', duration: 4000 });
} catch (err: unknown) {
DeesToast.show({ message: `Failed: ${(err as Error).message}`, type: 'error', duration: 4000 });
}
},
},

View File

@@ -237,7 +237,7 @@ export class OpsViewMyView extends DeesElement {
## License and Legal Information
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE](../LICENSE) file.
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [license](../license) file.
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.

View File

@@ -71,12 +71,12 @@ class AppRouter {
private updateViewState(view: string): void {
this.suppressStateUpdate = true;
const currentState = appstate.uiStatePart.getState();
const currentState = appstate.uiStatePart.getState()!;
if (currentState.activeView !== view) {
appstate.uiStatePart.setState({
...currentState,
activeView: view,
});
} as appstate.IUiState);
}
this.suppressStateUpdate = false;
}
@@ -94,7 +94,7 @@ class AppRouter {
}
public getCurrentView(): string {
return appstate.uiStatePart.getState().activeView;
return appstate.uiStatePart.getState()!.activeView;
}
public destroy(): void {