Files
dcrouter/ts_web/appstate.ts
2025-06-08 12:39:53 +00:00

361 lines
9.1 KiB
TypeScript

import * as plugins from './plugins.js';
import * as interfaces from '../dist_ts_interfaces/index.js';
// Create main app state instance
export const appState = new plugins.domtools.plugins.smartstate.Smartstate();
// Define state interfaces
export interface ILoginState {
identity: interfaces.data.IIdentity | null;
isLoggedIn: boolean;
}
export interface IStatsState {
serverStats: interfaces.data.IServerStats | null;
emailStats: interfaces.data.IEmailStats | null;
dnsStats: interfaces.data.IDnsStats | null;
securityMetrics: interfaces.data.ISecurityMetrics | null;
lastUpdated: number;
isLoading: boolean;
error: string | null;
}
export interface IConfigState {
config: any | null;
isLoading: boolean;
error: string | null;
}
export interface IUiState {
activeView: string;
sidebarCollapsed: boolean;
autoRefresh: boolean;
refreshInterval: number; // milliseconds
theme: 'light' | 'dark';
}
export interface ILogState {
recentLogs: interfaces.data.ILogEntry[];
isStreaming: boolean;
filters: {
level?: string[];
category?: string[];
};
}
// Create state parts with appropriate persistence
export const loginStatePart = await appState.getStatePart<ILoginState>(
'login',
{
identity: null,
isLoggedIn: false,
},
'soft' // Login state persists across sessions
);
export const statsStatePart = await appState.getStatePart<IStatsState>(
'stats',
{
serverStats: null,
emailStats: null,
dnsStats: null,
securityMetrics: null,
lastUpdated: 0,
isLoading: false,
error: null,
},
'soft' // Stats are cached but not persisted
);
export const configStatePart = await appState.getStatePart<IConfigState>(
'config',
{
config: null,
isLoading: false,
error: null,
}
);
export const uiStatePart = await appState.getStatePart<IUiState>(
'ui',
{
activeView: 'dashboard',
sidebarCollapsed: false,
autoRefresh: true,
refreshInterval: 30000, // 30 seconds
theme: 'light',
},
);
export const logStatePart = await appState.getStatePart<ILogState>(
'logs',
{
recentLogs: [],
isStreaming: false,
filters: {},
},
'soft'
);
// Actions for state management
interface IActionContext {
identity: interfaces.data.IIdentity | null;
}
const getActionContext = (): IActionContext => {
return {
identity: loginStatePart.getState().identity,
};
};
// Login Action
export const loginAction = loginStatePart.createAction<{
username: string;
password: string;
}>(async (statePartArg, dataArg) => {
const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_AdminLoginWithUsernameAndPassword
>('/typedrequest', 'adminLoginWithUsernameAndPassword');
try {
const response = await typedRequest.fire({
username: dataArg.username,
password: dataArg.password,
});
if (response.identity) {
return {
identity: response.identity,
isLoggedIn: true,
};
}
return statePartArg.getState();
} catch (error) {
console.error('Login failed:', error);
return statePartArg.getState();
}
});
// Logout Action
export const logoutAction = loginStatePart.createAction(async (statePartArg) => {
const context = getActionContext();
if (!context.identity) return statePartArg.getState();
const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_AdminLogout
>('/typedrequest', 'adminLogout');
try {
await typedRequest.fire({
identity: context.identity,
});
} catch (error) {
console.error('Logout error:', error);
}
// Clear login state regardless
return {
identity: null,
isLoggedIn: false,
};
});
// Fetch All Stats Action
export const fetchAllStatsAction = statsStatePart.createAction(async (statePartArg) => {
const context = getActionContext();
const currentState = statePartArg.getState();
try {
// Fetch server stats
const serverStatsRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_GetServerStatistics
>('/typedrequest', 'getServerStatistics');
const serverStatsResponse = await serverStatsRequest.fire({
identity: context.identity,
includeHistory: false,
});
// Fetch email stats
const emailStatsRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_GetEmailStatistics
>('/typedrequest', 'getEmailStatistics');
const emailStatsResponse = await emailStatsRequest.fire({
identity: context.identity,
});
// Fetch DNS stats
const dnsStatsRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_GetDnsStatistics
>('/typedrequest', 'getDnsStatistics');
const dnsStatsResponse = await dnsStatsRequest.fire({
identity: context.identity,
});
// Fetch security metrics
const securityRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_GetSecurityMetrics
>('/typedrequest', 'getSecurityMetrics');
const securityResponse = await securityRequest.fire({
identity: context.identity,
});
// Update state with all stats
return {
serverStats: serverStatsResponse.stats,
emailStats: emailStatsResponse.stats,
dnsStats: dnsStatsResponse.stats,
securityMetrics: securityResponse.metrics,
lastUpdated: Date.now(),
isLoading: false,
error: null,
};
} catch (error) {
return {
...currentState,
isLoading: false,
error: error.message || 'Failed to fetch statistics',
};
}
});
// Fetch Configuration Action
export const fetchConfigurationAction = configStatePart.createAction(async (statePartArg) => {
const context = getActionContext();
const currentState = statePartArg.getState();
try {
const configRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_GetConfiguration
>('/typedrequest', 'getConfiguration');
const response = await configRequest.fire({
identity: context.identity,
});
return {
config: response.config,
isLoading: false,
error: null,
};
} catch (error) {
return {
...currentState,
isLoading: false,
error: error.message || 'Failed to fetch configuration',
};
}
});
// Update Configuration Action
export const updateConfigurationAction = configStatePart.createAction<{
section: string;
config: any;
}>(async (statePartArg, dataArg) => {
const context = getActionContext();
if (!context.identity) {
throw new Error('Must be logged in to update configuration');
}
const updateRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_UpdateConfiguration
>('/typedrequest', 'updateConfiguration');
const response = await updateRequest.fire({
identity: context.identity,
section: dataArg.section,
config: dataArg.config,
});
if (response.updated) {
// Refresh configuration
await configStatePart.dispatchAction(fetchConfigurationAction, null);
return statePartArg.getState();
}
return statePartArg.getState();
});
// Fetch Recent Logs Action
export const fetchRecentLogsAction = logStatePart.createAction<{
limit?: number;
level?: 'debug' | 'info' | 'warn' | 'error';
category?: 'smtp' | 'dns' | 'security' | 'system' | 'email';
}>(async (statePartArg, dataArg) => {
const context = getActionContext();
const logsRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_GetRecentLogs
>('/typedrequest', 'getRecentLogs');
const response = await logsRequest.fire({
identity: context.identity,
limit: dataArg.limit || 100,
level: dataArg.level,
category: dataArg.category,
});
return {
...statePartArg.getState(),
recentLogs: response.logs,
};
});
// Toggle Auto Refresh Action
export const toggleAutoRefreshAction = uiStatePart.createAction(async (statePartArg) => {
const currentState = statePartArg.getState();
return {
...currentState,
autoRefresh: !currentState.autoRefresh,
};
});
// Set Active View Action
export const setActiveViewAction = uiStatePart.createAction<string>(async (statePartArg, viewName) => {
const currentState = statePartArg.getState();
return {
...currentState,
activeView: viewName,
};
});
// Initialize auto-refresh
let refreshInterval: NodeJS.Timeout | null = null;
// Initialize auto-refresh when UI state is ready
(() => {
const startAutoRefresh = () => {
const uiState = uiStatePart.getState();
if (uiState.autoRefresh && loginStatePart.getState().isLoggedIn) {
refreshInterval = setInterval(() => {
statsStatePart.dispatchAction(fetchAllStatsAction, null);
}, uiState.refreshInterval);
}
};
const stopAutoRefresh = () => {
if (refreshInterval) {
clearInterval(refreshInterval);
refreshInterval = null;
}
};
// Watch for changes
uiStatePart.state.subscribe(() => {
stopAutoRefresh();
startAutoRefresh();
});
loginStatePart.state.subscribe(() => {
stopAutoRefresh();
startAutoRefresh();
});
// Initial start
startAutoRefresh();
})();