feat(phase2): complete P1 tasks - Channels, Triggers, Skills CRUD and UI enhancements
Phase 2 P1 Tasks Completed: API Layer (gateway-client.ts, gatewayStore.ts): - Add Channels CRUD: getChannel, createChannel, updateChannel, deleteChannel - Add Triggers CRUD: getTrigger, createTrigger, updateTrigger, deleteTrigger - Add Skills CRUD: getSkill, createSkill, updateSkill, deleteSkill - Add Scheduled Tasks API: createScheduledTask, deleteScheduledTask, toggleScheduledTask - Add loadModels action for dynamic model list UI Components: - ModelsAPI.tsx: Dynamic model loading from API with loading/error states - SchedulerPanel.tsx: Full CreateJobModal with cron/interval/once scheduling - SecurityStatus.tsx: Loading states, error handling, retry functionality - WorkflowEditor.tsx: New workflow creation/editing modal (new file) - WorkflowHistory.tsx: Workflow execution history viewer (new file) - WorkflowList.tsx: Integrated editor and history access Configuration: - Add 4 Hands TOML configs: clip, collector, predictor, twitter Documentation (SYSTEM_ANALYSIS.md): - Update API coverage: 65% → 89% (53/62 endpoints) - Update UI completion: 85% → 92% - Mark Phase 2 P1 tasks as completed - Update technical debt cleanup status Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { useEffect } from 'react';
|
||||
import { Shield, ShieldCheck, ShieldAlert, ShieldX, RefreshCw } from 'lucide-react';
|
||||
import { Shield, ShieldCheck, ShieldAlert, ShieldX, RefreshCw, Loader2, AlertCircle } from 'lucide-react';
|
||||
import { useGatewayStore } from '../store/gatewayStore';
|
||||
|
||||
// OpenFang 16-layer security architecture names (Chinese)
|
||||
@@ -25,6 +25,7 @@ const SECURITY_LAYER_NAMES: Record<string, string> = {
|
||||
// Layer 6: Audit & Logging
|
||||
'audit.logging': '审计日志',
|
||||
'audit.tracing': '请求追踪',
|
||||
'audit.alerting': '审计告警',
|
||||
};
|
||||
|
||||
// Default 16 layers for display when API returns minimal data
|
||||
@@ -74,7 +75,13 @@ function getSecurityLabel(level: 'critical' | 'high' | 'medium' | 'low') {
|
||||
}
|
||||
|
||||
export function SecurityStatus() {
|
||||
const { connectionState, securityStatus, loadSecurityStatus } = useGatewayStore();
|
||||
const {
|
||||
connectionState,
|
||||
securityStatus,
|
||||
securityStatusLoading,
|
||||
securityStatusError,
|
||||
loadSecurityStatus,
|
||||
} = useGatewayStore();
|
||||
const connected = connectionState === 'connected';
|
||||
|
||||
useEffect(() => {
|
||||
@@ -95,6 +102,44 @@ export function SecurityStatus() {
|
||||
);
|
||||
}
|
||||
|
||||
// Loading state
|
||||
if (securityStatusLoading && !securityStatus) {
|
||||
return (
|
||||
<div className="rounded-xl border border-gray-200 bg-white p-4 shadow-sm">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<Loader2 className="w-4 h-4 text-gray-400 animate-spin" />
|
||||
<span className="text-sm font-semibold text-gray-900">安全状态</span>
|
||||
</div>
|
||||
<p className="text-xs text-gray-400">加载中...</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// API error state - show friendly message
|
||||
if (securityStatusError && !securityStatus) {
|
||||
return (
|
||||
<div className="rounded-xl border border-gray-200 bg-white p-4 shadow-sm">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<AlertCircle className="w-4 h-4 text-yellow-500" />
|
||||
<span className="text-sm font-semibold text-gray-900">安全状态</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => loadSecurityStatus()}
|
||||
className="p-1 text-gray-400 hover:text-orange-500 rounded transition-colors"
|
||||
title="重试"
|
||||
>
|
||||
<RefreshCw className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 mb-2">API 不可用</p>
|
||||
<p className="text-xs text-gray-400">
|
||||
OpenFang 安全状态 API ({'/api/security/status'}) 在当前版本可能未实现
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Use default layers if no data, or merge with API data
|
||||
const displayLayers = securityStatus?.layers?.length
|
||||
? DEFAULT_LAYERS.map((defaultLayer) => {
|
||||
@@ -117,6 +162,9 @@ export function SecurityStatus() {
|
||||
<div className="flex items-center gap-2">
|
||||
{getSecurityIcon(securityLevel)}
|
||||
<span className="text-sm font-semibold text-gray-900">安全状态</span>
|
||||
{securityStatusLoading && (
|
||||
<Loader2 className="w-3 h-3 text-gray-400 animate-spin" />
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className={`text-xs px-2 py-0.5 rounded-full border ${levelLabel.color}`}>
|
||||
@@ -124,8 +172,9 @@ export function SecurityStatus() {
|
||||
</span>
|
||||
<button
|
||||
onClick={() => loadSecurityStatus()}
|
||||
className="p-1 text-gray-400 hover:text-orange-500 rounded transition-colors"
|
||||
className="p-1 text-gray-400 hover:text-orange-500 rounded transition-colors disabled:opacity-50"
|
||||
title="刷新安全状态"
|
||||
disabled={securityStatusLoading}
|
||||
>
|
||||
<RefreshCw className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user