@zzusp/dingtalk-dsh-observer 0.4.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/cordis.patch.yml +3 -0
- package/index.js +2 -0
- package/package.json +39 -0
- package/web-client.js +332 -0
package/cordis.patch.yml
ADDED
package/index.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@zzusp/dingtalk-dsh-observer",
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "DingTalk task board client for DeepSeek Harness",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "index.js",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/HiQ-AI/dingtalk-dsh-assistant.git",
|
|
11
|
+
"directory": "packages/dingtalk-dsh-observer"
|
|
12
|
+
},
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public",
|
|
15
|
+
"registry": "https://registry.npmjs.org/"
|
|
16
|
+
},
|
|
17
|
+
"exports": {
|
|
18
|
+
".": "./index.js",
|
|
19
|
+
"./client": "./web-client.js",
|
|
20
|
+
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
21
|
+
"./package.json": "./package.json"
|
|
22
|
+
},
|
|
23
|
+
"dsh": {
|
|
24
|
+
"bundle": {
|
|
25
|
+
"patch": "./cordis.patch.yml"
|
|
26
|
+
},
|
|
27
|
+
"client": {
|
|
28
|
+
"inject": [
|
|
29
|
+
"@deepseek-ai/dsh-client-runtime",
|
|
30
|
+
"@deepseek-ai/dsh-client-ui-layout",
|
|
31
|
+
"@deepseek-ai/dsh-client-ui-sidebar"
|
|
32
|
+
],
|
|
33
|
+
"platform": "web"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"react": "^18.2.0"
|
|
38
|
+
}
|
|
39
|
+
}
|
package/web-client.js
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: '@zzusp/dingtalk-dsh-observer',
|
|
3
|
+
factory: (require) => {
|
|
4
|
+
const module = { exports: {} }
|
|
5
|
+
const React = require('react')
|
|
6
|
+
const { useCallback, useEffect, useState } = React
|
|
7
|
+
const ENDPOINT = 'http://127.0.0.1:18998'
|
|
8
|
+
const colors = {
|
|
9
|
+
surface: 'var(--dsw-alias-bg-base, #fff)',
|
|
10
|
+
surface2: 'var(--dsw-alias-bg-layer-1, #f7f7f8)',
|
|
11
|
+
cardSurface: 'var(--dsw-alias-bg-layer-1, #fff)',
|
|
12
|
+
border: 'var(--dsw-alias-border-l2, rgba(127,127,127,.25))',
|
|
13
|
+
muted: 'var(--dsw-alias-label-secondary, #737373)',
|
|
14
|
+
accent: 'var(--dsw-alias-brand-primary, #4d6bfe)',
|
|
15
|
+
danger: 'var(--dsw-alias-state-error-primary, #c33)',
|
|
16
|
+
warning: 'var(--dsw-alias-state-warn-primary, #a56500)'
|
|
17
|
+
}
|
|
18
|
+
let open = false
|
|
19
|
+
const listeners = new Set()
|
|
20
|
+
const setOpen = (value) => { open = value; for (const listener of listeners) listener(value) }
|
|
21
|
+
const useOpen = () => {
|
|
22
|
+
const [value, setValue] = useState(open)
|
|
23
|
+
useEffect(() => { listeners.add(setValue); return () => listeners.delete(setValue) }, [])
|
|
24
|
+
return [value, setOpen]
|
|
25
|
+
}
|
|
26
|
+
const fmt = (value) => value ? new Date(value).toLocaleString('zh-CN', { hour12: false }) : '—'
|
|
27
|
+
const short = (value) => value ? String(value).replace(/^session-/, '').slice(0, 14) : '—'
|
|
28
|
+
const pill = (tone) => ({ display: 'inline-flex', alignItems: 'center', borderRadius: 999, padding: '3px 8px', fontSize: 12, color: tone, background: `color-mix(in srgb, ${tone} 12%, transparent)` })
|
|
29
|
+
const card = { border: `1px solid ${colors.border}`, borderRadius: 12, background: colors.cardSurface, padding: 16, boxShadow: '0 4px 16px rgba(15,23,42,.07)' }
|
|
30
|
+
async function get(path) {
|
|
31
|
+
const response = await fetch(`${ENDPOINT}${path}`, { method: 'GET', headers: { accept: 'application/json' } })
|
|
32
|
+
const value = await response.json()
|
|
33
|
+
if (!response.ok) throw new Error(value.error || `HTTP ${response.status}`)
|
|
34
|
+
return value
|
|
35
|
+
}
|
|
36
|
+
async function post(path, body = {}) {
|
|
37
|
+
const response = await fetch(`${ENDPOINT}${path}`, { method: 'POST', headers: { accept: 'application/json', 'content-type': 'application/json' }, body: JSON.stringify(body) })
|
|
38
|
+
const value = await response.json()
|
|
39
|
+
if (!response.ok) throw new Error(value.error || `HTTP ${response.status}`)
|
|
40
|
+
return value
|
|
41
|
+
}
|
|
42
|
+
async function load() {
|
|
43
|
+
const [health, groups, tasks, activities, alerts, authorizations] = await Promise.all([
|
|
44
|
+
get('/health'), get('/state/groups'), get('/state/tasks'), get('/state/activities'), get('/state/supervisor/alerts'), get('/state/authorizations')
|
|
45
|
+
])
|
|
46
|
+
return { health, groups, tasks, activities, alerts, authorizations }
|
|
47
|
+
}
|
|
48
|
+
function FooterAction({ wide }) {
|
|
49
|
+
const [isOpen] = useOpen()
|
|
50
|
+
const [hovered, setHovered] = useState(false)
|
|
51
|
+
const icon = React.createElement('svg', { width: 16, height: 16, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 1.8, strokeLinecap: 'round', strokeLinejoin: 'round', 'aria-hidden': true },
|
|
52
|
+
React.createElement('rect', { x: 3, y: 3, width: 18, height: 18, rx: 3 }),
|
|
53
|
+
React.createElement('path', { d: 'M7 15v2M12 11v6M17 7v10' }))
|
|
54
|
+
return React.createElement('button', {
|
|
55
|
+
type: 'button', title: '钉钉群聊运行看板', 'aria-label': '钉钉群聊运行看板', onClick: () => setOpen(!isOpen), onMouseEnter: () => setHovered(true), onMouseLeave: () => setHovered(false),
|
|
56
|
+
style: wide
|
|
57
|
+
? { boxSizing: 'border-box', cursor: 'pointer', width: 'calc(100% + 4px)', height: 42, color: 'var(--dsw-alias-label-primary)', background: isOpen ? 'color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent)' : hovered ? 'var(--dsw-alias-interactive-bg-hover)' : 'transparent', border: 0, borderRadius: 12, flex: '0 0 auto', alignItems: 'center', gap: 8, margin: '4px -2px', padding: '0 10px 0 8px', fontFamily: 'inherit', fontSize: 14, lineHeight: '22px', display: 'flex', overflow: 'hidden' }
|
|
58
|
+
: { boxSizing: 'border-box', cursor: 'pointer', width: 36, height: 36, color: 'var(--dsw-alias-label-primary)', background: isOpen ? 'color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent)' : hovered ? 'var(--dsw-alias-interactive-bg-hover)' : 'transparent', border: 0, borderRadius: '50%', display: 'flex', alignItems: 'center', justifyContent: 'center', margin: '8px 0 10px', padding: 0 }
|
|
59
|
+
}, icon, wide ? React.createElement('span', { style: { whiteSpace: 'nowrap', overflow: 'hidden' } }, '运行看板') : null)
|
|
60
|
+
}
|
|
61
|
+
const taskBuckets = [
|
|
62
|
+
{ state: 'queued', label: '待执行', tone: 'var(--dsw-alias-label-secondary, #64748b)', background: 'color-mix(in srgb, var(--dsw-alias-label-secondary, #64748b) 8%, var(--dsw-alias-bg-layer-1, #fff))' },
|
|
63
|
+
{ state: 'running', label: '执行中', tone: colors.accent, background: 'color-mix(in srgb, var(--dsw-alias-brand-primary, #4d6bfe) 8%, var(--dsw-alias-bg-layer-1, #fff))' },
|
|
64
|
+
{ state: 'waiting', label: '等待中', tone: colors.warning, background: 'color-mix(in srgb, var(--dsw-alias-state-warn-primary, #a56500) 8%, var(--dsw-alias-bg-layer-1, #fff))' },
|
|
65
|
+
{ state: 'completed', label: '已完成', tone: 'var(--dsw-alias-state-success-primary, #248a3d)', background: 'color-mix(in srgb, var(--dsw-alias-state-success-primary, #248a3d) 8%, var(--dsw-alias-bg-layer-1, #fff))' }
|
|
66
|
+
]
|
|
67
|
+
const pages = [{ id: 'groups', label: '群聊会话' }, { id: 'tasks', label: '任务看板' }, { id: 'authorizations', label: '授权审批' }, { id: 'archive', label: '归档任务' }, { id: 'alerts', label: '告警' }]
|
|
68
|
+
function ObserverOverlay({ openSession }) {
|
|
69
|
+
const [isOpen] = useOpen()
|
|
70
|
+
const [activePage, setActivePage] = useState('groups')
|
|
71
|
+
const [data, setData] = useState()
|
|
72
|
+
const [error, setError] = useState()
|
|
73
|
+
const [navigationError, setNavigationError] = useState()
|
|
74
|
+
const [updatedAt, setUpdatedAt] = useState()
|
|
75
|
+
const [selectedGroupId, setSelectedGroupId] = useState('')
|
|
76
|
+
const [messagePage, setMessagePage] = useState(1)
|
|
77
|
+
const [hoveredTaskId, setHoveredTaskId] = useState('')
|
|
78
|
+
const [copiedId, setCopiedId] = useState('')
|
|
79
|
+
const [navigatingSessionId, setNavigatingSessionId] = useState('')
|
|
80
|
+
const [alertType, setAlertType] = useState('all')
|
|
81
|
+
const [resolvedAlertPage, setResolvedAlertPage] = useState(1)
|
|
82
|
+
const [authorizationComments, setAuthorizationComments] = useState({})
|
|
83
|
+
const [decidingAuthorizationId, setDecidingAuthorizationId] = useState('')
|
|
84
|
+
const [authorizationPage, setAuthorizationPage] = useState(1)
|
|
85
|
+
const [selectedAuthorizationId, setSelectedAuthorizationId] = useState('')
|
|
86
|
+
const refresh = useCallback(async () => {
|
|
87
|
+
try { setData(await load()); setUpdatedAt(new Date()); setError(undefined) }
|
|
88
|
+
catch (cause) { setError(cause instanceof Error ? cause.message : String(cause)) }
|
|
89
|
+
}, [])
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
if (!isOpen) return undefined
|
|
92
|
+
refresh()
|
|
93
|
+
const timer = window.setInterval(refresh, 5000)
|
|
94
|
+
return () => window.clearInterval(timer)
|
|
95
|
+
}, [isOpen, refresh])
|
|
96
|
+
if (!isOpen) return null
|
|
97
|
+
const activities = new Map((data?.activities || []).map((item) => [item.taskId, item]))
|
|
98
|
+
const groupsById = new Map((data?.groups || []).map((group) => [group.groupId, group]))
|
|
99
|
+
const navigate = async (sessionId, parentSessionId) => {
|
|
100
|
+
try { setNavigationError(undefined); setNavigatingSessionId(sessionId); await openSession(sessionId, parentSessionId); setOpen(false) }
|
|
101
|
+
catch (cause) { setNavigationError(cause instanceof Error ? cause.message : String(cause)) }
|
|
102
|
+
finally { setNavigatingSessionId('') }
|
|
103
|
+
}
|
|
104
|
+
const copyId = async (value) => {
|
|
105
|
+
let copied = false
|
|
106
|
+
try {
|
|
107
|
+
if (globalThis.navigator?.clipboard?.writeText) { await globalThis.navigator.clipboard.writeText(value); copied = true }
|
|
108
|
+
} catch {}
|
|
109
|
+
if (!copied) {
|
|
110
|
+
const input = document.createElement('textarea')
|
|
111
|
+
input.value = value
|
|
112
|
+
input.setAttribute('readonly', '')
|
|
113
|
+
input.style.position = 'fixed'
|
|
114
|
+
input.style.opacity = '0'
|
|
115
|
+
document.body.appendChild(input)
|
|
116
|
+
input.select()
|
|
117
|
+
copied = document.execCommand('copy')
|
|
118
|
+
input.remove()
|
|
119
|
+
}
|
|
120
|
+
if (!copied) throw new Error('当前浏览器未允许复制,请手动选择 ID')
|
|
121
|
+
setCopiedId(value)
|
|
122
|
+
window.setTimeout(() => setCopiedId((current) => current === value ? '' : current), 1500)
|
|
123
|
+
}
|
|
124
|
+
const copyButton = (value, label) => React.createElement('button', { type: 'button', disabled: !value, onClick: (event) => { event.stopPropagation(); copyId(value).catch((cause) => setNavigationError(cause instanceof Error ? cause.message : String(cause))) }, style: { border: `1px solid ${colors.border}`, borderRadius: 7, background: colors.surface2, color: copiedId === value ? 'var(--dsw-alias-state-success-primary, #248a3d)' : colors.muted, padding: '3px 7px', cursor: value ? 'pointer' : 'default', fontFamily: 'inherit', fontSize: 10.5 } }, copiedId === value ? '已复制' : `复制${label}`)
|
|
125
|
+
const groupCards = (data?.groups || []).map((group) => React.createElement('div', { key: group.groupId, style: { ...card, display: 'grid', gap: 7, minWidth: 280, padding: '12px 16px', boxShadow: '0 4px 16px rgba(15,23,42,.07)' } },
|
|
126
|
+
React.createElement('button', { type: 'button', onClick: () => navigate(group.residentSessionId), style: { border: 0, padding: 0, background: 'transparent', color: 'inherit', cursor: 'pointer', textAlign: 'left', display: 'grid', gap: 5, fontFamily: 'inherit' } },
|
|
127
|
+
React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 16 } }, React.createElement('strong', null, group.name || '未命名群'), React.createElement('span', { style: { flex: '0 0 auto', fontSize: 10.5, color: colors.muted } }, fmt(group.messages?.at?.(-1)?.occurredAt || group.updatedAt))),
|
|
128
|
+
React.createElement('div', { style: { fontSize: 11, color: colors.muted, overflowWrap: 'anywhere' } }, group.groupId)),
|
|
129
|
+
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 8 } }, React.createElement('code', { title: group.residentSessionId || '', style: { flex: 1, minWidth: 0, fontSize: 12, overflow: 'hidden', textOverflow: 'ellipsis' } }, group.residentSessionId || '尚未建立'), copyButton(group.residentSessionId, '会话 ID'))))
|
|
130
|
+
const selectedGroup = groupsById.get(selectedGroupId) || (data?.groups || [])[0]
|
|
131
|
+
const selectedMessages = [...(selectedGroup?.messages || [])].sort((left, right) => Number(right.sequence || 0) - Number(left.sequence || 0))
|
|
132
|
+
const pageSize = 10
|
|
133
|
+
const pageCount = Math.max(1, Math.ceil(selectedMessages.length / pageSize))
|
|
134
|
+
const currentMessagePage = Math.min(messagePage, pageCount)
|
|
135
|
+
const visibleMessages = selectedMessages.slice((currentMessagePage - 1) * pageSize, currentMessagePage * pageSize)
|
|
136
|
+
const delivery = {
|
|
137
|
+
delivered: { label: '已投递', tone: 'var(--dsw-alias-state-success-primary, #248a3d)' },
|
|
138
|
+
failed: { label: '投递失败', tone: colors.danger },
|
|
139
|
+
pending: { label: '投递中', tone: colors.warning },
|
|
140
|
+
skipped: { label: '历史补拉·未投递', tone: colors.muted },
|
|
141
|
+
unknown: { label: '历史状态未知', tone: colors.muted },
|
|
142
|
+
}
|
|
143
|
+
const messageRows = visibleMessages.map((message) => {
|
|
144
|
+
const status = delivery[message.agentDeliveryStatus] || delivery.unknown
|
|
145
|
+
return React.createElement('tr', { key: message.messageId },
|
|
146
|
+
React.createElement('td', { style: { padding: '11px 12px', borderTop: `1px solid ${colors.border}`, width: 150, verticalAlign: 'top' } }, React.createElement('strong', { style: { fontSize: 12 } }, message.senderName || message.senderOpenDingTalkId || '发送人未记录'), React.createElement('div', { style: { marginTop: 4, fontSize: 10.5, color: colors.muted } }, fmt(message.occurredAt))),
|
|
147
|
+
React.createElement('td', { title: message.text, style: { padding: '11px 12px', borderTop: `1px solid ${colors.border}`, fontSize: 12, lineHeight: 1.55, overflowWrap: 'anywhere' } }, message.text || '(空消息)'),
|
|
148
|
+
React.createElement('td', { style: { padding: '11px 12px', borderTop: `1px solid ${colors.border}`, width: 150, verticalAlign: 'top' } }, React.createElement('span', { title: message.agentDeliveryError || '', style: pill(status.tone) }, status.label)),
|
|
149
|
+
React.createElement('td', { style: { padding: '11px 12px', borderTop: `1px solid ${colors.border}`, width: 150, verticalAlign: 'top' } }, React.createElement('code', { title: message.messageId, style: { fontSize: 10, color: colors.muted } }, `#${message.sequence ?? '—'} · ${short(message.messageId)}`)))
|
|
150
|
+
})
|
|
151
|
+
const renderTaskCard = (task) => {
|
|
152
|
+
const group = groupsById.get(task.groupId)
|
|
153
|
+
const activity = activities.get(task.taskId)
|
|
154
|
+
const queued = task.state === 'queued'
|
|
155
|
+
const summary = task.waitingReason || task.completion || activity?.type || activity?.eventType
|
|
156
|
+
const hovered = hoveredTaskId === task.taskId
|
|
157
|
+
const navigating = navigatingSessionId === task.childSessionId
|
|
158
|
+
return React.createElement('div', { key: task.taskId, onMouseEnter: () => setHoveredTaskId(task.taskId), onMouseLeave: () => setHoveredTaskId(''), style: { width: '100%', minWidth: 0, boxSizing: 'border-box', border: `1px solid ${colors.border}`, borderRadius: 12, background: colors.cardSurface, boxShadow: hovered ? '0 8px 20px rgba(15,23,42,.08), 0 2px 6px rgba(15,23,42,.05)' : 'var(--dsw-shadow-card, 0 1px 2px rgba(0,0,0,.08))', padding: '11px 13px', color: 'inherit', display: 'grid', gap: 6, opacity: queued ? 0.72 : 1, transition: 'box-shadow 180ms ease' } },
|
|
159
|
+
React.createElement('button', { type: 'button', disabled: queued || navigating, onClick: () => navigate(task.childSessionId, group?.residentSessionId), style: { width: '100%', minWidth: 0, boxSizing: 'border-box', border: 0, padding: 0, background: 'transparent', color: 'inherit', cursor: queued || navigating ? 'default' : 'pointer', textAlign: 'left', display: 'grid', gap: 6, fontFamily: 'inherit' } },
|
|
160
|
+
React.createElement('strong', { title: task.objective, style: { fontSize: 13, lineHeight: 1.45, display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden', overflowWrap: 'anywhere' } }, task.objective),
|
|
161
|
+
React.createElement('div', { style: { display: 'flex', alignItems: 'center', minWidth: 0 } }, React.createElement('span', { title: group?.name || task.groupId, style: { ...pill(colors.muted), maxWidth: '100%', padding: '2px 7px', fontSize: 10.5, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, group?.name || task.groupId)),
|
|
162
|
+
summary ? React.createElement('div', { title: String(summary), style: { minWidth: 0, fontSize: 11, lineHeight: 1.55, color: task.waitingReason ? colors.warning : colors.muted, display: '-webkit-box', WebkitLineClamp: 4, WebkitBoxOrient: 'vertical', overflow: 'hidden', overflowWrap: 'anywhere', wordBreak: 'break-word' } }, summary) : null,
|
|
163
|
+
React.createElement('div', { style: { minWidth: 0, marginTop: 3, paddingTop: 9, borderTop: `1px solid ${colors.border}`, display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 } },
|
|
164
|
+
React.createElement('span', { title: task.updatedAt, style: { minWidth: 0, fontSize: 10.5, color: colors.muted, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, `最后活动 ${fmt(task.updatedAt)}`),
|
|
165
|
+
React.createElement('div', { style: { flex: '0 0 auto', display: 'flex', alignItems: 'center', gap: 5 } }, copyButton(task.taskId, ' Task'), task.state === 'completed' && !task.archivedAt ? React.createElement('button', { type: 'button', onClick: async (event) => { event.stopPropagation(); try { await post(`/tasks/${encodeURIComponent(task.taskId)}/archive`); await refresh() } catch (cause) { setNavigationError(cause instanceof Error ? cause.message : String(cause)) } }, style: { flex: '0 0 auto', border: `1px solid ${colors.border}`, borderRadius: 7, background: colors.surface2, color: colors.muted, padding: '3px 7px', cursor: 'pointer', fontFamily: 'inherit', fontSize: 10.5 } }, '归档') : null)),
|
|
166
|
+
navigating ? React.createElement('div', { role: 'status', style: { fontSize: 11.5, color: colors.accent } }, '正在打开会话…') : queued ? React.createElement('div', { style: { fontSize: 11.5, color: colors.muted } }, '等待执行,尚无对话和轨迹') : null),
|
|
167
|
+
null)
|
|
168
|
+
}
|
|
169
|
+
const alertCategories = [
|
|
170
|
+
{ id: 'all', label: '全部类型' },
|
|
171
|
+
{ id: 'message-channel', label: '消息通道' },
|
|
172
|
+
{ id: 'leaf-session', label: '叶子会话' },
|
|
173
|
+
{ id: 'goal', label: '任务目标' },
|
|
174
|
+
{ id: 'runtime', label: '运行时' }
|
|
175
|
+
]
|
|
176
|
+
const classifyAlert = (alert) => {
|
|
177
|
+
const fingerprint = String(alert.fingerprint || '')
|
|
178
|
+
if (fingerprint.startsWith('dws-') || fingerprint.includes('consumer')) return alertCategories[1]
|
|
179
|
+
if (fingerprint.startsWith('leaf-session') || fingerprint.startsWith('leaf-paused')) return alertCategories[2]
|
|
180
|
+
if (fingerprint.startsWith('leaf-goal')) return alertCategories[3]
|
|
181
|
+
return alertCategories[4]
|
|
182
|
+
}
|
|
183
|
+
const renderAlert = (alert, index) => {
|
|
184
|
+
const active = alert.status !== 'resolved'
|
|
185
|
+
const category = classifyAlert(alert)
|
|
186
|
+
return React.createElement('div', { key: alert.alertId || alert.fingerprint || index, style: { padding: '12px 0', borderTop: `1px solid ${colors.border}` } },
|
|
187
|
+
React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', gap: 12 } }, React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 8 } }, React.createElement('strong', { style: { color: active ? colors.danger : 'var(--dsw-alias-state-success-primary, #248a3d)' } }, active ? '当前异常' : '已恢复'), React.createElement('span', { style: pill(colors.muted) }, category.label)), React.createElement('span', { style: { fontSize: 12, color: colors.muted } }, fmt(active ? alert.lastSeenAt : alert.resolvedAt || alert.lastSeenAt))),
|
|
188
|
+
React.createElement('div', { style: { marginTop: 5, fontSize: 12 } }, alert.message || alert.detail || alert.reason || JSON.stringify(alert)),
|
|
189
|
+
React.createElement('code', { style: { display: 'block', marginTop: 6, fontSize: 10.5, color: colors.muted } }, `${alert.taskId} · ${alert.fingerprint} · ${alert.count || 1} 次`))
|
|
190
|
+
}
|
|
191
|
+
const activeAlerts = (data?.alerts || []).filter((alert) => alert.status !== 'resolved').sort((left, right) => String(right.lastSeenAt).localeCompare(String(left.lastSeenAt)))
|
|
192
|
+
const resolvedAlerts = (data?.alerts || []).filter((alert) => alert.status === 'resolved').sort((left, right) => String(right.resolvedAt || right.lastSeenAt).localeCompare(String(left.resolvedAt || left.lastSeenAt)))
|
|
193
|
+
const matchesAlertType = (alert) => alertType === 'all' || classifyAlert(alert).id === alertType
|
|
194
|
+
const filteredActiveAlerts = activeAlerts.filter(matchesAlertType)
|
|
195
|
+
const filteredResolvedAlerts = resolvedAlerts.filter(matchesAlertType)
|
|
196
|
+
const resolvedAlertPageSize = 10
|
|
197
|
+
const resolvedAlertPageCount = Math.max(1, Math.ceil(filteredResolvedAlerts.length / resolvedAlertPageSize))
|
|
198
|
+
const currentResolvedAlertPage = Math.min(resolvedAlertPage, resolvedAlertPageCount)
|
|
199
|
+
const visibleResolvedAlerts = filteredResolvedAlerts.slice((currentResolvedAlertPage - 1) * resolvedAlertPageSize, currentResolvedAlertPage * resolvedAlertPageSize)
|
|
200
|
+
const bucketColumns = taskBuckets.map((bucket) => {
|
|
201
|
+
const tasks = (data?.tasks || []).filter((task) => task.state === bucket.state && !task.archivedAt).sort((left, right) => String(right.updatedAt).localeCompare(String(left.updatedAt)))
|
|
202
|
+
const taskCards = tasks.length ? tasks.map(renderTaskCard) : [React.createElement('div', { key: 'empty', style: { border: `1px dashed ${colors.border}`, borderRadius: 10, minHeight: 90, display: 'grid', placeItems: 'center', color: colors.muted, fontSize: 12 } }, '暂无任务')]
|
|
203
|
+
return React.createElement('div', { key: bucket.state, style: { boxSizing: 'border-box', borderRadius: 14, background: bucket.background, boxShadow: '0 4px 16px rgba(15,23,42,.07)', padding: 10, display: 'flex', flexDirection: 'column', height: 'max(1000px, calc(100dvh - 120px))', minHeight: 'max(1000px, calc(100dvh - 120px))', maxHeight: 'max(1000px, calc(100dvh - 120px))', overflow: 'hidden' } },
|
|
204
|
+
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 8, padding: '4px 6px 10px' } }, React.createElement('strong', { style: { display: 'inline-flex', alignItems: 'center', borderRadius: 999, padding: '3px 9px', fontSize: 11.5, fontWeight: 650, lineHeight: 1.4, color: 'var(--dsw-alias-label-primary-inverted, #fff)', background: bucket.tone } }, bucket.label), React.createElement('span', { style: { borderRadius: 999, background: colors.cardSurface, padding: '2px 7px', fontSize: 10.5, color: colors.muted } }, tasks.length)),
|
|
205
|
+
React.createElement('div', { style: { flex: 1, minHeight: 0, padding: '8px 12px 16px 8px', display: 'flex', flexDirection: 'column', gap: 10, overflowY: 'scroll', scrollbarGutter: 'stable' } }, ...taskCards))
|
|
206
|
+
})
|
|
207
|
+
const menu = React.createElement('nav', { 'aria-label': '运行看板视图', style: { minWidth: 0, display: 'flex', justifyContent: 'center', gap: 4, overflowX: 'auto' } },
|
|
208
|
+
...pages.map((page) => React.createElement('button', { key: page.id, type: 'button', onClick: () => setActivePage(page.id), 'aria-current': activePage === page.id ? 'page' : undefined, style: { border: 0, borderRadius: 8, padding: '8px 14px', cursor: 'pointer', font: 'inherit', fontSize: 13, fontWeight: activePage === page.id ? 650 : 400, color: activePage === page.id ? colors.accent : 'inherit', background: activePage === page.id ? 'color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent)' : 'transparent' } }, page.label)))
|
|
209
|
+
const header = React.createElement('header', { style: { position: 'sticky', top: 0, zIndex: 2, display: 'grid', gridTemplateColumns: 'minmax(220px, auto) minmax(300px, 1fr) auto', alignItems: 'center', gap: 18, padding: '10px 24px', borderBottom: `1px solid ${colors.border}`, background: colors.surface } },
|
|
210
|
+
React.createElement('div', { style: { minWidth: 0 } }, React.createElement('strong', { style: { fontSize: 18, whiteSpace: 'nowrap' } }, '钉钉群聊运行看板'), React.createElement('span', { style: { marginLeft: 12, fontSize: 12, color: colors.muted, whiteSpace: 'nowrap' } }, `运行态 · ${updatedAt ? fmt(updatedAt) : '正在连接'}`)),
|
|
211
|
+
menu,
|
|
212
|
+
React.createElement('div', { style: { display: 'flex', gap: 8 } }, React.createElement('button', { type: 'button', onClick: refresh, style: { border: `1px solid ${colors.border}`, borderRadius: 8, background: 'transparent', color: 'inherit', padding: '7px 12px', cursor: 'pointer' } }, '刷新'), React.createElement('button', { type: 'button', onClick: () => setOpen(false), style: { border: 0, borderRadius: 8, background: colors.surface2, color: 'inherit', padding: '7px 12px', cursor: 'pointer' } }, '关闭')))
|
|
213
|
+
const groupsPage = React.createElement(React.Fragment, null,
|
|
214
|
+
React.createElement('section', null, React.createElement('h2', { style: { margin: '0 0 12px', fontSize: 16 } }, '群聊常驻会话'), React.createElement('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(280px, 1fr))', gap: 12 } }, ...(groupCards.length ? groupCards : [React.createElement('div', { key: 'empty', style: card }, '暂无常驻群')]))),
|
|
215
|
+
React.createElement('section', null,
|
|
216
|
+
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 12, marginBottom: 12 } },
|
|
217
|
+
React.createElement('h2', { style: { margin: 0, fontSize: 16 } }, '群聊消息'),
|
|
218
|
+
React.createElement('select', { 'aria-label': '选择群聊会话', value: selectedGroup?.groupId || '', onChange: (event) => { setSelectedGroupId(event.target.value); setMessagePage(1) }, style: { minWidth: 240, border: `1px solid ${colors.border}`, borderRadius: 8, background: colors.cardSurface, color: 'inherit', padding: '7px 10px', fontFamily: 'inherit', fontSize: 12 } }, ...(data?.groups || []).map((group) => React.createElement('option', { key: group.groupId, value: group.groupId }, group.name || group.groupId))),
|
|
219
|
+
React.createElement('span', { style: { fontSize: 11, color: colors.muted } }, `${selectedMessages.length} 条 · 每页 ${pageSize} 条`)),
|
|
220
|
+
React.createElement('div', { style: { ...card, padding: 0, overflow: 'hidden', boxShadow: '0 4px 16px rgba(15,23,42,.07)' } },
|
|
221
|
+
React.createElement('div', { style: { overflowX: 'auto' } }, React.createElement('table', { style: { width: '100%', minWidth: 820, borderCollapse: 'collapse', tableLayout: 'fixed' } },
|
|
222
|
+
React.createElement('thead', null, React.createElement('tr', { style: { background: colors.surface2, textAlign: 'left', fontSize: 11, color: colors.muted } }, React.createElement('th', { style: { padding: '14px 12px', width: 150 } }, '发送人 / 时间'), React.createElement('th', { style: { padding: '14px 12px' } }, '消息内容'), React.createElement('th', { style: { padding: '14px 12px', width: 150 } }, 'Agent 投递'), React.createElement('th', { style: { padding: '14px 12px', width: 150 } }, '消息'))),
|
|
223
|
+
React.createElement('tbody', null, ...(messageRows.length ? messageRows : [React.createElement('tr', { key: 'empty' }, React.createElement('td', { colSpan: 4, style: { padding: 36, textAlign: 'center', color: colors.muted, fontSize: 12 } }, '暂无群聊消息'))])))),
|
|
224
|
+
React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', alignItems: 'center', gap: 8, padding: '10px 12px' } },
|
|
225
|
+
React.createElement('button', { type: 'button', disabled: currentMessagePage <= 1, onClick: () => setMessagePage((page) => Math.max(1, page - 1)), style: { border: `1px solid ${colors.border}`, borderRadius: 8, background: colors.cardSurface, color: 'inherit', padding: '5px 10px', cursor: currentMessagePage <= 1 ? 'default' : 'pointer', opacity: currentMessagePage <= 1 ? 0.45 : 1 } }, '上一页'),
|
|
226
|
+
React.createElement('span', { style: { fontSize: 11, color: colors.muted } }, `${currentMessagePage} / ${pageCount}`),
|
|
227
|
+
React.createElement('button', { type: 'button', disabled: currentMessagePage >= pageCount, onClick: () => setMessagePage((page) => Math.min(pageCount, page + 1)), style: { border: `1px solid ${colors.border}`, borderRadius: 8, background: colors.cardSurface, color: 'inherit', padding: '5px 10px', cursor: currentMessagePage >= pageCount ? 'default' : 'pointer', opacity: currentMessagePage >= pageCount ? 0.45 : 1 } }, '下一页')))))
|
|
228
|
+
const tasksPage = React.createElement('section', null, React.createElement('h2', { style: { margin: '0 0 12px', fontSize: 16 } }, '任务'), React.createElement('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(4, minmax(320px, 1fr))', gap: 16, overflowX: 'auto', alignItems: 'start', paddingBottom: 8 } }, ...bucketColumns))
|
|
229
|
+
const authorizationStatus = {
|
|
230
|
+
'pending-send': { label: '待审批', tone: colors.warning },
|
|
231
|
+
'waiting-reply': { label: '待审批', tone: colors.warning },
|
|
232
|
+
answered: { label: '已处理', tone: colors.muted },
|
|
233
|
+
}
|
|
234
|
+
const authorizationDecision = { approved: { label: '已批准', tone: 'var(--dsw-alias-state-success-primary, #248a3d)' }, rejected: { label: '已拒绝', tone: colors.danger } }
|
|
235
|
+
const decideAuthorization = async (requestId, decision) => {
|
|
236
|
+
try {
|
|
237
|
+
setNavigationError(undefined); setDecidingAuthorizationId(requestId)
|
|
238
|
+
await post(`/authorizations/${encodeURIComponent(requestId)}/decision`, { decision, comment: authorizationComments[requestId] || '' })
|
|
239
|
+
await refresh()
|
|
240
|
+
} catch (cause) { setNavigationError(cause instanceof Error ? cause.message : String(cause)) }
|
|
241
|
+
finally { setDecidingAuthorizationId('') }
|
|
242
|
+
}
|
|
243
|
+
const authorizationItems = [...(data?.authorizations || [])].sort((left, right) => {
|
|
244
|
+
const leftPending = left.status === 'answered' ? 0 : 1; const rightPending = right.status === 'answered' ? 0 : 1
|
|
245
|
+
return rightPending - leftPending || String(right.createdAt || '').localeCompare(String(left.createdAt || ''))
|
|
246
|
+
})
|
|
247
|
+
const authorizationPageSize = 10
|
|
248
|
+
const authorizationPageCount = Math.max(1, Math.ceil(authorizationItems.length / authorizationPageSize))
|
|
249
|
+
const currentAuthorizationPage = Math.min(authorizationPage, authorizationPageCount)
|
|
250
|
+
const visibleAuthorizationItems = authorizationItems.slice((currentAuthorizationPage - 1) * authorizationPageSize, currentAuthorizationPage * authorizationPageSize)
|
|
251
|
+
const selectedAuthorization = authorizationItems.find((item) => item.requestId === selectedAuthorizationId)
|
|
252
|
+
const authorizationDetailField = (label, value) => React.createElement('div', { style: { display: 'grid', gap: 5 } },
|
|
253
|
+
React.createElement('strong', { style: { fontSize: 11, color: colors.muted } }, label),
|
|
254
|
+
React.createElement('div', { style: { fontSize: 13, lineHeight: 1.65, whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' } }, value || '—'))
|
|
255
|
+
const authorizationRows = visibleAuthorizationItems.map((item) => {
|
|
256
|
+
const pending = item.status !== 'answered'
|
|
257
|
+
const status = pending ? authorizationStatus[item.status] : authorizationDecision[item.decision] || authorizationStatus.answered
|
|
258
|
+
const group = groupsById.get(item.groupId)
|
|
259
|
+
return React.createElement('div', { key: item.requestId, style: { width: '100%', minWidth: 0, boxSizing: 'border-box', display: 'grid', gridTemplateColumns: '88px minmax(260px, 1.1fr) minmax(300px, 1.5fr) 130px 44px', alignItems: 'center', gap: 18, borderTop: `1px solid ${colors.border}`, background: 'transparent', color: 'inherit', padding: '15px 18px' } },
|
|
260
|
+
React.createElement('span', { style: { ...pill(status.tone), justifySelf: 'start', width: 'max-content', whiteSpace: 'nowrap', fontSize: 11, lineHeight: 1.4 } }, status.label),
|
|
261
|
+
React.createElement('span', { style: { minWidth: 0 } }, React.createElement('strong', { title: item.objective, style: { display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden', fontSize: 12.5, lineHeight: 1.5 } }, item.objective), React.createElement('span', { style: { display: 'block', marginTop: 5, color: colors.muted, fontSize: 10.5, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, group?.name || item.groupId)),
|
|
262
|
+
React.createElement('span', { style: { minWidth: 0 } }, React.createElement('span', { title: item.requestedAction, style: { display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden', fontSize: 11.5, lineHeight: 1.55, color: colors.muted } }, item.requestedAction), item.risk && item.risk !== '未单独说明' ? React.createElement('span', { style: { display: 'block', marginTop: 6, color: colors.danger, fontSize: 10.5, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, `风险 · ${item.risk}`) : null),
|
|
263
|
+
React.createElement('span', { style: { color: colors.muted, fontSize: 10.5, lineHeight: 1.5 } }, fmt(item.createdAt)),
|
|
264
|
+
React.createElement('button', { type: 'button', onClick: () => setSelectedAuthorizationId(item.requestId), style: { border: 0, background: 'transparent', color: colors.accent, padding: '5px 0', cursor: 'pointer', fontFamily: 'inherit', fontSize: 11.5, fontWeight: 600 } }, '查看'))
|
|
265
|
+
})
|
|
266
|
+
const authorizationDetail = selectedAuthorization ? React.createElement('div', { role: 'presentation', onMouseDown: (event) => { if (event.target === event.currentTarget) setSelectedAuthorizationId('') }, style: { position: 'fixed', inset: 0, zIndex: 1100, display: 'flex', justifyContent: 'flex-end', background: 'rgba(0,0,0,.28)', backdropFilter: 'blur(2px)' } },
|
|
267
|
+
React.createElement('section', { role: 'dialog', 'aria-modal': true, 'aria-label': '授权申请单详情', style: { width: 'min(640px, 100%)', height: '100%', display: 'flex', flexDirection: 'column', overflow: 'hidden', borderLeft: `1px solid ${colors.border}`, background: colors.surface, boxShadow: '-20px 0 60px rgba(0,0,0,.2)' } },
|
|
268
|
+
React.createElement('header', { style: { flex: '0 0 auto', display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 20, padding: '20px 24px 18px', borderBottom: `1px solid ${colors.border}`, background: colors.surface } },
|
|
269
|
+
React.createElement('div', { style: { minWidth: 0 } }, React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 9, marginBottom: 9 } }, React.createElement('span', { style: pill(selectedAuthorization.status === 'answered' ? (authorizationDecision[selectedAuthorization.decision]?.tone || colors.muted) : colors.warning) }, selectedAuthorization.status === 'answered' ? (authorizationDecision[selectedAuthorization.decision]?.label || '已处理') : '待审批'), React.createElement('span', { style: { color: colors.muted, fontSize: 11.5 } }, fmt(selectedAuthorization.createdAt))), React.createElement('h3', { style: { margin: 0, fontSize: 19, lineHeight: 1.45 } }, selectedAuthorization.objective || '授权申请'), React.createElement('code', { style: { display: 'block', marginTop: 8, color: colors.muted, fontSize: 10.5 } }, selectedAuthorization.requestId)),
|
|
270
|
+
React.createElement('button', { type: 'button', 'aria-label': '关闭申请单', onClick: () => setSelectedAuthorizationId(''), style: { width: 32, height: 32, border: `1px solid ${colors.border}`, borderRadius: 8, background: colors.surface2, color: 'inherit', cursor: 'pointer', fontSize: 18 } }, '×')),
|
|
271
|
+
React.createElement('div', { style: { flex: '1 1 auto', overflowY: 'auto', padding: '0 24px' } },
|
|
272
|
+
React.createElement('section', { style: { padding: '20px 0', borderBottom: `1px solid ${colors.border}` } }, authorizationDetailField('申请范围', selectedAuthorization.requestedAction)),
|
|
273
|
+
React.createElement('section', { style: { padding: '20px 0', borderBottom: `1px solid ${colors.border}` } }, authorizationDetailField('风险', selectedAuthorization.risk || '未单独说明')),
|
|
274
|
+
React.createElement('section', { style: { padding: '20px 0', borderBottom: `1px solid ${colors.border}` } }, authorizationDetailField('阻塞原因', selectedAuthorization.waitingReason)),
|
|
275
|
+
React.createElement('details', { open: true, style: { padding: '18px 0', borderBottom: `1px solid ${colors.border}` } }, React.createElement('summary', { style: { cursor: 'pointer', fontSize: 12, fontWeight: 600 } }, `现场证据 · ${(selectedAuthorization.evidence || []).length}`), React.createElement('div', { style: { marginTop: 14 } }, authorizationDetailField('', (selectedAuthorization.evidence || []).map((value, index) => `${index + 1}. ${value}`).join('\n')))),
|
|
276
|
+
React.createElement('details', { style: { padding: '18px 0', borderBottom: `1px solid ${colors.border}` } }, React.createElement('summary', { style: { cursor: 'pointer', fontSize: 12, fontWeight: 600 } }, `已尝试 · ${(selectedAuthorization.attemptedActions || []).length}`), React.createElement('div', { style: { marginTop: 14 } }, authorizationDetailField('', (selectedAuthorization.attemptedActions || []).map((value, index) => `${index + 1}. ${value}`).join('\n')))),
|
|
277
|
+
React.createElement('section', { style: { padding: '20px 0', borderBottom: `1px solid ${colors.border}` } }, authorizationDetailField('关联信息', `Task ID:${selectedAuthorization.taskId || '—'}\n群聊:${groupsById.get(selectedAuthorization.groupId)?.name || selectedAuthorization.groupId || '—'}`)),
|
|
278
|
+
selectedAuthorization.status === 'answered' ? React.createElement('section', { style: { padding: '20px 0' } }, authorizationDetailField('批复结果', `${selectedAuthorization.decision === 'approved' ? '批准' : selectedAuthorization.decision === 'rejected' ? '拒绝' : selectedAuthorization.decision || '已处理'}\n批复渠道:${selectedAuthorization.decisionSource === 'web' ? '运行看板' : selectedAuthorization.decisionSource === 'dingtalk' ? '钉钉私聊' : selectedAuthorization.decisionSource || '历史迁移'}\n批复时间:${fmt(selectedAuthorization.decidedAt)}\n批复内容:${selectedAuthorization.reply || '—'}`)) : null),
|
|
279
|
+
selectedAuthorization.status !== 'answered' ? React.createElement('footer', { style: { flex: '0 0 auto', display: 'grid', gridTemplateColumns: '1fr auto auto', gap: 10, alignItems: 'center', padding: '14px 24px', borderTop: `1px solid ${colors.border}`, background: colors.surface } },
|
|
280
|
+
React.createElement('input', { 'aria-label': `审批意见 ${selectedAuthorization.requestId}`, value: authorizationComments[selectedAuthorization.requestId] || '', onChange: (event) => setAuthorizationComments((current) => ({ ...current, [selectedAuthorization.requestId]: event.target.value })), placeholder: '审批意见(可选)', style: { minWidth: 0, border: `1px solid ${colors.border}`, borderRadius: 9, background: colors.surface2, color: 'inherit', padding: '9px 11px', fontFamily: 'inherit', fontSize: 12 } }),
|
|
281
|
+
React.createElement('button', { type: 'button', disabled: decidingAuthorizationId === selectedAuthorization.requestId, onClick: () => decideAuthorization(selectedAuthorization.requestId, 'rejected'), style: { minWidth: 76, border: `1px solid ${colors.danger}`, borderRadius: 9, background: 'transparent', color: colors.danger, padding: '9px 14px', cursor: 'pointer', fontWeight: 600 } }, '拒绝'),
|
|
282
|
+
React.createElement('button', { type: 'button', disabled: decidingAuthorizationId === selectedAuthorization.requestId, onClick: () => decideAuthorization(selectedAuthorization.requestId, 'approved'), style: { minWidth: 76, border: 0, borderRadius: 9, background: colors.accent, color: '#fff', padding: '10px 14px', cursor: 'pointer', fontWeight: 600 } }, decidingAuthorizationId === selectedAuthorization.requestId ? '处理中…' : '批准')) : null)) : null
|
|
283
|
+
const authorizationsPage = React.createElement(React.Fragment, null, React.createElement('section', null,
|
|
284
|
+
React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12 } }, React.createElement('h2', { style: { margin: 0, fontSize: 16 } }, '授权审批'), React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 8 } }, React.createElement('span', { style: pill(colors.warning) }, `${authorizationItems.filter((item) => item.status !== 'answered').length} 个待审批`), React.createElement('span', { style: { fontSize: 11, color: colors.muted } }, `${authorizationItems.length} 条 · 每页 ${authorizationPageSize} 条`))),
|
|
285
|
+
React.createElement('div', { style: { ...card, padding: 0, overflow: 'hidden' } },
|
|
286
|
+
React.createElement('div', { style: { minHeight: 44, boxSizing: 'border-box', display: 'grid', gridTemplateColumns: '88px minmax(260px, 1.1fr) minmax(300px, 1.5fr) 130px 44px', alignItems: 'center', gap: 18, padding: '0 18px', background: colors.surface2, color: colors.muted, fontSize: 10.5 } }, React.createElement('span', null, '状态'), React.createElement('span', null, '任务'), React.createElement('span', null, '申请摘要'), React.createElement('span', null, '申请时间'), React.createElement('span', null, '操作')),
|
|
287
|
+
React.createElement('div', null, ...(authorizationRows.length ? authorizationRows : [React.createElement('div', { key: 'empty', style: { padding: 36, textAlign: 'center', color: colors.muted, fontSize: 12 } }, '暂无授权申请')])),
|
|
288
|
+
React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', alignItems: 'center', gap: 8, padding: '10px 12px' } },
|
|
289
|
+
React.createElement('button', { type: 'button', disabled: currentAuthorizationPage <= 1, onClick: () => setAuthorizationPage((page) => Math.max(1, page - 1)), style: { border: `1px solid ${colors.border}`, borderRadius: 8, background: colors.cardSurface, color: 'inherit', padding: '5px 10px', cursor: currentAuthorizationPage <= 1 ? 'default' : 'pointer', opacity: currentAuthorizationPage <= 1 ? 0.45 : 1 } }, '上一页'),
|
|
290
|
+
React.createElement('span', { style: { fontSize: 11, color: colors.muted } }, `${currentAuthorizationPage} / ${authorizationPageCount}`),
|
|
291
|
+
React.createElement('button', { type: 'button', disabled: currentAuthorizationPage >= authorizationPageCount, onClick: () => setAuthorizationPage((page) => Math.min(authorizationPageCount, page + 1)), style: { border: `1px solid ${colors.border}`, borderRadius: 8, background: colors.cardSurface, color: 'inherit', padding: '5px 10px', cursor: currentAuthorizationPage >= authorizationPageCount ? 'default' : 'pointer', opacity: currentAuthorizationPage >= authorizationPageCount ? 0.45 : 1 } }, '下一页')))
|
|
292
|
+
), authorizationDetail)
|
|
293
|
+
const archivedTasks = (data?.tasks || []).filter((task) => task.state === 'completed' && task.archivedAt).sort((left, right) => String(right.archivedAt).localeCompare(String(left.archivedAt)))
|
|
294
|
+
const archivePage = React.createElement('section', null,
|
|
295
|
+
React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12 } }, React.createElement('h2', { style: { margin: 0, fontSize: 16 } }, '归档任务'), React.createElement('span', { style: pill('var(--dsw-alias-state-success-primary, #248a3d)') }, archivedTasks.length)),
|
|
296
|
+
React.createElement('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))', gap: 12 } }, ...(archivedTasks.length ? archivedTasks.map(renderTaskCard) : [React.createElement('div', { key: 'empty', style: { ...card, color: colors.muted } }, '暂无归档任务')]))
|
|
297
|
+
)
|
|
298
|
+
const alertsPage = React.createElement(React.Fragment, null,
|
|
299
|
+
React.createElement('section', { style: { ...card, display: 'flex', alignItems: 'center', gap: 12 } }, React.createElement('strong', { style: { fontSize: 13 } }, '告警类型'), React.createElement('select', { 'aria-label': '筛选告警类型', value: alertType, onChange: (event) => { setAlertType(event.target.value); setResolvedAlertPage(1) }, style: { minWidth: 160, border: `1px solid ${colors.border}`, borderRadius: 8, background: colors.cardSurface, color: 'inherit', padding: '7px 10px', fontFamily: 'inherit', fontSize: 12 } }, ...alertCategories.map((category) => React.createElement('option', { key: category.id, value: category.id }, category.label)))),
|
|
300
|
+
React.createElement('section', { style: card }, React.createElement('h2', { style: { margin: '0 0 14px', fontSize: 16 } }, `当前异常 · ${filteredActiveAlerts.length}`), ...(filteredActiveAlerts.length ? filteredActiveAlerts.map(renderAlert) : [React.createElement('div', { key: 'empty', style: { color: colors.muted } }, '当前没有此类型异常')])),
|
|
301
|
+
React.createElement('section', { style: card },
|
|
302
|
+
React.createElement('h2', { style: { margin: '0 0 14px', fontSize: 16 } }, `已恢复历史 · ${filteredResolvedAlerts.length}`),
|
|
303
|
+
...(visibleResolvedAlerts.length ? visibleResolvedAlerts.map(renderAlert) : [React.createElement('div', { key: 'empty', style: { color: colors.muted } }, '暂无此类型恢复记录')]),
|
|
304
|
+
React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', alignItems: 'center', gap: 8, paddingTop: 12 } },
|
|
305
|
+
React.createElement('button', { type: 'button', disabled: currentResolvedAlertPage <= 1, onClick: () => setResolvedAlertPage((page) => Math.max(1, page - 1)), style: { border: `1px solid ${colors.border}`, borderRadius: 8, background: colors.cardSurface, color: 'inherit', padding: '5px 10px', cursor: currentResolvedAlertPage <= 1 ? 'default' : 'pointer', opacity: currentResolvedAlertPage <= 1 ? 0.45 : 1 } }, '上一页'),
|
|
306
|
+
React.createElement('span', { style: { fontSize: 11, color: colors.muted } }, `${currentResolvedAlertPage} / ${resolvedAlertPageCount} · 每页 ${resolvedAlertPageSize} 条`),
|
|
307
|
+
React.createElement('button', { type: 'button', disabled: currentResolvedAlertPage >= resolvedAlertPageCount, onClick: () => setResolvedAlertPage((page) => Math.min(resolvedAlertPageCount, page + 1)), style: { border: `1px solid ${colors.border}`, borderRadius: 8, background: colors.cardSurface, color: 'inherit', padding: '5px 10px', cursor: currentResolvedAlertPage >= resolvedAlertPageCount ? 'default' : 'pointer', opacity: currentResolvedAlertPage >= resolvedAlertPageCount ? 0.45 : 1 } }, '下一页')))
|
|
308
|
+
)
|
|
309
|
+
const pageContent = activePage === 'tasks' ? tasksPage : activePage === 'authorizations' ? authorizationsPage : activePage === 'archive' ? archivePage : activePage === 'alerts' ? alertsPage : groupsPage
|
|
310
|
+
const main = React.createElement('main', { style: { maxWidth: activePage === 'tasks' ? 1480 : 1240, margin: '0 auto', padding: 24, display: 'grid', gap: 20 } },
|
|
311
|
+
error ? React.createElement('div', { style: { ...card, borderColor: colors.danger, color: colors.danger } }, `无法连接 resident 插件:${error}`) : null,
|
|
312
|
+
navigationError ? React.createElement('div', { style: { ...card, borderColor: colors.danger, color: colors.danger } }, `无法打开 DSH Session:${navigationError}`) : null,
|
|
313
|
+
pageContent
|
|
314
|
+
)
|
|
315
|
+
return React.createElement('div', { style: { pointerEvents: 'auto', position: 'fixed', inset: 0, zIndex: 1000, background: colors.surface, color: 'inherit', overflow: 'auto' } }, header, main)
|
|
316
|
+
}
|
|
317
|
+
function apply(ctx) {
|
|
318
|
+
ctx.slots.inject('sidebar.footer.action', () => ctx.slots.register({ name: 'sidebar.footer.action', id: 'dingtalk-dsh-observer-entry', order: 5, inject: () => ({}) }, FooterAction))
|
|
319
|
+
ctx.slots.inject('shell.overlay', () => ctx.slots.register({ name: 'shell.overlay', id: 'dingtalk-dsh-observer-panel', order: 20, inject: () => ({ openSession: async (sessionId, parentSessionId) => {
|
|
320
|
+
if (parentSessionId) {
|
|
321
|
+
let address = ctx.sessions.subagentAddress(sessionId)
|
|
322
|
+
if (!address) { await ctx.sessions.refreshSubagents(parentSessionId); address = ctx.sessions.subagentAddress(sessionId) }
|
|
323
|
+
if (address) { ctx.sessions.openSubagent(address); return }
|
|
324
|
+
}
|
|
325
|
+
await ctx.sessions.refresh()
|
|
326
|
+
ctx.sessions.open(sessionId)
|
|
327
|
+
} }) }, ObserverOverlay))
|
|
328
|
+
}
|
|
329
|
+
module.exports = { name: 'dingtalk-dsh-observer-client', inject: ['slots', 'sessions'], apply }
|
|
330
|
+
return module.exports
|
|
331
|
+
}
|
|
332
|
+
})
|