work-agent 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +234 -0
- package/app/(admin)/approvals/page.tsx +16 -0
- package/app/(admin)/audit/page.tsx +18 -0
- package/app/(admin)/layout.tsx +47 -0
- package/app/(admin)/scheduled-tasks/page.tsx +17 -0
- package/app/(admin)/settings/page.tsx +46 -0
- package/app/(admin)/skills/[name]/page.tsx +378 -0
- package/app/(admin)/skills/page.tsx +406 -0
- package/app/(admin)/statistics/page.tsx +416 -0
- package/app/(admin)/tickets/[id]/page.tsx +348 -0
- package/app/(admin)/tickets/new/page.tsx +309 -0
- package/app/(admin)/tickets/page.tsx +27 -0
- package/app/api/audit/route.ts +30 -0
- package/app/api/auth/feishu/callback/route.ts +72 -0
- package/app/api/auth/feishu/login/route.ts +17 -0
- package/app/api/auth/feishu/sso/route.ts +78 -0
- package/app/api/auth/login/route.ts +85 -0
- package/app/api/auth/oauth/route.ts +168 -0
- package/app/api/config/providers/route.ts +105 -0
- package/app/api/config/route.ts +115 -0
- package/app/api/config/status/route.ts +56 -0
- package/app/api/config/test/route.ts +212 -0
- package/app/api/documents/[id]/route.ts +88 -0
- package/app/api/documents/route.ts +53 -0
- package/app/api/health/route.ts +32 -0
- package/app/api/knowledge/[id]/route.ts +152 -0
- package/app/api/knowledge/from-session/route.ts +27 -0
- package/app/api/knowledge/route.ts +100 -0
- package/app/api/market/knowledge/[id]/route.ts +92 -0
- package/app/api/market/knowledge/route.ts +130 -0
- package/app/api/marketplace/skills/[id]/approve/route.ts +68 -0
- package/app/api/marketplace/skills/[id]/certify/route.ts +54 -0
- package/app/api/marketplace/skills/[id]/install/route.ts +180 -0
- package/app/api/marketplace/skills/[id]/promote-to-system/route.ts +219 -0
- package/app/api/marketplace/skills/[id]/rate/route.ts +90 -0
- package/app/api/marketplace/skills/[id]/ratings/route.ts +55 -0
- package/app/api/marketplace/skills/[id]/reject/route.ts +68 -0
- package/app/api/marketplace/skills/[id]/route.ts +177 -0
- package/app/api/marketplace/skills/route.ts +235 -0
- package/app/api/memory/route.ts +40 -0
- package/app/api/my/files/[id]/route.ts +52 -0
- package/app/api/my/files/route.ts +230 -0
- package/app/api/my/knowledge/route.ts +36 -0
- package/app/api/pi-chat/route.ts +443 -0
- package/app/api/recommend/route.ts +38 -0
- package/app/api/scheduled-tasks/[id]/execute/route.ts +132 -0
- package/app/api/scheduled-tasks/[id]/route.ts +165 -0
- package/app/api/scheduled-tasks/[id]/toggle/route.ts +53 -0
- package/app/api/scheduled-tasks/route.ts +101 -0
- package/app/api/sessions/[id]/messages/route.ts +212 -0
- package/app/api/sessions/route.ts +101 -0
- package/app/api/share/file/[id]/route.ts +37 -0
- package/app/api/skills/[name]/execute/route.ts +121 -0
- package/app/api/skills/[name]/route.ts +167 -0
- package/app/api/skills/create/route.ts +65 -0
- package/app/api/skills/generate/route.ts +405 -0
- package/app/api/skills/installed/route.ts +151 -0
- package/app/api/skills/route.ts +174 -0
- package/app/api/skills/translate/route.ts +40 -0
- package/app/api/skills/user/[name]/route.ts +159 -0
- package/app/api/skills/user/route.ts +90 -0
- package/app/api/statistics/route.ts +94 -0
- package/app/api/task-executions/[id]/route.ts +34 -0
- package/app/api/task-executions/route.ts +29 -0
- package/app/api/tickets/[id]/approve/route.ts +129 -0
- package/app/api/tickets/[id]/execute/route.ts +201 -0
- package/app/api/tickets/[id]/route.ts +127 -0
- package/app/api/tickets/route.ts +103 -0
- package/app/api/user/skills/route.ts +175 -0
- package/app/api/users/route.ts +80 -0
- package/app/chat/page.tsx +5 -0
- package/app/globals.css +84 -0
- package/app/h5/layout.tsx +5 -0
- package/app/h5/mobile-approvals-page.tsx +167 -0
- package/app/h5/mobile-chat-page.tsx +951 -0
- package/app/h5/mobile-profile-page.tsx +147 -0
- package/app/h5/mobile-tickets-page.tsx +121 -0
- package/app/h5/page.tsx +23 -0
- package/app/h5/ticket-action-buttons.tsx +80 -0
- package/app/layout.tsx +26 -0
- package/app/login/page.tsx +318 -0
- package/app/market/knowledge/[id]/page.tsx +77 -0
- package/app/market/knowledge/page.tsx +358 -0
- package/app/market/layout.tsx +29 -0
- package/app/market/page.tsx +18 -0
- package/app/market/skills/page.tsx +397 -0
- package/app/my/files/page.tsx +511 -0
- package/app/my/knowledge/[id]/page.tsx +271 -0
- package/app/my/knowledge/new/page.tsx +234 -0
- package/app/my/knowledge/page.tsx +248 -0
- package/app/my/layout.tsx +32 -0
- package/app/my/memory/page.tsx +164 -0
- package/app/my/page.tsx +18 -0
- package/app/my/scheduled-tasks/[id]/edit/page.tsx +290 -0
- package/app/my/scheduled-tasks/[id]/executions/page.tsx +275 -0
- package/app/my/scheduled-tasks/[id]/page.tsx +284 -0
- package/app/my/scheduled-tasks/new/page.tsx +230 -0
- package/app/my/scheduled-tasks/page.tsx +27 -0
- package/app/my/skills/[name]/page.tsx +320 -0
- package/app/my/skills/new/page.tsx +394 -0
- package/app/my/skills/page.tsx +303 -0
- package/app/page.tsx +2288 -0
- package/app/share/[sessionId]/page.tsx +226 -0
- package/app/share/file/[id]/page.tsx +140 -0
- package/bin/README.md +63 -0
- package/bin/generate-api-system +300 -0
- package/bin/postinstall.js +95 -0
- package/bin/work-agent.js +173 -0
- package/components/ai-elements/agent.tsx +142 -0
- package/components/ai-elements/artifact.tsx +149 -0
- package/components/ai-elements/attachments.tsx +427 -0
- package/components/ai-elements/audio-player.tsx +232 -0
- package/components/ai-elements/canvas.tsx +26 -0
- package/components/ai-elements/chain-of-thought.tsx +223 -0
- package/components/ai-elements/checkpoint.tsx +72 -0
- package/components/ai-elements/code-block.tsx +555 -0
- package/components/ai-elements/commit.tsx +449 -0
- package/components/ai-elements/confirmation.tsx +173 -0
- package/components/ai-elements/connection.tsx +28 -0
- package/components/ai-elements/context.tsx +410 -0
- package/components/ai-elements/controls.tsx +19 -0
- package/components/ai-elements/conversation.tsx +167 -0
- package/components/ai-elements/edge.tsx +144 -0
- package/components/ai-elements/environment-variables.tsx +325 -0
- package/components/ai-elements/file-tree.tsx +298 -0
- package/components/ai-elements/image.tsx +25 -0
- package/components/ai-elements/inline-citation.tsx +294 -0
- package/components/ai-elements/jsx-preview.tsx +250 -0
- package/components/ai-elements/message.tsx +367 -0
- package/components/ai-elements/mic-selector.tsx +372 -0
- package/components/ai-elements/model-selector.tsx +214 -0
- package/components/ai-elements/node.tsx +72 -0
- package/components/ai-elements/open-in-chat.tsx +367 -0
- package/components/ai-elements/package-info.tsx +235 -0
- package/components/ai-elements/panel.tsx +16 -0
- package/components/ai-elements/persona.tsx +280 -0
- package/components/ai-elements/plan.tsx +144 -0
- package/components/ai-elements/prompt-input.tsx +1341 -0
- package/components/ai-elements/queue.tsx +275 -0
- package/components/ai-elements/reasoning.tsx +355 -0
- package/components/ai-elements/sandbox.tsx +133 -0
- package/components/ai-elements/schema-display.tsx +473 -0
- package/components/ai-elements/shimmer.tsx +78 -0
- package/components/ai-elements/snippet.tsx +141 -0
- package/components/ai-elements/sources.tsx +78 -0
- package/components/ai-elements/speech-input.tsx +324 -0
- package/components/ai-elements/stack-trace.tsx +531 -0
- package/components/ai-elements/suggestion.tsx +58 -0
- package/components/ai-elements/task.tsx +88 -0
- package/components/ai-elements/terminal.tsx +277 -0
- package/components/ai-elements/test-results.tsx +497 -0
- package/components/ai-elements/tool.tsx +174 -0
- package/components/ai-elements/toolbar.tsx +17 -0
- package/components/ai-elements/transcription.tsx +126 -0
- package/components/ai-elements/voice-selector.tsx +525 -0
- package/components/ai-elements/web-preview.tsx +282 -0
- package/components/audit-log-list.tsx +114 -0
- package/components/chat/EmptyPreviewState.tsx +12 -0
- package/components/chat/KnowledgePickerDialog.tsx +464 -0
- package/components/chat/KnowledgePreview.tsx +70 -0
- package/components/chat/KnowledgePreviewPanel.tsx +86 -0
- package/components/chat/MentionInput.tsx +309 -0
- package/components/chat/OrganizeDialog.tsx +258 -0
- package/components/chat/RecommendationBanner.tsx +94 -0
- package/components/chat/SaveToKnowledgeDialog.tsx +193 -0
- package/components/chat/SkillSelector.tsx +305 -0
- package/components/chat/SkillSwitcher.tsx +163 -0
- package/components/client-layout.tsx +15 -0
- package/components/knowledge/KnowledgeMetadataPanel.tsx +293 -0
- package/components/layout-wrapper.tsx +18 -0
- package/components/mobile-layout.tsx +62 -0
- package/components/scheduled-task-list.tsx +356 -0
- package/components/setup-guide.tsx +484 -0
- package/components/sub-nav.tsx +54 -0
- package/components/ticket-detail-content.tsx +383 -0
- package/components/ticket-list.tsx +366 -0
- package/components/top-nav.tsx +132 -0
- package/components/ui/accordion.tsx +58 -0
- package/components/ui/alert.tsx +59 -0
- package/components/ui/avatar.tsx +50 -0
- package/components/ui/badge.tsx +36 -0
- package/components/ui/button-group.tsx +83 -0
- package/components/ui/button.tsx +57 -0
- package/components/ui/card.tsx +91 -0
- package/components/ui/carousel.tsx +262 -0
- package/components/ui/collapsible.tsx +11 -0
- package/components/ui/command.tsx +153 -0
- package/components/ui/dialog.tsx +122 -0
- package/components/ui/dropdown-menu.tsx +200 -0
- package/components/ui/hover-card.tsx +29 -0
- package/components/ui/input-group.tsx +170 -0
- package/components/ui/input.tsx +22 -0
- package/components/ui/label.tsx +26 -0
- package/components/ui/popover.tsx +31 -0
- package/components/ui/progress.tsx +28 -0
- package/components/ui/scroll-area.tsx +48 -0
- package/components/ui/select.tsx +174 -0
- package/components/ui/separator.tsx +31 -0
- package/components/ui/spinner.tsx +16 -0
- package/components/ui/switch.tsx +29 -0
- package/components/ui/table.tsx +120 -0
- package/components/ui/tabs.tsx +55 -0
- package/components/ui/textarea.tsx +22 -0
- package/components/ui/tooltip.tsx +30 -0
- package/components/welcome-guide.tsx +182 -0
- package/components.json +24 -0
- package/lib/command-parser.ts +331 -0
- package/lib/dangerous-commands.ts +672 -0
- package/lib/db.ts +2250 -0
- package/lib/feishu-auth.ts +135 -0
- package/lib/file-storage.ts +306 -0
- package/lib/file-tool.ts +583 -0
- package/lib/knowledge-tool.ts +152 -0
- package/lib/knowledge-types.ts +66 -0
- package/lib/market-client.ts +313 -0
- package/lib/market-db.ts +736 -0
- package/lib/market-types.ts +51 -0
- package/lib/memory-tool.ts +211 -0
- package/lib/memory.ts +197 -0
- package/lib/pi-config.ts +436 -0
- package/lib/pi-session.ts +799 -0
- package/lib/pinyin.ts +13 -0
- package/lib/recommendation.ts +227 -0
- package/lib/risk-estimator.ts +350 -0
- package/lib/scheduled-task-tool.ts +184 -0
- package/lib/scheduler-init.ts +43 -0
- package/lib/scheduler.ts +416 -0
- package/lib/secure-bash-tool.ts +413 -0
- package/lib/skill-engine.ts +396 -0
- package/lib/skill-generator.ts +269 -0
- package/lib/skill-loader.ts +234 -0
- package/lib/skill-tool.ts +188 -0
- package/lib/skill-types.ts +82 -0
- package/lib/skills-init.ts +58 -0
- package/lib/ticket-tool.ts +246 -0
- package/lib/user-skill-types.ts +30 -0
- package/lib/user-skills.ts +362 -0
- package/lib/utils.ts +6 -0
- package/lib/workflow.ts +154 -0
- package/lib/zip-tool.ts +191 -0
- package/next.config.js +8 -0
- package/package.json +106 -0
- package/public/.gitkeep +1 -0
- package/public/icon.svg +1 -0
- package/tsconfig.json +42 -0
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect } from 'react';
|
|
4
|
+
import type { Ticket } from '@/lib/db';
|
|
5
|
+
import { Button } from '@/components/ui/button';
|
|
6
|
+
|
|
7
|
+
interface TicketDetailContentProps {
|
|
8
|
+
ticket: Ticket;
|
|
9
|
+
onApprove?: () => void;
|
|
10
|
+
onReject?: () => void;
|
|
11
|
+
onExecute?: () => void;
|
|
12
|
+
loading?: boolean;
|
|
13
|
+
showActions?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface AuditLog {
|
|
17
|
+
id: string;
|
|
18
|
+
action: string;
|
|
19
|
+
userId: string;
|
|
20
|
+
timestamp: Date;
|
|
21
|
+
status: 'success' | 'failure';
|
|
22
|
+
details?: unknown;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const ACTION_LABELS: Record<string, string> = {
|
|
26
|
+
'ticket_created': '工单创建',
|
|
27
|
+
'ticket_executed': '工单执行',
|
|
28
|
+
'ticket_execution_failed': '执行失败',
|
|
29
|
+
'ticket_execution_error': '执行异常',
|
|
30
|
+
'status_transition': '状态变更',
|
|
31
|
+
'ticket_approve': '工单审批',
|
|
32
|
+
'execution_failed': '执行失败',
|
|
33
|
+
'scheduled_task_created': '定时任务创建',
|
|
34
|
+
'scheduled_task_start': '任务开始',
|
|
35
|
+
'scheduled_task_complete': '任务完成',
|
|
36
|
+
'scheduled_task_failed': '任务失败',
|
|
37
|
+
'blocked_automated_execution': '自动执行被拦截',
|
|
38
|
+
'blocked_automated_approval': '自动审批被拦截',
|
|
39
|
+
'ticket_deleted': '工单删除',
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const STATUS_LABELS: Record<string, string> = {
|
|
43
|
+
'draft': '草稿',
|
|
44
|
+
'pending': '待审核',
|
|
45
|
+
'approved': '已批准',
|
|
46
|
+
'rejected': '已拒绝',
|
|
47
|
+
'executing': '执行中',
|
|
48
|
+
'completed': '已完成',
|
|
49
|
+
'failed': '失败',
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
function formatAuditAction(action: string): string {
|
|
53
|
+
return ACTION_LABELS[action] || action;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function formatAuditDetails(action: string, details: unknown): string {
|
|
57
|
+
if (!details || typeof details !== 'object') return '';
|
|
58
|
+
|
|
59
|
+
const d = details as Record<string, unknown>;
|
|
60
|
+
|
|
61
|
+
switch (action) {
|
|
62
|
+
case 'ticket_executed':
|
|
63
|
+
if (d.output) return `输出: ${String(d.output).substring(0, 200)}${String(d.output).length > 200 ? '...' : ''}`;
|
|
64
|
+
if (d.executionTime) return `耗时: ${d.executionTime}ms`;
|
|
65
|
+
return '';
|
|
66
|
+
case 'ticket_execution_failed':
|
|
67
|
+
case 'ticket_execution_error':
|
|
68
|
+
return d.error ? `错误: ${d.error}` : '';
|
|
69
|
+
case 'status_transition': {
|
|
70
|
+
const from = d.from ? STATUS_LABELS[String(d.from)] || String(d.from) : '';
|
|
71
|
+
const to = d.to ? STATUS_LABELS[String(d.to)] || String(d.to) : '';
|
|
72
|
+
return `从 "${from}" → "${to}"`;
|
|
73
|
+
}
|
|
74
|
+
case 'scheduled_task_start':
|
|
75
|
+
return d.command ? `命令: ${d.command}` : '';
|
|
76
|
+
case 'scheduled_task_complete':
|
|
77
|
+
return d.output ? `输出: ${String(d.output).substring(0, 100)}` : '';
|
|
78
|
+
case 'scheduled_task_failed':
|
|
79
|
+
return d.error ? `错误: ${d.error}` : '';
|
|
80
|
+
default:
|
|
81
|
+
return '';
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function TicketDetailContent({
|
|
86
|
+
ticket,
|
|
87
|
+
onApprove,
|
|
88
|
+
onReject,
|
|
89
|
+
onExecute,
|
|
90
|
+
loading = false,
|
|
91
|
+
showActions = true,
|
|
92
|
+
}: TicketDetailContentProps) {
|
|
93
|
+
const [comment, setComment] = useState('');
|
|
94
|
+
const [auditLogs, setAuditLogs] = useState<AuditLog[]>([]);
|
|
95
|
+
const [loadingLogs, setLoadingLogs] = useState(false);
|
|
96
|
+
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
async function fetchAuditLogs() {
|
|
99
|
+
setLoadingLogs(true);
|
|
100
|
+
try {
|
|
101
|
+
const response = await fetch(`/api/audit?ticketId=${ticket.id}&limit=20`);
|
|
102
|
+
if (response.ok) {
|
|
103
|
+
const data = await response.json();
|
|
104
|
+
setAuditLogs(data.logs || []);
|
|
105
|
+
}
|
|
106
|
+
} catch (error) {
|
|
107
|
+
console.error('Failed to fetch audit logs:', error);
|
|
108
|
+
} finally {
|
|
109
|
+
setLoadingLogs(false);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
fetchAuditLogs();
|
|
113
|
+
}, [ticket.id]);
|
|
114
|
+
|
|
115
|
+
const getRiskBadge = (risk: string) => {
|
|
116
|
+
const config: Record<string, { label: string; className: string }> = {
|
|
117
|
+
low: { label: '低风险', className: 'bg-green-100 text-green-800 border-green-200' },
|
|
118
|
+
medium: { label: '中风险', className: 'bg-yellow-100 text-yellow-800 border-yellow-200' },
|
|
119
|
+
high: { label: '高风险', className: 'bg-orange-100 text-orange-800 border-orange-200' },
|
|
120
|
+
critical: { label: '极高风险', className: 'bg-red-100 text-red-800 border-red-200' },
|
|
121
|
+
};
|
|
122
|
+
const { label, className } = config[risk] || config.medium;
|
|
123
|
+
return <span className={`px-2 py-0.5 rounded-full text-xs border ${className}`}>{label}</span>;
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const getTypeEmoji = (type: string) => {
|
|
127
|
+
const emojis: Record<string, string> = {
|
|
128
|
+
'bash-execute': '💻',
|
|
129
|
+
'skill-script': '🛠️',
|
|
130
|
+
'deploy': '🚀',
|
|
131
|
+
'scale': '📊',
|
|
132
|
+
'update': '🔄',
|
|
133
|
+
'delete': '🗑️',
|
|
134
|
+
'rollback': '⏪',
|
|
135
|
+
};
|
|
136
|
+
return emojis[type] || '📋';
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const getStatusBadge = (status: string) => {
|
|
140
|
+
const config: Record<string, { label: string; className: string }> = {
|
|
141
|
+
draft: { label: '草稿', className: 'bg-gray-100 text-gray-800' },
|
|
142
|
+
pending: { label: '待审核', className: 'bg-yellow-100 text-yellow-800' },
|
|
143
|
+
approved: { label: '已批准', className: 'bg-green-100 text-green-800' },
|
|
144
|
+
rejected: { label: '已拒绝', className: 'bg-red-100 text-red-800' },
|
|
145
|
+
executing: { label: '执行中', className: 'bg-blue-100 text-blue-800' },
|
|
146
|
+
completed: { label: '已完成', className: 'bg-green-100 text-green-800' },
|
|
147
|
+
failed: { label: '失败', className: 'bg-red-100 text-red-800' },
|
|
148
|
+
};
|
|
149
|
+
const { label, className } = config[status] || config.pending;
|
|
150
|
+
return <span className={`px-2 py-0.5 rounded-full text-xs ${className}`}>{label}</span>;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
return (
|
|
154
|
+
<div className="space-y-4">
|
|
155
|
+
{/* 标题 */}
|
|
156
|
+
<div className="flex items-start justify-between gap-4">
|
|
157
|
+
<div className="flex items-center gap-3">
|
|
158
|
+
<span className="text-2xl">{getTypeEmoji(ticket.type)}</span>
|
|
159
|
+
<div>
|
|
160
|
+
<h2 className="text-xl font-bold">{ticket.title}</h2>
|
|
161
|
+
<p className="text-xs text-muted-foreground">ID: {ticket.id}</p>
|
|
162
|
+
</div>
|
|
163
|
+
</div>
|
|
164
|
+
{getStatusBadge(ticket.status)}
|
|
165
|
+
</div>
|
|
166
|
+
|
|
167
|
+
{/* 风险等级 */}
|
|
168
|
+
{ticket.riskLevel && (
|
|
169
|
+
<div className="flex items-center gap-2">
|
|
170
|
+
<span className="text-sm text-muted-foreground">风险等级:</span>
|
|
171
|
+
{getRiskBadge(ticket.riskLevel)}
|
|
172
|
+
</div>
|
|
173
|
+
)}
|
|
174
|
+
|
|
175
|
+
{/* 核心信息卡片 */}
|
|
176
|
+
<div className="bg-muted/50 rounded-lg p-4 space-y-4">
|
|
177
|
+
{/* 命令执行 */}
|
|
178
|
+
{ticket.command && (
|
|
179
|
+
<div>
|
|
180
|
+
<h4 className="text-xs font-medium text-muted-foreground mb-1">执行命令</h4>
|
|
181
|
+
<div className="bg-gray-900 rounded px-3 py-2 font-mono text-sm overflow-x-auto max-w-full">
|
|
182
|
+
<pre className="whitespace-pre-wrap break-all text-green-400">{ticket.command}</pre>
|
|
183
|
+
</div>
|
|
184
|
+
</div>
|
|
185
|
+
)}
|
|
186
|
+
|
|
187
|
+
{/* Skill 脚本 */}
|
|
188
|
+
{ticket.scriptContent && (
|
|
189
|
+
<div>
|
|
190
|
+
<h4 className="text-xs font-medium text-muted-foreground mb-1">
|
|
191
|
+
Skill: {ticket.skillName || '未知'}
|
|
192
|
+
</h4>
|
|
193
|
+
<div className="bg-gray-900 rounded px-3 py-2 font-mono text-sm max-h-48 overflow-y-auto max-w-full">
|
|
194
|
+
<pre className="whitespace-pre-wrap break-all text-green-400">{ticket.scriptContent}</pre>
|
|
195
|
+
</div>
|
|
196
|
+
</div>
|
|
197
|
+
)}
|
|
198
|
+
|
|
199
|
+
{/* 受影响资源 */}
|
|
200
|
+
{ticket.affectedResources && ticket.affectedResources.length > 0 && (
|
|
201
|
+
<div>
|
|
202
|
+
<h4 className="text-xs font-medium text-muted-foreground mb-1">受影响资源</h4>
|
|
203
|
+
<div className="flex flex-wrap gap-1">
|
|
204
|
+
{ticket.affectedResources.map((resource, idx) => (
|
|
205
|
+
<span key={idx} className="px-2 py-0.5 bg-orange-100 text-orange-800 rounded text-xs">
|
|
206
|
+
{resource}
|
|
207
|
+
</span>
|
|
208
|
+
))}
|
|
209
|
+
</div>
|
|
210
|
+
</div>
|
|
211
|
+
)}
|
|
212
|
+
</div>
|
|
213
|
+
|
|
214
|
+
{/* 描述/审批意见 */}
|
|
215
|
+
{ticket.description && (
|
|
216
|
+
<div className="text-sm text-muted-foreground bg-muted/30 p-3 rounded">
|
|
217
|
+
{ticket.description}
|
|
218
|
+
</div>
|
|
219
|
+
)}
|
|
220
|
+
|
|
221
|
+
{/* 执行结果 */}
|
|
222
|
+
{ticket.status === 'completed' && ticket.output && (
|
|
223
|
+
<div className="bg-green-50 border border-green-200 rounded-lg p-4">
|
|
224
|
+
<h4 className="text-xs font-medium text-green-800 mb-1">执行结果</h4>
|
|
225
|
+
<div className="bg-gray-900 rounded px-3 py-2 font-mono text-sm max-h-48 overflow-y-auto max-w-full">
|
|
226
|
+
<pre className="whitespace-pre-wrap break-all text-green-400">{ticket.output}</pre>
|
|
227
|
+
</div>
|
|
228
|
+
</div>
|
|
229
|
+
)}
|
|
230
|
+
|
|
231
|
+
{ticket.status === 'failed' && ticket.error && (
|
|
232
|
+
<div className="bg-red-50 border border-red-200 rounded-lg p-4">
|
|
233
|
+
<h4 className="text-xs font-medium text-red-800 mb-1">执行失败</h4>
|
|
234
|
+
<div className="text-red-700 text-sm">{ticket.error}</div>
|
|
235
|
+
</div>
|
|
236
|
+
)}
|
|
237
|
+
|
|
238
|
+
{/* 时间信息 */}
|
|
239
|
+
<div className="text-xs text-muted-foreground">
|
|
240
|
+
创建时间: {new Date(ticket.createdAt).toLocaleString('zh-CN')}
|
|
241
|
+
</div>
|
|
242
|
+
|
|
243
|
+
{/* 审批记录 */}
|
|
244
|
+
{ticket.approvals && ticket.approvals.length > 0 && (
|
|
245
|
+
<div className="border-t pt-4">
|
|
246
|
+
<h4 className="text-sm font-medium mb-2">审批记录</h4>
|
|
247
|
+
<div className="space-y-2">
|
|
248
|
+
{ticket.approvals.map((approval, idx) => (
|
|
249
|
+
<div key={idx} className="text-sm bg-muted/30 p-2 rounded">
|
|
250
|
+
<div className="flex items-center justify-between">
|
|
251
|
+
<span className="font-medium">{approval.approver}</span>
|
|
252
|
+
<span className={`text-xs px-2 py-0.5 rounded ${
|
|
253
|
+
approval.decision === 'approved'
|
|
254
|
+
? 'bg-green-100 text-green-800'
|
|
255
|
+
: 'bg-red-100 text-red-800'
|
|
256
|
+
}`}>
|
|
257
|
+
{approval.decision === 'approved' ? '已批准' : '已拒绝'}
|
|
258
|
+
</span>
|
|
259
|
+
</div>
|
|
260
|
+
{approval.comment && (
|
|
261
|
+
<div className="text-muted-foreground mt-1">{approval.comment}</div>
|
|
262
|
+
)}
|
|
263
|
+
<div className="text-xs text-muted-foreground mt-1">
|
|
264
|
+
{new Date(approval.timestamp).toLocaleString('zh-CN')}
|
|
265
|
+
</div>
|
|
266
|
+
</div>
|
|
267
|
+
))}
|
|
268
|
+
</div>
|
|
269
|
+
</div>
|
|
270
|
+
)}
|
|
271
|
+
|
|
272
|
+
{/* 审计日志 */}
|
|
273
|
+
{(auditLogs.length > 0 || loadingLogs) && (
|
|
274
|
+
<div className="border-t pt-4">
|
|
275
|
+
<h4 className="text-sm font-medium mb-2">审计日志</h4>
|
|
276
|
+
{loadingLogs ? (
|
|
277
|
+
<div className="text-xs text-muted-foreground">加载中...</div>
|
|
278
|
+
) : (
|
|
279
|
+
<div className="space-y-2 max-h-48 overflow-y-auto">
|
|
280
|
+
{[...auditLogs].reverse().map((log) => {
|
|
281
|
+
const details = formatAuditDetails(log.action, log.details);
|
|
282
|
+
return (
|
|
283
|
+
<div key={log.id} className="text-xs bg-muted/30 p-2 rounded">
|
|
284
|
+
<div className="flex items-center gap-2">
|
|
285
|
+
<span className={`w-2 h-2 rounded-full ${log.status === 'success' ? 'bg-green-500' : 'bg-red-500'}`}></span>
|
|
286
|
+
<span className="font-medium">{formatAuditAction(log.action)}</span>
|
|
287
|
+
<span className="text-muted-foreground">· {log.userId}</span>
|
|
288
|
+
</div>
|
|
289
|
+
{details && (
|
|
290
|
+
<div className="mt-1 text-muted-foreground font-mono text-xs bg-gray-100 p-1 rounded truncate">
|
|
291
|
+
{details}
|
|
292
|
+
</div>
|
|
293
|
+
)}
|
|
294
|
+
<div className="text-muted-foreground mt-1">
|
|
295
|
+
{new Date(log.timestamp).toLocaleString('zh-CN')}
|
|
296
|
+
</div>
|
|
297
|
+
</div>
|
|
298
|
+
);
|
|
299
|
+
})}
|
|
300
|
+
</div>
|
|
301
|
+
)}
|
|
302
|
+
</div>
|
|
303
|
+
)}
|
|
304
|
+
|
|
305
|
+
{/* 操作按钮 */}
|
|
306
|
+
{showActions && (
|
|
307
|
+
<div className="border-t pt-4 space-y-2">
|
|
308
|
+
{ticket.status === 'pending' && (
|
|
309
|
+
<>
|
|
310
|
+
<textarea
|
|
311
|
+
value={comment}
|
|
312
|
+
onChange={(e) => setComment(e.target.value)}
|
|
313
|
+
placeholder="审批意见(可选)"
|
|
314
|
+
className="w-full px-3 py-2 border rounded-lg text-sm bg-background"
|
|
315
|
+
rows={2}
|
|
316
|
+
/>
|
|
317
|
+
<div className="flex gap-2">
|
|
318
|
+
<Button
|
|
319
|
+
onClick={onApprove}
|
|
320
|
+
disabled={loading}
|
|
321
|
+
className="flex-1"
|
|
322
|
+
size="sm"
|
|
323
|
+
>
|
|
324
|
+
{loading ? (
|
|
325
|
+
<span className="flex items-center justify-center gap-2">
|
|
326
|
+
<span className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin"></span>
|
|
327
|
+
处理中...
|
|
328
|
+
</span>
|
|
329
|
+
) : (
|
|
330
|
+
'✓ 批准'
|
|
331
|
+
)}
|
|
332
|
+
</Button>
|
|
333
|
+
<Button
|
|
334
|
+
variant="destructive"
|
|
335
|
+
onClick={onReject}
|
|
336
|
+
disabled={loading}
|
|
337
|
+
className="flex-1"
|
|
338
|
+
size="sm"
|
|
339
|
+
>
|
|
340
|
+
{loading ? (
|
|
341
|
+
<span className="flex items-center justify-center gap-2">
|
|
342
|
+
<span className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin"></span>
|
|
343
|
+
处理中...
|
|
344
|
+
</span>
|
|
345
|
+
) : (
|
|
346
|
+
'✗ 拒绝'
|
|
347
|
+
)}
|
|
348
|
+
</Button>
|
|
349
|
+
</div>
|
|
350
|
+
</>
|
|
351
|
+
)}
|
|
352
|
+
|
|
353
|
+
{ticket.status === 'approved' && (
|
|
354
|
+
<Button
|
|
355
|
+
onClick={onExecute}
|
|
356
|
+
disabled={loading}
|
|
357
|
+
className="w-full"
|
|
358
|
+
size="sm"
|
|
359
|
+
>
|
|
360
|
+
{loading ? (
|
|
361
|
+
<span className="flex items-center justify-center gap-2">
|
|
362
|
+
<span className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin"></span>
|
|
363
|
+
执行中...
|
|
364
|
+
</span>
|
|
365
|
+
) : (
|
|
366
|
+
'▶️ 执行工单'
|
|
367
|
+
)}
|
|
368
|
+
</Button>
|
|
369
|
+
)}
|
|
370
|
+
|
|
371
|
+
{ticket.status === 'executing' && (
|
|
372
|
+
<div className="text-center py-2 text-muted-foreground">
|
|
373
|
+
<span className="inline-flex items-center gap-2">
|
|
374
|
+
<span className="w-2 h-2 rounded-full bg-blue-600 animate-pulse"></span>
|
|
375
|
+
执行中...
|
|
376
|
+
</span>
|
|
377
|
+
</div>
|
|
378
|
+
)}
|
|
379
|
+
</div>
|
|
380
|
+
)}
|
|
381
|
+
</div>
|
|
382
|
+
);
|
|
383
|
+
}
|