agentworkshop 0.7.22 → 0.7.23
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/.output/nitro.json +1 -1
- package/.output/public/_nuxt/{BAbAEW0D.js → BJbhwS5o.js} +1 -1
- package/.output/public/_nuxt/Bo8t5fkJ.js +1 -0
- package/.output/public/_nuxt/{QT54MQV9.js → C53drffV.js} +1 -1
- package/.output/public/_nuxt/{0exC4jVL.js → CEHmSufQ.js} +1 -1
- package/.output/public/_nuxt/{D6B0xnjk.js → Dc32T41C.js} +1 -1
- package/.output/public/_nuxt/{Ci0uX4c4.js → DtzZqC0z.js} +1 -1
- package/.output/public/_nuxt/{c3mwkHWP.js → Ko9mMsJj.js} +3 -3
- package/.output/public/_nuxt/_id_.C-YXjn8Q.css +1 -0
- package/.output/public/_nuxt/_id_.MbzdFygI.css +1 -0
- package/.output/public/_nuxt/_wsId_.BqWBlY7X.css +1 -0
- package/.output/public/_nuxt/builds/latest.json +1 -1
- package/.output/public/_nuxt/builds/meta/1adc96e0-8bbd-4a79-ac7e-b6952556fef8.json +1 -0
- package/.output/public/_nuxt/daq.3OKj58HH.css +1 -0
- package/.output/public/_nuxt/dcw.rXH2fc2Z.css +1 -0
- package/.output/public/_nuxt/entry.OR_psZHp.css +1 -0
- package/.output/public/_nuxt/logs.DMOM-iHe.css +1 -0
- package/.output/server/chunks/_/nitro.mjs +744 -744
- package/.output/server/chunks/build/{_id_-B9RvEQFC.mjs → _id_-DQDqj-cZ.mjs} +31 -31
- package/.output/server/chunks/build/{_id_-B9RvEQFC.mjs.map → _id_-DQDqj-cZ.mjs.map} +1 -1
- package/.output/server/chunks/build/{_id_-CqF0R5W5.mjs → _id_-DY6v6sqB.mjs} +135 -135
- package/.output/server/chunks/build/{_id_-CqF0R5W5.mjs.map → _id_-DY6v6sqB.mjs.map} +1 -1
- package/.output/server/chunks/build/{_wsId_-YC2t_fRt.mjs → _wsId_-C2O5zZsI.mjs} +4 -4
- package/.output/server/chunks/build/{_wsId_-YC2t_fRt.mjs.map → _wsId_-C2O5zZsI.mjs.map} +1 -1
- package/.output/server/chunks/build/{daq-Bhwwz9vp.mjs → daq-B-3xCp_2.mjs} +103 -103
- package/.output/server/chunks/build/{daq-Bhwwz9vp.mjs.map → daq-B-3xCp_2.mjs.map} +1 -1
- package/.output/server/chunks/build/{dcw-CBPaFj2f.mjs → dcw-BL2dhuz7.mjs} +43 -43
- package/.output/server/chunks/build/{dcw-CBPaFj2f.mjs.map → dcw-BL2dhuz7.mjs.map} +1 -1
- package/.output/server/chunks/build/{logs-Bz1p6x_5.mjs → logs-Dto_umFY.mjs} +44 -38
- package/.output/server/chunks/build/logs-Dto_umFY.mjs.map +1 -0
- package/.output/server/chunks/routes/renderer.mjs +1 -1
- package/.output/server/chunks/virtual/entry.mjs +14 -6
- package/.output/server/chunks/virtual/precomputed.mjs +1 -1
- package/.output/server/package.json +1 -1
- package/app/assets/css/main.css +306 -161
- package/app/components/workshop/blocks/ClusterArtifact.vue +0 -16
- package/app/pages/daq/[id].vue +919 -938
- package/app/pages/daq/index.vue +1 -22
- package/app/pages/dcw/[id].vue +1 -4
- package/app/pages/dcw/index.vue +1 -30
- package/app/pages/logs/index.vue +99 -87
- package/i18n/locales/en.ts +3 -0
- package/i18n/locales/zh-CN.ts +5 -0
- package/package.json +1 -1
- package/scripts/_dbg-logs-ui-e2e.py +125 -0
- package/server/data/daqs.json +101 -101
- package/server/data/dcw-rollback.json +21 -10
- package/server/data/dcw-runs.json +25 -27
- package/server/data/dcw-writes.json +12 -0
- package/server/data/dcws.json +505 -216
- package/server/data/line-runs.json +18 -18
- package/.output/public/_nuxt/C_2bTK0C.js +0 -1
- package/.output/public/_nuxt/_id_.C2VHq_vT.css +0 -1
- package/.output/public/_nuxt/_id_.Dswy2p0I.css +0 -1
- package/.output/public/_nuxt/_wsId_.BFyCuUwS.css +0 -1
- package/.output/public/_nuxt/builds/meta/e2341fc0-6f3f-4680-abf8-935a7c9fb912.json +0 -1
- package/.output/public/_nuxt/daq.BTdEun-8.css +0 -1
- package/.output/public/_nuxt/dcw.rpqpOYQ8.css +0 -1
- package/.output/public/_nuxt/entry.DRUzDwmX.css +0 -1
- package/.output/public/_nuxt/logs.BarkrvUp.css +0 -1
- package/.output/server/chunks/build/logs-Bz1p6x_5.mjs.map +0 -1
package/app/pages/daq/index.vue
CHANGED
|
@@ -2225,7 +2225,7 @@ tr.row-recipe-alarm td:first-child { box-shadow: inset 3px 0 0 var(--tone-danger
|
|
|
2225
2225
|
.infra-banner .txt a { color: var(--accent); text-decoration: underline; }
|
|
2226
2226
|
.infra-banner .txt b { font-weight: 600; }
|
|
2227
2227
|
.infra-banner .txt small { margin-left: 8px; font-size: 10.5px; opacity: 0.75; }
|
|
2228
|
-
.infra-banner .pill-btn { flex: 0 0 auto;
|
|
2228
|
+
.infra-banner .pill-btn { flex: 0 0 auto; }
|
|
2229
2229
|
|
|
2230
2230
|
/* ---------- 未确认报警条(S5) ---------- */
|
|
2231
2231
|
.alarm-bar {
|
|
@@ -2351,16 +2351,6 @@ tr.row-recipe-alarm td:first-child { box-shadow: inset 3px 0 0 var(--tone-danger
|
|
|
2351
2351
|
}
|
|
2352
2352
|
.hint { font-size: 10px; color: var(--ink-fainter); }
|
|
2353
2353
|
.test-row { display: flex; gap: 10px; align-items: center; margin: 6px 0 4px; }
|
|
2354
|
-
.pill-btn {
|
|
2355
|
-
padding: 7px 14px;
|
|
2356
|
-
font-size: 12.5px;
|
|
2357
|
-
cursor: pointer;
|
|
2358
|
-
color: var(--paper-raised);
|
|
2359
|
-
background: var(--accent);
|
|
2360
|
-
border: 1px solid var(--accent);
|
|
2361
|
-
border-radius: var(--radius-pill);
|
|
2362
|
-
}
|
|
2363
|
-
.pill-btn:disabled { opacity: 0.5; cursor: default; }
|
|
2364
2354
|
.test-result { font-family: var(--font-mono); font-size: 11px; }
|
|
2365
2355
|
.test-result.ok { color: var(--tone-success-dot); }
|
|
2366
2356
|
.test-result.bad { color: var(--tone-danger-dot); }
|
|
@@ -2383,17 +2373,6 @@ tr.row-recipe-alarm td:first-child { box-shadow: inset 3px 0 0 var(--tone-danger
|
|
|
2383
2373
|
.tpl-table .range { font-size: 11.5px; color: var(--ink-soft); }
|
|
2384
2374
|
.tpl-table .empty { color: var(--ink-fainter); font-size: 12px; text-align: center; padding: 14px 0; }
|
|
2385
2375
|
.tpl-table .actions { white-space: nowrap; }
|
|
2386
|
-
.mini-btn {
|
|
2387
|
-
padding: 3px 10px;
|
|
2388
|
-
font-size: 11.5px;
|
|
2389
|
-
cursor: pointer;
|
|
2390
|
-
color: var(--ink-soft);
|
|
2391
|
-
background: var(--paper-deep);
|
|
2392
|
-
border: 1px solid var(--line-strong);
|
|
2393
|
-
border-radius: var(--radius-chip);
|
|
2394
|
-
}
|
|
2395
|
-
.mini-btn.danger { color: var(--tone-danger-dot); }
|
|
2396
|
-
.mini-btn:hover { border-color: var(--accent); color: var(--accent); }
|
|
2397
2376
|
|
|
2398
2377
|
/* ---------- 节点筛选工具条 ---------- */
|
|
2399
2378
|
.tbl-toolbar {
|
package/app/pages/dcw/[id].vue
CHANGED
|
@@ -2443,7 +2443,7 @@ h1 { margin: 2px 0 4px; font-size: 30px; font-weight: 400; letter-spacing: -0.01
|
|
|
2443
2443
|
.write-row { display: flex; gap: 6px; align-items: center; }
|
|
2444
2444
|
.write-inp { width: 92px; padding: 4px 8px; }
|
|
2445
2445
|
.write-btn { padding: 5px 12px; }
|
|
2446
|
-
.mini-btn {
|
|
2446
|
+
.mini-btn { margin-right: 4px; }
|
|
2447
2447
|
.mini-btn.danger { color: var(--tone-danger-dot); }
|
|
2448
2448
|
.mini-btn:hover { border-color: var(--accent); color: var(--accent); }
|
|
2449
2449
|
.inp { padding: 5px 9px; font-size: 12.5px; color: var(--ink); background: var(--paper-deep); border: 1px solid var(--line-strong); border-radius: var(--radius-chip); }
|
|
@@ -2497,9 +2497,6 @@ h1 { margin: 2px 0 4px; font-size: 30px; font-weight: 400; letter-spacing: -0.01
|
|
|
2497
2497
|
.ver-diff { margin: 4px 0 0; font-size: 11px; color: var(--ink-faint); }
|
|
2498
2498
|
.sec-label { margin: 14px 0 6px; font-size: 11px; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; color: var(--ink-faint); }
|
|
2499
2499
|
.runs { margin-top: 6px; }
|
|
2500
|
-
.pill-btn { padding: 6px 14px; font-size: 12.5px; cursor: pointer; color: var(--paper-raised); background: var(--accent); border: 1px solid var(--accent); border-radius: var(--radius-pill); }
|
|
2501
|
-
.pill-btn.outline { color: var(--ink); background: var(--paper-raised); border-color: var(--line-strong); }
|
|
2502
|
-
.pill-btn:disabled { opacity: 0.5; cursor: default; }
|
|
2503
2500
|
|
|
2504
2501
|
.modal-mask { position: fixed; z-index: 50; inset: 0; display: flex; align-items: center; justify-content: center; background: var(--scrim); backdrop-filter: blur(2px); }
|
|
2505
2502
|
.modal { width: 680px; max-width: 94vw; max-height: 88vh; overflow-y: auto; padding: 22px 24px; background: var(--surface-glass-strong); backdrop-filter: var(--aurora-blur); -webkit-backdrop-filter: var(--aurora-blur); border: 1px solid var(--glass-line); border-radius: var(--radius-panel); box-shadow: var(--glass-edge), var(--shadow-float); }
|
package/app/pages/dcw/index.vue
CHANGED
|
@@ -925,25 +925,7 @@ const builtinCount = computed(() => dcw.templates.filter(t => t.builtin).length)
|
|
|
925
925
|
border-radius: var(--radius-panel-sm);
|
|
926
926
|
}
|
|
927
927
|
.lc-ctl .inp:focus { outline: none; border-color: var(--tone-success-dot); box-shadow: 0 0 0 3px var(--accent-soft); }
|
|
928
|
-
.pill-btn {
|
|
929
|
-
flex: none;
|
|
930
|
-
height: 30px;
|
|
931
|
-
padding: 0 14px;
|
|
932
|
-
font-size: 11.5px;
|
|
933
|
-
font-weight: 600;
|
|
934
|
-
color: var(--on-accent);
|
|
935
|
-
background: var(--accent);
|
|
936
|
-
border: 0;
|
|
937
|
-
border-radius: var(--radius-panel-sm);
|
|
938
|
-
cursor: pointer;
|
|
939
|
-
transition: background 0.15s, box-shadow 0.15s;
|
|
940
|
-
}
|
|
941
|
-
.pill-btn:hover:not(:disabled) { background: var(--accent-strong); box-shadow: 0 0 14px var(--accent-soft); }
|
|
942
|
-
.pill-btn:disabled { opacity: 0.4; cursor: default; }
|
|
943
|
-
.pill-btn.danger,
|
|
944
|
-
.pill-btn.stop { background: var(--tone-danger-bg); color: var(--tone-danger-dot); }
|
|
945
|
-
.pill-btn.danger:hover:not(:disabled),
|
|
946
|
-
.pill-btn.stop:hover:not(:disabled) { background: color-mix(in srgb, var(--tone-danger-dot) 26%, transparent); box-shadow: none; }
|
|
928
|
+
.lc-ctl .pill-btn { flex: none; }
|
|
947
929
|
.lc-manage {
|
|
948
930
|
font-size: 11.5px;
|
|
949
931
|
font-weight: 600;
|
|
@@ -1065,15 +1047,4 @@ const builtinCount = computed(() => dcw.templates.filter(t => t.builtin).length)
|
|
|
1065
1047
|
.tpl-form { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
|
|
1066
1048
|
.tpl-sem { flex: 1; color: var(--ink-faint); }
|
|
1067
1049
|
textarea.inp { height: auto; padding: 6px 9px; font-size: 11.5px; resize: vertical; }
|
|
1068
|
-
.mini-btn {
|
|
1069
|
-
padding: 4px 10px;
|
|
1070
|
-
font-size: 10.5px;
|
|
1071
|
-
color: var(--ink-faint);
|
|
1072
|
-
background: var(--paper-deep);
|
|
1073
|
-
border: 1px solid var(--line-strong);
|
|
1074
|
-
border-radius: 7px;
|
|
1075
|
-
cursor: pointer;
|
|
1076
|
-
}
|
|
1077
|
-
.mini-btn:hover { border-color: var(--ink-faint); color: var(--ink); }
|
|
1078
|
-
.mini-btn.danger { color: var(--tone-danger-dot); }
|
|
1079
1050
|
</style>
|
package/app/pages/logs/index.vue
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="page">
|
|
3
|
-
<!--
|
|
4
|
-
<header class="head">
|
|
3
|
+
<!-- 页头(aw-page-head 规范:kicker + 大标题 + 描述)+ 手动记录入口 -->
|
|
4
|
+
<header class="aw-page-head">
|
|
5
5
|
<div>
|
|
6
6
|
<p class="aw-kicker">
|
|
7
7
|
agentworkshop / audit log
|
|
8
8
|
</p>
|
|
9
|
-
<h1
|
|
10
|
-
{{ $t('logs.title') }}
|
|
11
|
-
</h1>
|
|
9
|
+
<h1>{{ $t('logs.title') }}</h1>
|
|
12
10
|
<p class="sub">
|
|
13
11
|
{{ $t('logs.sub') }}
|
|
14
12
|
</p>
|
|
@@ -182,67 +180,78 @@
|
|
|
182
180
|
</tr>
|
|
183
181
|
</thead>
|
|
184
182
|
<tbody>
|
|
185
|
-
<
|
|
183
|
+
<template
|
|
186
184
|
v-for="row in opsLog.results"
|
|
187
|
-
:key="
|
|
185
|
+
:key="row.id"
|
|
188
186
|
>
|
|
189
|
-
<
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
<
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
<
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
<
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
<
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
187
|
+
<tr>
|
|
188
|
+
<td class="mono dim">
|
|
189
|
+
{{ fmtTime(row.at) }}
|
|
190
|
+
</td>
|
|
191
|
+
<td>
|
|
192
|
+
<span
|
|
193
|
+
class="src-badge"
|
|
194
|
+
:class="row.actorKind"
|
|
195
|
+
>{{ srcLabel(row.actorKind) }}</span>
|
|
196
|
+
</td>
|
|
197
|
+
<td class="actor">
|
|
198
|
+
{{ row.actorName || row.actor || '—' }}
|
|
199
|
+
</td>
|
|
200
|
+
<td>
|
|
201
|
+
<span
|
|
202
|
+
class="kind-chip"
|
|
203
|
+
:class="row.kind"
|
|
204
|
+
>{{ kindLabel(row.kind) }}</span>
|
|
205
|
+
</td>
|
|
206
|
+
<td class="summary">
|
|
207
|
+
{{ row.summary }}
|
|
208
|
+
<small class="mono dim action">{{ row.action }}</small>
|
|
209
|
+
</td>
|
|
210
|
+
<td class="scope">
|
|
211
|
+
<span
|
|
212
|
+
v-if="lineName(row.lineId)"
|
|
213
|
+
class="scope-chip"
|
|
214
|
+
:title="$t('logs.fLine')"
|
|
215
|
+
>{{ lineName(row.lineId) }}</span>
|
|
216
|
+
<span
|
|
217
|
+
v-if="productName(row.productId)"
|
|
218
|
+
class="scope-chip"
|
|
219
|
+
:title="$t('logs.fProduct')"
|
|
220
|
+
>{{ productName(row.productId) }}</span>
|
|
221
|
+
<span
|
|
222
|
+
v-if="recipeName(row.recipeId)"
|
|
223
|
+
class="scope-chip"
|
|
224
|
+
:title="'Recipe'"
|
|
225
|
+
>{{ recipeName(row.recipeId) }}</span>
|
|
226
|
+
<span
|
|
227
|
+
v-if="!row.lineId && !row.productId && !row.recipeId"
|
|
228
|
+
class="dim"
|
|
229
|
+
>—</span>
|
|
230
|
+
</td>
|
|
231
|
+
<td class="detail-cell">
|
|
232
|
+
<button
|
|
233
|
+
v-if="hasDetail(row)"
|
|
234
|
+
class="mini-btn"
|
|
235
|
+
@click="toggle(row)"
|
|
236
|
+
>
|
|
237
|
+
{{ expandedId === row.id ? $t('logs.fold') : $t('logs.expand') }}
|
|
238
|
+
</button>
|
|
239
|
+
<span
|
|
240
|
+
v-else
|
|
241
|
+
class="dim"
|
|
242
|
+
>{{ $t('logs.noDetail') }}</span>
|
|
243
|
+
</td>
|
|
244
|
+
</tr>
|
|
245
|
+
<!-- 详情行内展开:紧贴事件行,点击即见(不再沉到表尾) -->
|
|
246
|
+
<tr v-if="expandedId === row.id">
|
|
247
|
+
<td
|
|
248
|
+
colspan="7"
|
|
249
|
+
class="detail-td"
|
|
237
250
|
>
|
|
238
|
-
{{
|
|
239
|
-
</
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
class="dim"
|
|
243
|
-
>—</span>
|
|
244
|
-
</td>
|
|
245
|
-
</tr>
|
|
251
|
+
<pre class="detail-box mono">{{ pretty(row.detailJson) }}</pre>
|
|
252
|
+
</td>
|
|
253
|
+
</tr>
|
|
254
|
+
</template>
|
|
246
255
|
<tr>
|
|
247
256
|
<td
|
|
248
257
|
v-if="opsLog.results.length === 0"
|
|
@@ -259,10 +268,6 @@
|
|
|
259
268
|
</tr>
|
|
260
269
|
</tbody>
|
|
261
270
|
</table>
|
|
262
|
-
<pre
|
|
263
|
-
v-if="expanded"
|
|
264
|
-
class="detail-box mono"
|
|
265
|
-
>{{ pretty(expanded.detailJson) }}</pre>
|
|
266
271
|
</section>
|
|
267
272
|
|
|
268
273
|
<!-- 人工记录弹窗 -->
|
|
@@ -284,6 +289,14 @@
|
|
|
284
289
|
:placeholder="$t('logs.mSummaryPh')"
|
|
285
290
|
/>
|
|
286
291
|
</label>
|
|
292
|
+
<label class="m-f">
|
|
293
|
+
<span>{{ $t('logs.mDetail') }}</span>
|
|
294
|
+
<textarea
|
|
295
|
+
v-model="manual.detail"
|
|
296
|
+
rows="2"
|
|
297
|
+
:placeholder="$t('logs.mDetailPh')"
|
|
298
|
+
/>
|
|
299
|
+
</label>
|
|
287
300
|
<div class="m-grid">
|
|
288
301
|
<label class="m-f">
|
|
289
302
|
<span>{{ $t('logs.fLine') }}</span>
|
|
@@ -385,9 +398,10 @@ const opsLog = useOpsLog()
|
|
|
385
398
|
const KINDS = ['write', 'manual', 'alarm', 'line', 'recipe', 'rollback', 'daq', 'system'] as const
|
|
386
399
|
|
|
387
400
|
const q = reactive({ lineId: '', productId: '', recipeId: '', actorKind: '', kind: '', text: '' })
|
|
388
|
-
|
|
401
|
+
/** 当前展开详情的行(audit_log 行 id;null = 全部收起) */
|
|
402
|
+
const expandedId = ref<number | null>(null)
|
|
389
403
|
const manualOpen = ref(false)
|
|
390
|
-
const manual = reactive({ summary: '', lineId: '', productId: '', recipeId: '' })
|
|
404
|
+
const manual = reactive({ summary: '', detail: '', lineId: '', productId: '', recipeId: '' })
|
|
391
405
|
|
|
392
406
|
const hasFilter = computed(() => !!(q.lineId || q.productId || q.recipeId || q.actorKind || q.kind || q.text.trim()))
|
|
393
407
|
const productsOfLine = computed(() => dcw.products.filter(p => !q.lineId || p.lineId === q.lineId))
|
|
@@ -404,7 +418,7 @@ function doQuery(): void {
|
|
|
404
418
|
q: q.text.trim(),
|
|
405
419
|
limit: 300,
|
|
406
420
|
})
|
|
407
|
-
|
|
421
|
+
expandedId.value = null
|
|
408
422
|
}
|
|
409
423
|
|
|
410
424
|
function resetFilters(): void {
|
|
@@ -419,6 +433,7 @@ function resetFilters(): void {
|
|
|
419
433
|
|
|
420
434
|
function openManual(): void {
|
|
421
435
|
manual.summary = ''
|
|
436
|
+
manual.detail = ''
|
|
422
437
|
manual.lineId = q.lineId
|
|
423
438
|
manual.productId = q.productId
|
|
424
439
|
manual.recipeId = q.recipeId
|
|
@@ -427,11 +442,13 @@ function openManual(): void {
|
|
|
427
442
|
|
|
428
443
|
async function submitManual(): Promise<void> {
|
|
429
444
|
try {
|
|
445
|
+
const note = manual.detail.trim()
|
|
430
446
|
await opsLog.postManual({
|
|
431
447
|
summary: manual.summary.trim(),
|
|
432
448
|
lineId: manual.lineId,
|
|
433
449
|
productId: manual.productId,
|
|
434
450
|
recipeId: manual.recipeId,
|
|
451
|
+
...(note ? { detail: { note } } : {}),
|
|
435
452
|
})
|
|
436
453
|
message.success(tt('logs.mOk'))
|
|
437
454
|
manualOpen.value = false
|
|
@@ -442,8 +459,13 @@ async function submitManual(): Promise<void> {
|
|
|
442
459
|
}
|
|
443
460
|
}
|
|
444
461
|
|
|
462
|
+
/** 该行是否带结构化详情(空对象 = 无) */
|
|
463
|
+
function hasDetail(row: OpsLogRow): boolean {
|
|
464
|
+
return !!row.detailJson && row.detailJson !== '{}'
|
|
465
|
+
}
|
|
466
|
+
|
|
445
467
|
function toggle(row: OpsLogRow): void {
|
|
446
|
-
|
|
468
|
+
expandedId.value = expandedId.value === row.id ? null : row.id
|
|
447
469
|
}
|
|
448
470
|
|
|
449
471
|
function pretty(json: string): string {
|
|
@@ -496,10 +518,8 @@ export default { name: 'OpsLogsPage' }
|
|
|
496
518
|
|
|
497
519
|
<style scoped>
|
|
498
520
|
.page { display: flex; flex-direction: column; gap: 12px; }
|
|
499
|
-
.head { display: flex;
|
|
500
|
-
.
|
|
501
|
-
.sub { margin: 4px 0 0; font-size: 12.5px; color: var(--ink-faint); }
|
|
502
|
-
.head-actions { display: flex; gap: 10px; align-items: center; }
|
|
521
|
+
.head-actions { display: flex; gap: 10px; align-items: center; padding-bottom: 4px; }
|
|
522
|
+
.sub { margin: 8px 0 0; font-size: 12.5px; color: var(--ink-faint); }
|
|
503
523
|
.live-dot {
|
|
504
524
|
width: 8px; height: 8px; border-radius: 50%;
|
|
505
525
|
background: var(--tone-success-dot);
|
|
@@ -530,12 +550,6 @@ export default { name: 'OpsLogsPage' }
|
|
|
530
550
|
border: 1px solid var(--glass-line); border-radius: 7px; outline: none;
|
|
531
551
|
}
|
|
532
552
|
.inp-sel:focus { border-color: color-mix(in srgb, var(--tone-info-dot) 55%, transparent); }
|
|
533
|
-
.mini-btn {
|
|
534
|
-
padding: 6px 11px; font-size: 12px; color: var(--ink-soft);
|
|
535
|
-
background: var(--frost-bg); border: 1px solid var(--glass-line); border-radius: 7px;
|
|
536
|
-
cursor: pointer;
|
|
537
|
-
}
|
|
538
|
-
.mini-btn:hover { border-color: color-mix(in srgb, var(--tone-info-dot) 45%, transparent); }
|
|
539
553
|
|
|
540
554
|
.table-card {
|
|
541
555
|
background: var(--surface-glass);
|
|
@@ -569,10 +583,12 @@ export default { name: 'OpsLogsPage' }
|
|
|
569
583
|
}
|
|
570
584
|
.detail-cell { white-space: nowrap; }
|
|
571
585
|
.empty { padding: 22px 0 !important; color: var(--ink-faint); text-align: center; }
|
|
586
|
+
/* 详情行内展开:紧贴事件行的整行 pre 面板 */
|
|
587
|
+
.detail-td { padding: 0 !important; }
|
|
572
588
|
.detail-box {
|
|
573
589
|
max-height: 260px; margin: 0; padding: 10px 14px; overflow: auto;
|
|
574
|
-
font-size: 11.5px; color: var(--ink-soft);
|
|
575
|
-
background: var(--
|
|
590
|
+
font-size: 11.5px; color: var(--ink-soft); white-space: pre-wrap; word-break: break-word;
|
|
591
|
+
background: var(--paper-deep); border-top: 1px solid var(--glass-line);
|
|
576
592
|
}
|
|
577
593
|
.src-badge {
|
|
578
594
|
display: inline-block; padding: 1px 8px; font-size: 10.5px;
|
|
@@ -616,8 +632,4 @@ export default { name: 'OpsLogsPage' }
|
|
|
616
632
|
.m-f textarea:focus, .m-f .inp-sel:focus { border-color: color-mix(in srgb, var(--tone-info-dot) 55%, transparent); }
|
|
617
633
|
.m-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-top: 8px; }
|
|
618
634
|
.m-actions { display: flex; gap: 8px; justify-content: flex-end; margin-top: 12px; }
|
|
619
|
-
.ghost-btn {
|
|
620
|
-
padding: 7px 14px; font-size: 12.5px; color: var(--ink-soft);
|
|
621
|
-
background: transparent; border: 1px solid var(--glass-line); border-radius: 8px; cursor: pointer;
|
|
622
|
-
}
|
|
623
635
|
</style>
|
package/i18n/locales/en.ts
CHANGED
|
@@ -748,6 +748,7 @@ export default {
|
|
|
748
748
|
'thDetail': 'Detail',
|
|
749
749
|
'expand': 'Detail',
|
|
750
750
|
'fold': 'Fold',
|
|
751
|
+
'noDetail': 'None',
|
|
751
752
|
'loading': 'Querying…',
|
|
752
753
|
'empty': 'No matching log entries',
|
|
753
754
|
'src': {
|
|
@@ -766,6 +767,8 @@ export default {
|
|
|
766
767
|
},
|
|
767
768
|
'mSummary': 'Entry content',
|
|
768
769
|
'mSummaryPh': 'e.g. Replaced heater coil of oven #3; extended soaking for current batch by 10 min',
|
|
770
|
+
'mDetail': 'Detail (optional)',
|
|
771
|
+
'mDetailPh': 'Context, handling notes, related ticket… (stored in the log detail)',
|
|
769
772
|
'mSubmit': 'Record',
|
|
770
773
|
'mOk': 'Recorded',
|
|
771
774
|
},
|
package/i18n/locales/zh-CN.ts
CHANGED
|
@@ -705,6 +705,8 @@ export default {
|
|
|
705
705
|
'kind.system': '系统',
|
|
706
706
|
'mSummary': '记录内容',
|
|
707
707
|
'mSummaryPh': '如:更换 3 号烘箱加热丝,当前批次延长保温 10 分钟',
|
|
708
|
+
'mDetail': '详情(可选)',
|
|
709
|
+
'mDetailPh': '补充说明、处置过程、关联单号等(将存入日志详情)',
|
|
708
710
|
'mSubmit': '入册',
|
|
709
711
|
'mOk': '已入册',
|
|
710
712
|
'src.user': '用户',
|
|
@@ -748,6 +750,7 @@ export default {
|
|
|
748
750
|
'thDetail': '详情',
|
|
749
751
|
'expand': '详情',
|
|
750
752
|
'fold': '收起',
|
|
753
|
+
'noDetail': '无',
|
|
751
754
|
'loading': '查询中…',
|
|
752
755
|
'empty': '无匹配日志',
|
|
753
756
|
'src': {
|
|
@@ -766,6 +769,8 @@ export default {
|
|
|
766
769
|
},
|
|
767
770
|
'mSummary': '记录内容',
|
|
768
771
|
'mSummaryPh': '如:更换 3 号烘箱加热丝,当前批次延长保温 10 分钟',
|
|
772
|
+
'mDetail': '详情(可选)',
|
|
773
|
+
'mDetailPh': '补充说明、处置过程、关联单号等(将存入日志详情)',
|
|
769
774
|
'mSubmit': '入册',
|
|
770
775
|
'mOk': '已入册',
|
|
771
776
|
},
|
package/package.json
CHANGED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# 运维日志 UI 三项修复浏览器实测:
|
|
2
|
+
# 1) 详情按钮行内展开/无详情显示"无"/人工记录可写详情
|
|
3
|
+
# 2) 全局按钮统一(pill accent,非白底默认)
|
|
4
|
+
# 3) 页头 aw-page-head 规范(标题30px + 描述间距)
|
|
5
|
+
# 附带:daq/dcw 页按钮抽查截图。产物 → .e2e-shots/
|
|
6
|
+
import json
|
|
7
|
+
import sys
|
|
8
|
+
from playwright.sync_api import sync_playwright
|
|
9
|
+
|
|
10
|
+
BASE = 'http://127.0.0.1:3000'
|
|
11
|
+
SHOT = '.e2e-shots'
|
|
12
|
+
passed = 0
|
|
13
|
+
failed = 0
|
|
14
|
+
|
|
15
|
+
def ok(cond, name, extra=''):
|
|
16
|
+
global passed, failed
|
|
17
|
+
if cond:
|
|
18
|
+
passed += 1
|
|
19
|
+
print(f' OK {name}')
|
|
20
|
+
else:
|
|
21
|
+
failed += 1
|
|
22
|
+
print(f' FAIL {name} {extra}')
|
|
23
|
+
|
|
24
|
+
with sync_playwright() as p:
|
|
25
|
+
browser = p.chromium.launch(headless=True)
|
|
26
|
+
page = browser.new_page(viewport={'width': 1440, 'height': 900})
|
|
27
|
+
page.set_default_timeout(30000)
|
|
28
|
+
|
|
29
|
+
# ---- 1. 登录(/logs 不强制跳登录,先去 /workshop 触发登录墙) ----
|
|
30
|
+
page.goto(BASE + '/workshop', wait_until='domcontentloaded', timeout=90000)
|
|
31
|
+
try:
|
|
32
|
+
page.wait_for_selector('input[placeholder="邮箱 / 用户名"]', timeout=20000)
|
|
33
|
+
except Exception:
|
|
34
|
+
page.goto(BASE + '/workshop', wait_until='domcontentloaded', timeout=60000)
|
|
35
|
+
page.wait_for_selector('input[placeholder="邮箱 / 用户名"]', timeout=60000)
|
|
36
|
+
for attempt in range(3):
|
|
37
|
+
page.wait_for_timeout(2500 + attempt * 2000)
|
|
38
|
+
page.fill('input[placeholder="邮箱 / 用户名"]', 'admin')
|
|
39
|
+
page.fill('.ant-input-password input', 'admin123')
|
|
40
|
+
try:
|
|
41
|
+
with page.expect_response(lambda r: '/api/users/login' in r.url, timeout=15000) as ri:
|
|
42
|
+
page.locator('.ant-input-password input').first.press('Enter')
|
|
43
|
+
status = ri.value.status
|
|
44
|
+
except Exception:
|
|
45
|
+
status = None
|
|
46
|
+
inp = page.locator('input[placeholder="邮箱 / 用户名"]')
|
|
47
|
+
if status == 200 and (inp.count() == 0 or not inp.first.is_visible()):
|
|
48
|
+
break
|
|
49
|
+
ok(status == 200, f'登录(admin) status={status}')
|
|
50
|
+
page.wait_for_timeout(1500)
|
|
51
|
+
|
|
52
|
+
# ---- 2. 页头规范 ----
|
|
53
|
+
page.goto(BASE + '/logs', wait_until='domcontentloaded', timeout=60000)
|
|
54
|
+
page.wait_for_timeout(2500)
|
|
55
|
+
head = page.locator('.aw-page-head')
|
|
56
|
+
ok(head.count() > 0, '页头使用 aw-page-head 规范组件')
|
|
57
|
+
h1 = page.locator('.aw-page-head h1').first
|
|
58
|
+
fs = h1.evaluate('el => getComputedStyle(el).fontSize')
|
|
59
|
+
ok(fs == '30px', f'标题字号与其他页统一 30px(实际 {fs})')
|
|
60
|
+
sub = page.locator('.aw-page-head .sub').first
|
|
61
|
+
mt = sub.evaluate('el => getComputedStyle(el).marginTop')
|
|
62
|
+
ok(float(mt.replace('px', '')) >= 8, f'标题-描述间距放大 marginTop={mt}')
|
|
63
|
+
page.screenshot(path=f'{SHOT}/logs-head.png', clip={'x': 0, 'y': 0, 'width': 1440, 'height': 240})
|
|
64
|
+
|
|
65
|
+
# ---- 3. 按钮统一(非白底默认;accent 药丸) ----
|
|
66
|
+
def bg_of(sel):
|
|
67
|
+
return page.locator(sel).first.evaluate('el => getComputedStyle(el).backgroundColor')
|
|
68
|
+
man_bg = bg_of('.head-actions .pill-btn')
|
|
69
|
+
ok(man_bg not in ('rgb(255, 255, 255)', 'rgba(0, 0, 0, 0)'), f'人工记录按钮有主题底色({man_bg})')
|
|
70
|
+
man_radius = page.locator('.head-actions .pill-btn').first.evaluate('el => getComputedStyle(el).borderRadius')
|
|
71
|
+
ok(man_radius == '9999px', f'人工记录按钮药丸形(radius={man_radius})')
|
|
72
|
+
ok(True, '查询/重置按钮走全局 pill/mini 词汇(截图目视)')
|
|
73
|
+
page.screenshot(path=f'{SHOT}/logs-buttons.png', clip={'x': 0, 'y': 100, 'width': 1440, 'height': 260})
|
|
74
|
+
|
|
75
|
+
# ---- 4. 人工记录:摘要 + 详情(可选) 入册 ----
|
|
76
|
+
stamp = f'UI验证-{json.dumps("", ensure_ascii=False)}{page.evaluate("Date.now()")}'
|
|
77
|
+
summary = f'浏览器实测人工记录 {page.evaluate("new Date().toISOString().slice(11,19)")}'
|
|
78
|
+
detail = f'处置详情备注:round-{page.evaluate("Date.now()")}'
|
|
79
|
+
page.locator('.head-actions .pill-btn').click()
|
|
80
|
+
page.wait_for_selector('.modal', timeout=10000)
|
|
81
|
+
page.locator('.modal textarea').nth(0).fill(summary)
|
|
82
|
+
page.locator('.modal textarea').nth(1).fill(detail)
|
|
83
|
+
page.screenshot(path=f'{SHOT}/logs-manual-modal.png')
|
|
84
|
+
with page.expect_response(lambda r: '/api/workshop/ops-logs' in r.url, timeout=15000) as ri:
|
|
85
|
+
page.locator('.modal .pill-btn').click()
|
|
86
|
+
ok(ri.value.status in (200, 201), f'人工记录入册 status={ri.value.status}')
|
|
87
|
+
page.wait_for_timeout(1200)
|
|
88
|
+
|
|
89
|
+
# ---- 5. 详情行:新记录带详情按钮,点击行内展开 ----
|
|
90
|
+
row = page.locator('tr', has_text=summary).first
|
|
91
|
+
row.wait_for(state='visible', timeout=15000)
|
|
92
|
+
btn = row.locator('.detail-cell .mini-btn')
|
|
93
|
+
ok(btn.count() == 1, '新人工记录带"详情"按钮')
|
|
94
|
+
btn.click()
|
|
95
|
+
page.wait_for_timeout(400)
|
|
96
|
+
detail_box = row.locator('+ tr .detail-box')
|
|
97
|
+
ok(detail_box.count() == 1, '点击详情 → 行内紧贴展开详情面板')
|
|
98
|
+
txt = detail_box.first.inner_text() if detail_box.count() else ''
|
|
99
|
+
ok(detail in txt, '详情内容含人工填写的备注(note)')
|
|
100
|
+
row.scroll_into_view_if_needed()
|
|
101
|
+
page.wait_for_timeout(300)
|
|
102
|
+
page.screenshot(path=f'{SHOT}/logs-detail-expanded.png')
|
|
103
|
+
btn.click()
|
|
104
|
+
page.wait_for_timeout(300)
|
|
105
|
+
ok(detail_box.count() == 0 or not detail_box.first.is_visible(), '再点收起 → 详情面板关闭')
|
|
106
|
+
|
|
107
|
+
# ---- 6. 无详情行显示"无" ----
|
|
108
|
+
none_cells = page.locator('.detail-cell', has_text='无')
|
|
109
|
+
ok(none_cells.count() > 0, f'无详情行显示"无"(共 {none_cells.count()} 行)')
|
|
110
|
+
|
|
111
|
+
# ---- 7. daq/dcw 页按钮抽查(全局词汇无回归) ----
|
|
112
|
+
page.goto(BASE + '/dcw', wait_until='domcontentloaded', timeout=60000)
|
|
113
|
+
page.wait_for_timeout(2500)
|
|
114
|
+
lc_btn = page.locator('.pill-btn').first
|
|
115
|
+
if lc_btn.count():
|
|
116
|
+
b = lc_btn.evaluate('el => getComputedStyle(el).backgroundColor')
|
|
117
|
+
ok(b not in ('rgb(255, 255, 255)', 'rgba(0, 0, 0, 0)'), f'dcw 页 pill-btn 有主题底色({b})')
|
|
118
|
+
page.screenshot(path=f'{SHOT}/dcw-buttons.png', clip={'x': 0, 'y': 0, 'width': 1440, 'height': 420})
|
|
119
|
+
else:
|
|
120
|
+
ok(True, 'dcw 页无 pill-btn 可查(空态)')
|
|
121
|
+
|
|
122
|
+
browser.close()
|
|
123
|
+
|
|
124
|
+
print(f'\n== logs UI e2e: {passed} passed, {failed} failed ==')
|
|
125
|
+
sys.exit(1 if failed else 0)
|