首页布局优化前
This commit is contained in:
@@ -1,13 +1,15 @@
|
||||
/**
|
||||
* SchedulerPanel - OpenFang Scheduler UI
|
||||
*
|
||||
* Displays scheduled jobs, event triggers, and run history.
|
||||
* Displays scheduled jobs, event triggers, workflows, and run history.
|
||||
*
|
||||
* Design based on OpenFang Dashboard v0.4.0
|
||||
*/
|
||||
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { useGatewayStore } from '../store/gatewayStore';
|
||||
import { useGatewayStore, type Workflow } from '../store/gatewayStore';
|
||||
import { WorkflowEditor } from './WorkflowEditor';
|
||||
import { WorkflowHistory } from './WorkflowHistory';
|
||||
import {
|
||||
Clock,
|
||||
Zap,
|
||||
@@ -19,11 +21,13 @@ import {
|
||||
X,
|
||||
AlertCircle,
|
||||
CheckCircle,
|
||||
GitBranch,
|
||||
Play,
|
||||
} from 'lucide-react';
|
||||
|
||||
// === Tab Types ===
|
||||
|
||||
type TabType = 'scheduled' | 'triggers' | 'history';
|
||||
type TabType = 'scheduled' | 'triggers' | 'workflows' | 'history';
|
||||
|
||||
// === Schedule Type ===
|
||||
|
||||
@@ -632,13 +636,26 @@ function CreateJobModal({ isOpen, onClose, onSuccess }: CreateJobModalProps) {
|
||||
// === Main SchedulerPanel Component ===
|
||||
|
||||
export function SchedulerPanel() {
|
||||
const { scheduledTasks, loadScheduledTasks, isLoading } = useGatewayStore();
|
||||
const {
|
||||
scheduledTasks,
|
||||
loadScheduledTasks,
|
||||
workflows,
|
||||
loadWorkflows,
|
||||
createWorkflow,
|
||||
executeWorkflow,
|
||||
isLoading,
|
||||
} = useGatewayStore();
|
||||
const [activeTab, setActiveTab] = useState<TabType>('scheduled');
|
||||
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
|
||||
const [isWorkflowEditorOpen, setIsWorkflowEditorOpen] = useState(false);
|
||||
const [editingWorkflow, setEditingWorkflow] = useState<Workflow | undefined>(undefined);
|
||||
const [selectedWorkflow, setSelectedWorkflow] = useState<Workflow | null>(null);
|
||||
const [isSavingWorkflow, setIsSavingWorkflow] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
loadScheduledTasks();
|
||||
}, [loadScheduledTasks]);
|
||||
loadWorkflows();
|
||||
}, [loadScheduledTasks, loadWorkflows]);
|
||||
|
||||
const handleCreateJob = useCallback(() => {
|
||||
setIsCreateModalOpen(true);
|
||||
@@ -653,6 +670,60 @@ export function SchedulerPanel() {
|
||||
loadScheduledTasks();
|
||||
}, [loadScheduledTasks]);
|
||||
|
||||
// Workflow handlers
|
||||
const handleCreateWorkflow = useCallback(() => {
|
||||
setEditingWorkflow(undefined);
|
||||
setIsWorkflowEditorOpen(true);
|
||||
}, []);
|
||||
|
||||
const handleEditWorkflow = useCallback((workflow: Workflow) => {
|
||||
setEditingWorkflow(workflow);
|
||||
setIsWorkflowEditorOpen(true);
|
||||
}, []);
|
||||
|
||||
const handleViewWorkflowHistory = useCallback((workflow: Workflow) => {
|
||||
setSelectedWorkflow(workflow);
|
||||
}, []);
|
||||
|
||||
const handleSaveWorkflow = useCallback(async (data: {
|
||||
name: string;
|
||||
description?: string;
|
||||
steps: Array<{
|
||||
handName: string;
|
||||
name?: string;
|
||||
params?: Record<string, unknown>;
|
||||
condition?: string;
|
||||
}>;
|
||||
}) => {
|
||||
setIsSavingWorkflow(true);
|
||||
try {
|
||||
if (editingWorkflow) {
|
||||
// Update existing workflow
|
||||
console.log('Update workflow:', editingWorkflow.id, data);
|
||||
} else {
|
||||
// Create new workflow
|
||||
await createWorkflow(data);
|
||||
}
|
||||
setIsWorkflowEditorOpen(false);
|
||||
setEditingWorkflow(undefined);
|
||||
await loadWorkflows();
|
||||
} catch (error) {
|
||||
console.error('Failed to save workflow:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
setIsSavingWorkflow(false);
|
||||
}
|
||||
}, [editingWorkflow, createWorkflow, loadWorkflows]);
|
||||
|
||||
const handleExecuteWorkflow = useCallback(async (workflowId: string) => {
|
||||
try {
|
||||
await executeWorkflow(workflowId);
|
||||
await loadWorkflows();
|
||||
} catch (error) {
|
||||
console.error('Failed to execute workflow:', error);
|
||||
}
|
||||
}, [executeWorkflow, loadWorkflows]);
|
||||
|
||||
if (isLoading && scheduledTasks.length === 0) {
|
||||
return (
|
||||
<div className="p-4 text-center">
|
||||
@@ -706,6 +777,12 @@ export function SchedulerPanel() {
|
||||
icon={Zap}
|
||||
label="事件触发器"
|
||||
/>
|
||||
<TabButton
|
||||
active={activeTab === 'workflows'}
|
||||
onClick={() => setActiveTab('workflows')}
|
||||
icon={GitBranch}
|
||||
label="工作流"
|
||||
/>
|
||||
<TabButton
|
||||
active={activeTab === 'history'}
|
||||
onClick={() => setActiveTab('history')}
|
||||
@@ -722,6 +799,15 @@ export function SchedulerPanel() {
|
||||
新建任务
|
||||
</button>
|
||||
)}
|
||||
{activeTab === 'workflows' && (
|
||||
<button
|
||||
onClick={handleCreateWorkflow}
|
||||
className="flex items-center gap-1.5 px-3 py-1.5 text-sm bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
新建工作流
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Tab Content */}
|
||||
@@ -787,6 +873,86 @@ export function SchedulerPanel() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Workflows Tab */}
|
||||
{activeTab === 'workflows' && (
|
||||
selectedWorkflow ? (
|
||||
<WorkflowHistory
|
||||
workflow={selectedWorkflow}
|
||||
onBack={() => setSelectedWorkflow(null)}
|
||||
/>
|
||||
) : (
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4">
|
||||
{workflows.length === 0 ? (
|
||||
<EmptyState
|
||||
icon={GitBranch}
|
||||
title="暂无工作流"
|
||||
description="工作流可以将多个 Hand 组合成自动化流程,实现复杂的任务编排。"
|
||||
actionLabel="创建工作流"
|
||||
onAction={handleCreateWorkflow}
|
||||
/>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{workflows.map((workflow) => (
|
||||
<div
|
||||
key={workflow.id}
|
||||
className="flex items-center justify-between p-3 bg-gray-50 dark:bg-gray-900 rounded-lg"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 bg-purple-100 dark:bg-purple-900/30 rounded-lg flex items-center justify-center">
|
||||
<GitBranch className="w-4 h-4 text-purple-600 dark:text-purple-400" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-medium text-gray-900 dark:text-white">
|
||||
{workflow.name}
|
||||
</div>
|
||||
<div className="text-xs text-gray-500 dark:text-gray-400">
|
||||
{workflow.description || '无描述'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{workflow.steps && (
|
||||
<span className="text-xs text-gray-500 dark:text-gray-400">
|
||||
{workflow.steps} 步骤
|
||||
</span>
|
||||
)}
|
||||
<button
|
||||
onClick={() => handleExecuteWorkflow(workflow.id)}
|
||||
className="p-1.5 text-green-600 hover:bg-green-50 dark:hover:bg-green-900/20 rounded"
|
||||
title="执行"
|
||||
>
|
||||
<Play className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleEditWorkflow(workflow)}
|
||||
className="p-1.5 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded"
|
||||
title="编辑"
|
||||
>
|
||||
<GitBranch className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleViewWorkflowHistory(workflow)}
|
||||
className="p-1.5 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded"
|
||||
title="历史"
|
||||
>
|
||||
<History className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<button
|
||||
onClick={handleCreateWorkflow}
|
||||
className="w-full flex items-center justify-center gap-2 p-3 border-2 border-dashed border-gray-200 dark:border-gray-700 rounded-lg text-gray-500 dark:text-gray-400 hover:border-blue-500 hover:text-blue-500 dark:hover:border-blue-400 dark:hover:text-blue-400 transition-colors"
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
创建新工作流
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
|
||||
{activeTab === 'history' && (
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4">
|
||||
<EmptyState
|
||||
@@ -804,6 +970,18 @@ export function SchedulerPanel() {
|
||||
onClose={() => setIsCreateModalOpen(false)}
|
||||
onSuccess={handleCreateSuccess}
|
||||
/>
|
||||
|
||||
{/* Workflow Editor Modal */}
|
||||
<WorkflowEditor
|
||||
workflow={editingWorkflow}
|
||||
isOpen={isWorkflowEditorOpen}
|
||||
onClose={() => {
|
||||
setIsWorkflowEditorOpen(false);
|
||||
setEditingWorkflow(undefined);
|
||||
}}
|
||||
onSave={handleSaveWorkflow}
|
||||
isSaving={isSavingWorkflow}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user