361 lines
9.1 KiB
TypeScript
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();
|
|
})(); |