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:
iven
2026-03-20 22:14:13 +08:00
parent 6f72442531
commit 1cf3f585d3
43 changed files with 2826 additions and 3103 deletions

View 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 });
}