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:
iven
2026-03-15 01:38:34 +08:00
parent 1f9b6553fc
commit 5599c1a4db
15 changed files with 3216 additions and 296 deletions

View File

@@ -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>