Files
Nexus/web/app/assets/ScriptRunsPage-3pyRb1xb.js
T
Nexus Agent 0b2d7ac50d feat(scripts): 异步执行、全局进度看板与完成提示音设置
脚本 exec 立即返回 running 并在后台跑批次;/ws/alerts 推送 script_progress/complete;
新增 ScriptRunsPage、Telegram 汇总通知与可配置的浏览器完成提示音。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 07:07:02 +08:00

4 lines
13 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import{a as e}from"./VDataTable-BmjSKPho.js";import{a as t,o as n}from"./VSelectionControl-BQRUjX5P.js";import{n as r,t as i}from"./VRow-C1b4Uu8Q.js";import{$n as a,At as o,Er as s,F as c,Fr as l,Ft as ee,Gr as u,It as te,Kt as ne,Lt as re,Mt as ie,N as d,Nt as f,Or as p,P as ae,Pt as oe,Q as m,Qn as se,R as h,Ur as g,Ut as _,Vr as v,Z as ce,_r as le,ar as y,cr as b,ir as x,jt as ue,lr as de,lt as fe,nr as S,pt as C,qt as pe,rr as w,sr as T,tr as E,vr as me,xr as D,yr as O,z as k}from"./index-DbSyD28P.js";import{n as he}from"./dataTable-esDY4cOk.js";import{t as ge}from"./VSelect-glh4Zdhv.js";import{t as _e}from"./VDataTableServer-BisDXMgG.js";import{t as A}from"./apiError-D4MvEqzY.js";import{t as ve}from"./useServerList-Bj74a8DL.js";import{t as ye}from"./paginatedFetch-BezwS41g.js";import{t as j}from"./_plugin-vue_export-helper-BDNMzG2s.js";import{t as be}from"./execDetailGrouping-BdJXnrw-.js";import{i as xe,n as Se,r as Ce,t as we}from"./VExpansionPanels-BwyWLBgd.js";var Te={class:`d-flex align-center flex-wrap ga-2 mb-4`},Ee={class:`text-h6`},De={class:`text-caption text-medium-emphasis mb-2`},Oe={class:`text-body-2 mb-3 pa-3 rounded bg-surface-variant overflow-auto`,style:{"max-height":`100px`}},ke={key:0,class:`d-flex flex-wrap ga-2 mb-3`},Ae={class:`text-body-2`},je={class:`d-flex align-center flex-wrap ga-2 mb-3`},Me=[`onClick`],Ne={class:`font-weight-medium`},Pe={key:0,class:`text-caption text-medium-emphasis`},Fe=[`title`],Ie={key:0},Le={class:`d-flex align-center ga-2 flex-grow-1 min-width-0`},Re=[`title`],ze={key:0,class:`text-medium-emphasis`},Be={class:`d-flex align-center flex-wrap ga-2 mb-4`},M=j(de({__name:`ScriptRunsPage`,setup(de){let j=ne(),M=pe(),N=t(),{servers:Ve,loadServers:He}=ve(),P=E(()=>{let e=j.params.id;if(!e)return null;let t=Number(e);return Number.isFinite(t)?t:null}),F=l(!1),I=l([]),L=l(0),R=l(1),z=l(20),B=l(null),Ue=[{title:`执行中`,value:`running`},{title:`已完成`,value:`completed`},{title:`部分成功`,value:`partial`},{title:`失败`,value:`failed`},{title:`已取消`,value:`cancelled`}],We=he([{title:`ID`,key:`id`,width:72},{title:`脚本/命令`,key:`label`},{title:`状态`,key:`status`,width:100},{title:`进度`,key:`progress`,width:88},{title:`台数`,key:`server_count`,width:72},{title:`操作人`,key:`operator`,width:88},{title:`开始时间`,key:`started_at`,width:168},{title:`操作`,key:`actions`,width:88}]),V=l(null),H=l(!1),U=l(`all`),W=l(`list`),Ge=l(null),Ke=l([]),G=null,qe=E(()=>new Map(Ve.value.map(e=>[e.id,e])));function Je(e){return e||`—`}function Ye(e){if(!e)return 0;try{let t=JSON.parse(e);return Array.isArray(t)?t.length:0}catch{return 0}}function Xe(e){if(e.label)return e.label;let t=(e.command||``).replace(/^\[long_task\] /,``);return t.length>48?t.slice(0,48)+`…`:t||`执行 #${e.id}`}function Ze(e){let t=(e.stderr||``).trim(),n=(e.stdout||``).trim(),r=(e.message||``).trim(),i=t||n||r||`—`;return{summary:(t||r||n||``).slice(0,200)||`—`,full:i}}let K=E(()=>{if(!V.value?.results)return[];let e=qe.value;return Object.entries(V.value.results).filter(([e])=>e!==`_meta`&&/^\d+$/.test(e)).map(([t,n])=>{let r=Number(t),i=e.get(r),a=String(n.status||``),o=n.phase?String(n.phase):void 0,s=n.exit_code??null,c=Ze(n);return{serverId:t,serverIdNum:r,serverName:i?.name||`服务器 #${t}`,serverDomain:i?.domain||``,status:a,phase:o,exitCode:s,summary:c.summary,fullOutput:c.full,failed:ee(a,o,s),pending:te(o)}})}),Qe=E(()=>U.value===`failed`?K.value.filter(e=>e.failed):U.value===`success`?K.value.filter(e=>!e.failed&&!e.pending):K.value),q=E(()=>re(V.value?.results,V.value?.server_ids)),J=E(()=>K.value.filter(e=>e.failed).map(e=>e.serverName)),Y=E(()=>be(K.value.filter(e=>e.failed)));async function X(){F.value=!0;try{let e=await ye(async(e,t)=>{let n={limit:e,offset:t};B.value&&(n.status=B.value);let r=await _.get(`/scripts/executions`,n);return{items:r.items||[],total:r.total||0}},R.value,z.value);I.value=e.items,L.value=e.total}catch(e){I.value=[],L.value=0,N(A(e,`加载失败`),`error`)}finally{F.value=!1}}function $e(e){R.value=e,X()}function et(e){z.value=e,R.value=1,X()}function tt(e){M.push(`/script-runs/${e}`)}async function Z(e=!1){if(P.value){H.value=!0;try{let t=e?`?fetch_logs=true`:``;V.value=await _.get(`/scripts/executions/${P.value}${t}`),V.value?.status===`running`?nt():Q()}catch(e){N(A(e,`加载详情失败`),`error`)}finally{H.value=!1}}}function nt(){G||=setInterval(()=>{Z(!1)},3e3)}function Q(){G&&=(clearInterval(G),null)}function rt(e){Ge.value=Ge.value===e?null:e}function it(e){M.push({path:`/terminal`,query:{server:String(e)}})}async function at(){if(P.value)try{await _.post(`/scripts/executions/${P.value}/stop`),await Z(!1),N(`已发送停止指令`)}catch(e){N(A(e,`停止失败`),`error`)}}async function ot(){if(P.value)try{let e=await _.post(`/scripts/executions/${P.value}/retry`,{}),t=Ye(e.server_ids);o(e.id,`${Xe(e)}(重试)`,t,!!e.long_task,e.progress,e.status),N(`重试已提交 #${e.id}`),M.push(`/script-runs/${e.id}`)}catch(e){N(A(e,`重试失败`),`error`)}}function st(e){let t=e.serverDomain?` (${e.serverDomain})`:``,n=e.exitCode==null?``:` · exit ${e.exitCode}`,r=e.summary.split(`
`)[0]?.trim()||e.summary,i=r&&r!==`—`?` · ${r.slice(0,120)}`:``;return`- ${e.serverName}${t}${n}${i}`}async function $(e,t){try{await navigator.clipboard.writeText(e),N(t)}catch{N(`复制失败`,`error`)}}async function ct(){let e=K.value.filter(e=>e.failed);e.length&&await $([`执行 #${P.value} · 失败 ${e.length} 台`,...e.map(st)].join(`
`),`已复制失败清单`)}async function lt(){let e=Y.value;if(!e.length)return;let t=[`执行 #${P.value} · 失败分组 ${e.length} 类`];for(let n of e){t.push(`\n[${n.count} 台] ${n.errorKey}`);for(let e of n.rows.slice(0,20))t.push(` - ${e.serverName}`);n.rows.length>20&&t.push(` …及其他 ${n.rows.length-20} 台`)}await $(t.join(`
`),`已复制分组摘要`)}return s(B,()=>{R.value=1,X()}),s(P,e=>{Q(),V.value=null,e?(U.value=`all`,W.value=`list`,Z(!1)):X()},{immediate:!0}),le(()=>{He()}),me(()=>{Q()}),(t,o)=>(O(),w(n,{fluid:``,class:`pa-4`},{default:p(()=>[P.value?(O(),y(a,{key:0},[S(`div`,Te,[b(m,{variant:`text`,"prepend-icon":`mdi-arrow-left`,onClick:o[0]||=e=>v(M).push(`/script-runs`)},{default:p(()=>[...o[7]||=[T(` 返回列表 `,-1)]]),_:1}),S(`h1`,Ee,`执行详情 #`+u(P.value),1),V.value?(O(),w(c,{key:0,color:v(f)(V.value.status),size:`small`,variant:`tonal`,label:``},{default:p(()=>[T(u(v(oe)(V.value.status)),1)]),_:1},8,[`color`])):x(``,!0),b(d),V.value?.long_task?(O(),w(m,{key:1,size:`small`,variant:`tonal`,loading:H.value,onClick:o[1]||=e=>Z(!0)},{default:p(()=>[...o[8]||=[T(` 拉取日志 `,-1)]]),_:1},8,[`loading`])):x(``,!0),V.value&&(V.value.status===`running`||V.value.status===`partial`)?(O(),w(m,{key:2,size:`small`,variant:`text`,color:`error`,onClick:at},{default:p(()=>[...o[9]||=[T(` 停止 `,-1)]]),_:1})):x(``,!0),V.value&&(V.value.status===`failed`||V.value.status===`partial`)?(O(),w(m,{key:3,size:`small`,variant:`tonal`,color:`primary`,onClick:ot},{default:p(()=>[...o[10]||=[T(` 重试失败项 `,-1)]]),_:1})):x(``,!0)]),H.value&&!V.value?(O(),w(h,{key:0,elevation:`0`,border:``,rounded:`lg`,class:`pa-8 text-center`},{default:p(()=>[b(fe,{indeterminate:``,color:`primary`})]),_:1})):V.value?(O(),w(h,{key:1,elevation:`0`,border:``,rounded:`lg`},{default:p(()=>[b(k,null,{default:p(()=>[S(`div`,De,u(Je(V.value.progress))+` · 操作人 `+u(V.value.operator||`—`),1),S(`pre`,Oe,u(V.value.command),1),q.value.total?(O(),y(`div`,ke,[b(c,{size:`small`,variant:`tonal`,label:``},{default:p(()=>[T(`共 `+u(q.value.total)+` 台`,1)]),_:1}),q.value.success?(O(),w(c,{key:0,color:`success`,size:`small`,variant:`tonal`,label:``},{default:p(()=>[T(` 成功 `+u(q.value.success),1)]),_:1})):x(``,!0),q.value.failed?(O(),w(c,{key:1,color:`error`,size:`small`,variant:`tonal`,label:``},{default:p(()=>[T(` 失败 `+u(q.value.failed),1)]),_:1})):x(``,!0),q.value.pending?(O(),w(c,{key:2,color:`info`,size:`small`,variant:`tonal`,label:``},{default:p(()=>[T(` 进行中 `+u(q.value.pending),1)]),_:1})):x(``,!0)])):x(``,!0),J.value.length?(O(),w(ce,{key:1,type:`error`,variant:`tonal`,density:`compact`,class:`mb-3`,title:`失败 ${J.value.length} 台`},{default:p(()=>[S(`div`,Ae,u(J.value.join(`、`)),1)]),_:1},8,[`title`])):x(``,!0),S(`div`,je,[o[19]||=S(`span`,{class:`text-caption text-medium-emphasis`},`筛选`,-1),b(C,{modelValue:U.value,"onUpdate:modelValue":o[2]||=e=>U.value=e,mandatory:``,density:`compact`,color:`primary`,variant:`outlined`,divided:``},{default:p(()=>[b(m,{value:`all`,size:`small`},{default:p(()=>[...o[11]||=[T(`全部`,-1)]]),_:1}),b(m,{value:`failed`,size:`small`},{default:p(()=>[...o[12]||=[T(`仅失败`,-1)]]),_:1}),b(m,{value:`success`,size:`small`},{default:p(()=>[...o[13]||=[T(`仅成功`,-1)]]),_:1})]),_:1},8,[`modelValue`]),q.value.failed>0?(O(),y(a,{key:0},[b(ae,{vertical:``,class:`mx-1`}),o[18]||=S(`span`,{class:`text-caption text-medium-emphasis`},`视图`,-1),b(C,{modelValue:W.value,"onUpdate:modelValue":o[3]||=e=>W.value=e,mandatory:``,density:`compact`,color:`primary`,variant:`outlined`,divided:``},{default:p(()=>[b(m,{value:`list`,size:`small`},{default:p(()=>[...o[14]||=[T(`列表`,-1)]]),_:1}),b(m,{value:`group`,size:`small`},{default:p(()=>[...o[15]||=[T(`按错误分组`,-1)]]),_:1})]),_:1},8,[`modelValue`]),b(m,{size:`small`,variant:`tonal`,"prepend-icon":`mdi-content-copy`,onClick:ct},{default:p(()=>[...o[16]||=[T(` 复制失败清单 `,-1)]]),_:1}),W.value===`group`?(O(),w(m,{key:0,size:`small`,variant:`tonal`,"prepend-icon":`mdi-content-copy`,onClick:lt},{default:p(()=>[...o[17]||=[T(` 复制分组摘要 `,-1)]]),_:1})):x(``,!0)],64)):x(``,!0)]),W.value===`list`?(O(),w(e,{key:2,density:`compact`},{default:p(()=>[o[21]||=S(`thead`,null,[S(`tr`,null,[S(`th`,null,`服务器`),S(`th`,null,`状态`),S(`th`,{style:{width:`72px`}},`退出码`),S(`th`,null,`错误摘要`),S(`th`,{style:{width:`120px`},class:`text-end`},`操作`)])],-1),S(`tbody`,null,[(O(!0),y(a,null,D(Qe.value,e=>(O(),y(`tr`,{key:e.serverId,class:g([{"exec-detail-row--failed":e.failed},`cursor-pointer`]),onClick:t=>rt(e.serverId)},[S(`td`,null,[S(`div`,Ne,u(e.serverName),1),e.serverDomain?(O(),y(`div`,Pe,u(e.serverDomain),1)):x(``,!0)]),S(`td`,null,[b(c,{color:v(ue)(e.status,e.phase,e.exitCode),size:`x-small`,variant:`tonal`,label:``},{default:p(()=>[T(u(v(ie)(e.status,e.phase)),1)]),_:2},1032,[`color`])]),S(`td`,{class:g({"text-error font-weight-medium":e.failed})},u(e.exitCode??`—`),3),S(`td`,{class:`text-truncate`,style:{"max-width":`280px`},title:e.summary},u(e.summary),9,Fe),S(`td`,{class:`text-end`,onClick:o[4]||=se(()=>{},[`stop`])},[b(m,{size:`x-small`,variant:`text`,icon:`mdi-console`,title:`打开终端`,onClick:t=>it(e.serverIdNum)},null,8,[`onClick`])])],10,Me))),128)),Qe.value.length===0?(O(),y(`tr`,Ie,[...o[20]||=[S(`td`,{colspan:`5`,class:`text-center text-medium-emphasis py-4`},`当前筛选无记录`,-1)]])):x(``,!0)])]),_:1})):Y.value.length?(O(),w(we,{key:3,modelValue:Ke.value,"onUpdate:modelValue":o[5]||=e=>Ke.value=e,multiple:``,variant:`accordion`},{default:p(()=>[(O(!0),y(a,null,D(Y.value,(e,t)=>(O(),w(Se,{key:`${t}-${e.errorKey}`},{default:p(()=>[b(Ce,null,{default:p(()=>[S(`div`,Le,[S(`span`,{class:`text-body-2 text-truncate`,title:e.errorKey},u(e.errorKey),9,Re),b(c,{color:`error`,size:`x-small`,variant:`tonal`,label:``},{default:p(()=>[T(u(e.count)+` 台`,1)]),_:2},1024)])]),_:2},1024),b(xe,null,{default:p(()=>[(O(!0),y(a,null,D(e.rows,e=>(O(),y(`div`,{key:e.serverId,class:`text-body-2 py-1`},[T(u(e.serverName)+` `,1),e.serverDomain?(O(),y(`span`,ze,``+u(e.serverDomain)+``,1)):x(``,!0)]))),128))]),_:2},1024)]),_:2},1024))),128))]),_:1},8,[`modelValue`])):x(``,!0)]),_:1})]),_:1})):x(``,!0)],64)):(O(),y(a,{key:1},[S(`div`,Be,[o[23]||=S(`h1`,{class:`text-h6`},`脚本执行看板`,-1),b(d),b(m,{variant:`tonal`,"prepend-icon":`mdi-refresh`,loading:F.value,onClick:X},{default:p(()=>[...o[22]||=[T(` 刷新 `,-1)]]),_:1},8,[`loading`])]),b(h,{elevation:`0`,border:``,rounded:`lg`},{default:p(()=>[b(k,null,{default:p(()=>[b(i,{dense:``,class:`mb-2`},{default:p(()=>[b(r,{cols:`12`,sm:`4`},{default:p(()=>[b(ge,{modelValue:B.value,"onUpdate:modelValue":o[6]||=e=>B.value=e,items:Ue,label:`状态筛选`,variant:`outlined`,density:`compact`,"hide-details":``,clearable:``},null,8,[`modelValue`])]),_:1})]),_:1}),b(_e,{headers:v(We),items:I.value,"items-length":L.value,loading:F.value,"items-per-page":z.value,page:R.value,"onUpdate:page":$e,"onUpdate:itemsPerPage":et},{"item.label":p(({item:e})=>[T(u(Xe(e)),1)]),"item.status":p(({item:e})=>[b(c,{color:v(f)(e.status),size:`x-small`,variant:`tonal`,label:``},{default:p(()=>[T(u(v(oe)(e.status)),1)]),_:2},1032,[`color`])]),"item.progress":p(({item:e})=>[T(u(Je(e.progress)),1)]),"item.server_count":p(({item:e})=>[T(u(Ye(e.server_ids)),1)]),"item.actions":p(({item:e})=>[b(m,{size:`x-small`,variant:`text`,color:`primary`,onClick:t=>tt(e.id)},{default:p(()=>[...o[24]||=[T(` 详情 `,-1)]]),_:1},8,[`onClick`])]),"no-data":p(()=>[...o[25]||=[S(`div`,{class:`text-center text-medium-emphasis py-6`},`暂无执行记录`,-1)]]),_:1},8,[`headers`,`items`,`items-length`,`loading`,`items-per-page`,`page`])]),_:1})]),_:1})],64))]),_:1}))}}),[[`__scopeId`,`data-v-256c6913`]]);export{M as default};