refactor(store): split gatewayStore into specialized domain stores
Major restructuring: - Split monolithic gatewayStore into 5 focused stores: - connectionStore: WebSocket connection and gateway lifecycle - configStore: quickConfig, workspaceInfo, MCP services - agentStore: clones, usage stats, agent management - handStore: hands, approvals, triggers, hand runs - workflowStore: workflows, workflow runs, execution - Update all components to use new stores with selector pattern - Remove
This commit is contained in:
141
desktop/src/store/securityStore.ts
Normal file
141
desktop/src/store/securityStore.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
/**
|
||||
* securityStore.ts - Security Status and Audit Log Management
|
||||
*
|
||||
* Extracted from gatewayStore.ts for Store Refactoring.
|
||||
* Manages OpenFang security layers, security status, and audit logs.
|
||||
*/
|
||||
import { create } from 'zustand';
|
||||
import type { GatewayClient } from '../lib/gateway-client';
|
||||
|
||||
// === Types ===
|
||||
|
||||
export interface SecurityLayer {
|
||||
name: string;
|
||||
enabled: boolean;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface SecurityStatus {
|
||||
layers: SecurityLayer[];
|
||||
enabledCount: number;
|
||||
totalCount: number;
|
||||
securityLevel: 'critical' | 'high' | 'medium' | 'low';
|
||||
}
|
||||
|
||||
export interface AuditLogEntry {
|
||||
id: string;
|
||||
timestamp: string;
|
||||
action: string;
|
||||
actor?: string;
|
||||
result?: 'success' | 'failure';
|
||||
details?: Record<string, unknown>;
|
||||
// Merkle hash chain fields (OpenFang)
|
||||
hash?: string;
|
||||
previousHash?: string;
|
||||
}
|
||||
|
||||
// === Helpers ===
|
||||
|
||||
function calculateSecurityLevel(enabledCount: number, totalCount: number): 'critical' | 'high' | 'medium' | 'low' {
|
||||
if (totalCount === 0) return 'low';
|
||||
const ratio = enabledCount / totalCount;
|
||||
if (ratio >= 0.875) return 'critical'; // 14-16 layers
|
||||
if (ratio >= 0.625) return 'high'; // 10-13 layers
|
||||
if (ratio >= 0.375) return 'medium'; // 6-9 layers
|
||||
return 'low'; // 0-5 layers
|
||||
}
|
||||
|
||||
// === Client Interface ===
|
||||
|
||||
interface SecurityClient {
|
||||
getSecurityStatus(): Promise<{ layers?: SecurityLayer[] } | null>;
|
||||
getAuditLogs(opts?: { limit?: number; offset?: number }): Promise<{ logs?: AuditLogEntry[] } | null>;
|
||||
}
|
||||
|
||||
// === Store Interface ===
|
||||
|
||||
export interface SecurityStateSlice {
|
||||
securityStatus: SecurityStatus | null;
|
||||
securityStatusLoading: boolean;
|
||||
securityStatusError: string | null;
|
||||
auditLogs: AuditLogEntry[];
|
||||
auditLogsLoading: boolean;
|
||||
}
|
||||
|
||||
export interface SecurityActionsSlice {
|
||||
loadSecurityStatus: () => Promise<void>;
|
||||
loadAuditLogs: (opts?: { limit?: number; offset?: number }) => Promise<void>;
|
||||
}
|
||||
|
||||
export type SecurityStore = SecurityStateSlice & SecurityActionsSlice & { client: SecurityClient | null };
|
||||
|
||||
// === Store Implementation ===
|
||||
|
||||
export const useSecurityStore = create<SecurityStore>((set, get) => ({
|
||||
// Initial state
|
||||
securityStatus: null,
|
||||
securityStatusLoading: false,
|
||||
securityStatusError: null,
|
||||
auditLogs: [],
|
||||
auditLogsLoading: false,
|
||||
client: null,
|
||||
|
||||
loadSecurityStatus: async () => {
|
||||
const client = get().client;
|
||||
if (!client) return;
|
||||
|
||||
set({ securityStatusLoading: true, securityStatusError: null });
|
||||
try {
|
||||
const result = await client.getSecurityStatus();
|
||||
if (result?.layers) {
|
||||
const layers = result.layers as SecurityLayer[];
|
||||
const enabledCount = layers.filter(l => l.enabled).length;
|
||||
const totalCount = layers.length;
|
||||
const securityLevel = calculateSecurityLevel(enabledCount, totalCount);
|
||||
set({
|
||||
securityStatus: { layers, enabledCount, totalCount, securityLevel },
|
||||
securityStatusLoading: false,
|
||||
securityStatusError: null,
|
||||
});
|
||||
} else {
|
||||
set({
|
||||
securityStatusLoading: false,
|
||||
securityStatusError: 'API returned no data',
|
||||
});
|
||||
}
|
||||
} catch (err: unknown) {
|
||||
set({
|
||||
securityStatusLoading: false,
|
||||
securityStatusError: (err instanceof Error ? err.message : String(err)) || 'Security API not available',
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
loadAuditLogs: async (opts?: { limit?: number; offset?: number }) => {
|
||||
const client = get().client;
|
||||
if (!client) return;
|
||||
|
||||
set({ auditLogsLoading: true });
|
||||
try {
|
||||
const result = await client.getAuditLogs(opts);
|
||||
set({ auditLogs: (result?.logs || []) as AuditLogEntry[], auditLogsLoading: false });
|
||||
} catch {
|
||||
set({ auditLogsLoading: false });
|
||||
/* ignore if audit API not available */
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
// === Client Injection ===
|
||||
|
||||
function createSecurityClientFromGateway(client: GatewayClient): SecurityClient {
|
||||
return {
|
||||
getSecurityStatus: () => client.getSecurityStatus() as Promise<{ layers?: SecurityLayer[] } | null>,
|
||||
getAuditLogs: (opts) => client.getAuditLogs(opts) as Promise<{ logs?: AuditLogEntry[] } | null>,
|
||||
};
|
||||
}
|
||||
|
||||
export function setSecurityStoreClient(client: unknown): void {
|
||||
const securityClient = createSecurityClientFromGateway(client as GatewayClient);
|
||||
useSecurityStore.setState({ client: securityClient });
|
||||
}
|
||||
Reference in New Issue
Block a user