@@ -1,5 +1,5 @@
( ( ) => {
( ( ) => {
const state = { token : '' , queue : '' , base : location . origin , timer : null , connected : false , loading : false , selectedTask : null , actionMode : '' , selectedSchedule : null , selectedWorkflow : null , workflowActionMode : '' , selectedSubscription : null , subscriptionActionMode : '' , selectedDelivery : null } ;
const state = { token : '' , queue : '' , base : location . origin , timer : null , connected : false , loading : false , selectedTask : null , actionMode : '' , selectedSchedule : null , selectedWorkflow : null , workflowActionMode : '' , workflowRecoveryMode : '' , workflowRecoveryContext : null , selectedSubscription : null , subscriptionActionMode : '' , selectedDelivery : null } ;
const $ = ( id ) => document . getElementById ( id ) ;
const $ = ( id ) => document . getElementById ( id ) ;
const text = ( id , value ) => { const el = $ ( id ) ; if ( el ) el . textContent = value ; } ;
const text = ( id , value ) => { const el = $ ( id ) ; if ( el ) el . textContent = value ; } ;
const pageNames = { overview : '总览' , tasks : '任务执行' , schedules : '定时计划' , workflows : '流程实例' , subscriptions : '结果订阅' } ;
const pageNames = { overview : '总览' , tasks : '任务执行' , schedules : '定时计划' , workflows : '流程实例' , subscriptions : '结果订阅' } ;
@@ -217,7 +217,8 @@
}
}
function showWorkflowDrawer ( ) { $ ( 'workflow-drawer-backdrop' ) . hidden = false ; document . body . classList . add ( 'drawer-open' ) ; }
function showWorkflowDrawer ( ) { $ ( 'workflow-drawer-backdrop' ) . hidden = false ; document . body . classList . add ( 'drawer-open' ) ; }
function closeWorkflowAction ( ) { state . workflowActionMode = '' ; $ ( 'workflow-action-form' ) . hidden = true ; $ ( 'workflow-action-error' ) . textContent = '' ; }
function closeWorkflowAction ( ) { state . workflowActionMode = '' ; $ ( 'workflow-action-form' ) . hidden = true ; $ ( 'workflow-action-error' ) . textContent = '' ; }
function closeWorkflowDraw er ( ) { $ ( 'workflow-drawer-backdrop' ) . hidden = true ; document . body . classList . remove ( 'drawer-open' ) ; closeWorkflowAction ( ) ; }
function closeWorkflowRecov ery ( ) { state . workflowRecoveryMode = '' ; state . workflowRecoveryContext = null ; $ ( 'workflow-recovery-form' ) . hidden = true ; $ ( 'workflow-recovery-error' ) . textContent = '' ; }
function closeWorkflowDrawer ( ) { $ ( 'workflow-drawer-backdrop' ) . hidden = true ; document . body . classList . remove ( 'drawer-open' ) ; closeWorkflowAction ( ) ; closeWorkflowRecovery ( ) ; }
function showSubscriptionDrawer ( ) { $ ( 'subscription-drawer-backdrop' ) . hidden = false ; document . body . classList . add ( 'drawer-open' ) ; }
function showSubscriptionDrawer ( ) { $ ( 'subscription-drawer-backdrop' ) . hidden = false ; document . body . classList . add ( 'drawer-open' ) ; }
function closeSubscriptionAction ( ) { state . subscriptionActionMode = '' ; $ ( 'subscription-action-form' ) . hidden = true ; $ ( 'subscription-action-error' ) . textContent = '' ; }
function closeSubscriptionAction ( ) { state . subscriptionActionMode = '' ; $ ( 'subscription-action-form' ) . hidden = true ; $ ( 'subscription-action-error' ) . textContent = '' ; }
function closeSubscriptionRetry ( ) { state . selectedDelivery = null ; $ ( 'subscription-retry-form' ) . hidden = true ; $ ( 'subscription-retry-error' ) . textContent = '' ; }
function closeSubscriptionRetry ( ) { state . selectedDelivery = null ; $ ( 'subscription-retry-form' ) . hidden = true ; $ ( 'subscription-retry-error' ) . textContent = '' ; }
@@ -236,6 +237,84 @@
catch ( error ) { $ ( 'workflow-action-error' ) . textContent = error . message || '流程控制失败' ; }
catch ( error ) { $ ( 'workflow-action-error' ) . textContent = error . message || '流程控制失败' ; }
finally { button . disabled = false ; }
finally { button . disabled = false ; }
}
}
const workflowRecoveryLabels = {
'task-review' : [ '核查任务结果' , '先核实任务执行已经结清,再记录这次执行的最终结论。' ] ,
'step-resolve' : [ '确认步骤结论' , '确认当前步骤的逻辑结果;步骤结果不会自动解除流程核查。' ] ,
'continue' : [ '解除流程核查' , '确认全部核查事项已处理,再允许流程继续推进。' ] ,
'step-retry' : [ '安全重试步骤' , '仅在确认旧执行不会继续产生外部效果时,创建新的步骤激活。' ] ,
'poll-continue' : [ '接续 PENDING 查询' , '确认权威结果仍为 PENDING, 沿原查询间隔继续, 不重复查询同一轮。' ] ,
'wait-rearm' : [ '重建等待代次' , '确认当前等待事实后重建 SIGNAL 或 TIMER 等待代次,原截止时间不延长。' ] ,
} ;
function recoveryStepState ( step ) { return String ( step ? . state || '' ) . toUpperCase ( ) ; }
function renderWorkflowRecovery ( workflow , payload , error ) {
const block = $ ( 'workflow-recovery-block' ) ; const actions = $ ( 'workflow-recovery-actions' ) ;
block . hidden = workflow ? . state !== 'NEEDS_REVIEW' ; actions . innerHTML = '' ;
if ( block . hidden ) return ;
if ( error ) { text ( 'workflow-recovery-hint' , '步骤读取失败' ) ; actions . innerHTML = '<div class="detail-empty">当前无法读取步骤,暂不提供恢复按钮。</div>' ; return ; }
text ( 'workflow-recovery-hint' , '需要 workflows:recover · 所有操作进入审计' ) ;
const items = Array . isArray ( payload ? . steps ) ? payload . steps : [ ] ;
const buttons = [ { mode : 'continue' , label : '解除流程核查' , hint : '全部事项已处理' } ] ;
items . forEach ( step => {
const kind = String ( step . kind || '' ) . toUpperCase ( ) ; const status = recoveryStepState ( step ) ; const context = { step _key : step . key || step . id || '' , activation _id : step . activation _id || '' , expected _step _revision : step . revision || '' , task _id : step . task _id || '' } ;
if ( status !== 'NEEDS_REVIEW' ) return ;
if ( context . task _id ) buttons . push ( { mode : 'task-review' , label : ` 任务核查 · ${ step . key || step . id } ` , hint : '记录任务最终结论' , context } ) ;
buttons . push ( { mode : 'step-resolve' , label : ` 步骤结论 · ${ step . key || step . id } ` , hint : ` ${ kind || 'STEP' } · 记录逻辑结果 ` , context } ) ;
if ( kind === 'TASK' || kind === 'POLL' ) buttons . push ( { mode : 'step-retry' , label : ` 重试步骤 · ${ step . key || step . id } ` , hint : '确认安全后创建新激活' , context } ) ;
if ( kind === 'POLL' ) buttons . push ( { mode : 'poll-continue' , label : ` 接续查询 · ${ step . key || step . id } ` , hint : '确认 PENDING 后接续' , context } ) ;
if ( kind === 'SIGNAL' || kind === 'TIMER' ) buttons . push ( { mode : 'wait-rearm' , label : ` 重建等待 · ${ step . key || step . id } ` , hint : '确认等待事实后换代' , context } ) ;
} ) ;
actions . innerHTML = buttons . map ( button => ` <button class="recovery-action-button" type="button" data-recovery-action=" ${ escapeHTML ( button . mode ) } " data-recovery-context=" ${ escapeHTML ( JSON . stringify ( button . context || { } ))}"><b> ${ escapeHTML ( button . label ) } </b><small> ${ escapeHTML ( button . hint ) } </small></button> ` ) . join ( '' ) ;
}
async function openWorkflowRecovery ( mode , context = { } ) {
if ( ! state . selectedWorkflow || state . selectedWorkflow . state !== 'NEEDS_REVIEW' || ! workflowRecoveryLabels [ mode ] ) return ;
state . workflowRecoveryMode = mode ; state . workflowRecoveryContext = context ;
const needsStep = [ 'step-resolve' , 'step-retry' , 'poll-continue' , 'wait-rearm' ] . includes ( mode ) ; const needsTask = mode === 'task-review' ; const needsOutcome = [ 'task-review' , 'step-resolve' ] . includes ( mode ) ;
$ ( 'workflow-recovery-form' ) . hidden = false ; $ ( 'workflow-recovery-error' ) . textContent = '' ; $ ( 'workflow-recovery-reason' ) . value = '' ; $ ( 'workflow-recovery-evidence' ) . value = ` evidence:console- ${ Date . now ( ) } ` ; $ ( 'workflow-recovery-confirm' ) . checked = false ;
$ ( 'workflow-recovery-step-fields' ) . hidden = ! needsStep ; $ ( 'workflow-recovery-task-fields' ) . hidden = ! needsTask ; $ ( 'workflow-recovery-outcome-fields' ) . hidden = ! needsOutcome ;
$ ( 'workflow-recovery-result' ) . value = '{}' ; $ ( 'workflow-recovery-step-key' ) . value = context . step _key || '' ; $ ( 'workflow-recovery-activation-id' ) . value = context . activation _id || '' ; $ ( 'workflow-recovery-step-revision' ) . value = context . expected _step _revision || '' ;
$ ( 'workflow-recovery-task-id' ) . value = context . task _id || '' ; $ ( 'workflow-recovery-task-revision' ) . value = '' ;
text ( 'workflow-recovery-title' , workflowRecoveryLabels [ mode ] [ 0 ] ) ; text ( 'workflow-recovery-description' , workflowRecoveryLabels [ mode ] [ 1 ] ) ; text ( 'workflow-recovery-submit' , ` ${ workflowRecoveryLabels [ mode ] [ 0 ] } → ` ) ; text ( 'workflow-recovery-snapshot' , ` 当前流程修订 ${ state . selectedWorkflow . revision || '—' } · ${ context . step _key ? ` 步骤 ${ context . step _key } ` : '流程级决定' } ` ) ;
text ( 'workflow-recovery-confirm-label' , mode === 'continue' ? '我确认全部核查事项已经处理,可以解除流程核查。' : '我确认已经核实当前事实,并承担提交此恢复决定的责任。' ) ;
$ ( 'workflow-recovery-reason' ) . focus ( ) ;
if ( needsTask && context . task _id ) {
try { const task = extractData ( await request ( ` /v1/tasks/ ${ encodeURIComponent ( context . task _id ) } ` ) ) ; if ( ! $ ( 'workflow-recovery-task-revision' ) . value && task . revision ) $ ( 'workflow-recovery-task-revision' ) . value = task . revision ; }
catch { text ( 'workflow-recovery-description' , ` ${ workflowRecoveryLabels [ mode ] [ 1 ] } 当前任务修订读取失败,请手工填写任务 revision。 ` ) ; }
}
}
function parseRecoveryResult ( value ) {
try { return JSON . parse ( value ) ; } catch { throw new Error ( '结果必须是合法 JSON, 例如 {"status":"DONE"};没有结果时填写 null。' ) ; }
}
function validRevision ( value ) { return /^[1-9]\d*$/ . test ( String ( value || '' ) . trim ( ) ) ; }
async function submitWorkflowRecovery ( event ) {
event . preventDefault ( ) ; if ( ! state . selectedWorkflow || ! state . workflowRecoveryMode ) return ;
const mode = state . workflowRecoveryMode ; const workflow = state . selectedWorkflow ; const reason = $ ( 'workflow-recovery-reason' ) . value . trim ( ) ; const evidence = $ ( 'workflow-recovery-evidence' ) . value . trim ( ) ;
const error = message => { $ ( 'workflow-recovery-error' ) . textContent = message ; } ;
if ( ! reason ) { error ( '请填写核查原因。' ) ; return ; }
if ( ! evidence || /[\s\x00-\x1f\x7f]/ . test ( evidence ) ) { error ( '证据引用必填,且不能包含空格或控制字符。' ) ; return ; }
if ( ! validRevision ( workflow . revision ) ) { error ( '当前流程 revision 无效,请重新打开流程详情。' ) ; return ; }
if ( ! $ ( 'workflow-recovery-confirm' ) . checked ) { error ( '请先确认本次核查决定。' ) ; return ; }
const body = { expected _revision : String ( workflow . revision ) , reason , evidence _ref : evidence } ; let path = ` /v1/workflows/ ${ encodeURIComponent ( workflow . id ) } /recovery/ ${ mode } ` ;
if ( mode === 'continue' ) body . review _resolved = true ;
if ( [ 'step-resolve' , 'step-retry' , 'poll-continue' , 'wait-rearm' ] . includes ( mode ) ) {
const stepKey = $ ( 'workflow-recovery-step-key' ) . value . trim ( ) ; const activationID = $ ( 'workflow-recovery-activation-id' ) . value . trim ( ) ; const stepRevision = $ ( 'workflow-recovery-step-revision' ) . value . trim ( ) ;
if ( ! stepKey || ! activationID || ! validRevision ( stepRevision ) ) { error ( '请填写当前步骤 key、激活 ID 和步骤 revision。' ) ; return ; }
Object . assign ( body , { step _key : stepKey , activation _id : activationID , expected _step _revision : stepRevision } ) ;
if ( mode === 'step-resolve' ) { const result = parseRecoveryResult ( $ ( 'workflow-recovery-result' ) . value . trim ( ) ) ; body . execution _settled = true ; body . outcome = $ ( 'workflow-recovery-outcome' ) . value ; body . result = result ; }
if ( mode === 'step-retry' ) body . safe _to _retry = true ;
if ( mode === 'poll-continue' ) body . pending _confirmed = true ;
if ( mode === 'wait-rearm' ) body . wait _verified = true ;
}
if ( mode === 'task-review' ) {
const taskID = $ ( 'workflow-recovery-task-id' ) . value . trim ( ) ; const taskRevision = $ ( 'workflow-recovery-task-revision' ) . value . trim ( ) ; const result = parseRecoveryResult ( $ ( 'workflow-recovery-result' ) . value . trim ( ) ) ;
if ( ! taskID || ! validRevision ( taskRevision ) ) { error ( '请填写待核查任务 ID 和当前任务 revision。' ) ; return ; }
if ( result === null ) { error ( '任务核查的 result 不能为 null, 请填写实际 JSON 结果。' ) ; return ; }
Object . assign ( body , { task _id : taskID , expected _task _revision : taskRevision , execution _settled : true , outcome : $ ( 'workflow-recovery-outcome' ) . value , result } ) ;
}
const button = $ ( 'workflow-recovery-submit' ) ; button . disabled = true ; error ( '' ) ; const workflowID = workflow . id ;
try { const response = await request ( path , { method : 'POST' , headers : { 'Content-Type' : 'application/json' , 'Idempotency-Key' : operationKey ( ` workflow-recovery- ${ mode } ` ) } , body : JSON . stringify ( body ) } ) ; showToast ( response ? . data ? . deduplicated ? '已恢复原恢复回执' : ` ${ workflowRecoveryLabels [ mode ] [ 0 ] } 已提交 ` ) ; closeWorkflowRecovery ( ) ; await refresh ( ) ; await openWorkflowDetail ( workflowID ) ; }
catch ( err ) { error ( err . message || '流程恢复失败' ) ; }
finally { button . disabled = false ; }
}
function renderWorkflowDetail ( workflow ) {
function renderWorkflowDetail ( workflow ) {
state . selectedWorkflow = workflow ;
state . selectedWorkflow = workflow ;
text ( 'workflow-detail-title' , workflow . business _key || '流程实例' ) ; text ( 'workflow-detail-id' , workflow . id || '—' ) ; text ( 'workflow-detail-state' , workflow . state || 'UNKNOWN' ) ; $ ( 'workflow-detail-state' ) . className = ` state-pill ${ stateClass ( workflow . state ) } ` ;
text ( 'workflow-detail-title' , workflow . business _key || '流程实例' ) ; text ( 'workflow-detail-id' , workflow . id || '—' ) ; text ( 'workflow-detail-state' , workflow . state || 'UNKNOWN' ) ; $ ( 'workflow-detail-state' ) . className = ` state-pill ${ stateClass ( workflow . state ) } ` ;
@@ -259,11 +338,13 @@
async function openWorkflowDetail ( id ) {
async function openWorkflowDetail ( id ) {
if ( ! id ) return ;
if ( ! id ) return ;
if ( ! state . token ) { showToast ( '请先连接具备 workflows:read 权限的凭据' ) ; return ; }
if ( ! state . token ) { showToast ( '请先连接具备 workflows:read 权限的凭据' ) ; return ; }
showWorkflowDrawer ( ) ; closeWorkflowAction ( ) ; $ ( 'workflow-actions ') . hidden = true ; text ( 'workflow-detail-title' , '正在读取流程…' ) ; text ( 'workflow-detail-id' , shortID ( id ) ) ; text ( 'workflow-detail-state' , '读取中' ) ; $ ( 'workflow-steps' ) . innerHTML = '<div class="detail-loading">正在读取步骤…</div>' ; $ ( 'workflow-events' ) . innerHTML = '<div class="detail-loading">正在读取事件…</div>' ;
showWorkflowDrawer ( ) ; closeWorkflowAction ( ) ; closeWorkflowRecovery ( ) ; $ ( 'workflow-actions' ) . hidden = true ; $ ( 'workflow-recovery-block ') . hidden = true ; text ( 'workflow-detail-title' , '正在读取流程…' ) ; text ( 'workflow-detail-id' , shortID ( id ) ) ; text ( 'workflow-detail-state' , '读取中' ) ; $ ( 'workflow-steps' ) . innerHTML = '<div class="detail-loading">正在读取步骤…</div>' ; $ ( 'workflow-events' ) . innerHTML = '<div class="detail-loading">正在读取事件…</div>' ;
const encoded = encodeURIComponent ( id ) ; const [ workflowResult , stepsResult , eventsResult ] = await Promise . allSettled ( [ request ( ` /v1/workflows/ ${ encoded } ` ) , request ( ` /v1/workflows/ ${ encoded } /steps ` ) , request ( ` /v1/workflows/ ${ encoded } /events?limit=20 ` ) ] ) ;
const encoded = encodeURIComponent ( id ) ; const [ workflowResult , stepsResult , eventsResult ] = await Promise . allSettled ( [ request ( ` /v1/workflows/ ${ encoded } ` ) , request ( ` /v1/workflows/ ${ encoded } /steps ` ) , request ( ` /v1/workflows/ ${ encoded } /events?limit=20 ` ) ] ) ;
if ( workflowResult . status !== 'fulfilled' ) { text ( 'workflow-detail-title' , '流程读取失败' ) ; text ( 'workflow-detail-note' , workflowResult . reason ? . message || '无法读取流程详情' ) ; renderWorkflowSteps ( { } , true ) ; renderWorkflowEvents ( { } , true ) ; return ; }
if ( workflowResult . status !== 'fulfilled' ) { text ( 'workflow-detail-title' , '流程读取失败' ) ; text ( 'workflow-detail-note' , workflowResult . reason ? . message || '无法读取流程详情' ) ; renderWorkflowSteps ( { } , true ) ; renderWorkflowEvents ( { } , true ) ; return ; }
renderWorkflowDetail ( extractData ( workflowResult . value ) ) ;
const workflow = extractData ( workflowResult . value ) ; const stepsPayload = stepsResult . status === 'fulfilled' ? extractData ( stepsResult . value ) : { } ;
renderWorkflowSteps ( stepsResult . status === 'fulfilled' ? extractData ( stepsResult . value ) : { } , stepsResult . status === 'rejected' ) ;
renderWorkflowDetail ( workflow ) ;
renderWorkflowSteps ( stepsPayload , stepsResult . status === 'rejected' ) ;
renderWorkflowRecovery ( workflow , stepsPayload , stepsResult . status === 'rejected' ) ;
renderWorkflowEvents ( eventsResult . status === 'fulfilled' ? extractData ( eventsResult . value ) : { } , eventsResult . status === 'rejected' ) ;
renderWorkflowEvents ( eventsResult . status === 'fulfilled' ? extractData ( eventsResult . value ) : { } , eventsResult . status === 'rejected' ) ;
}
}
function renderSubscriptionDetail ( subscription ) {
function renderSubscriptionDetail ( subscription ) {
@@ -336,6 +417,8 @@
}
}
$ ( 'connect-button' ) . addEventListener ( 'click' , connect ) ; $ ( 'connect-hero' ) . addEventListener ( 'click' , ( ) => $ ( 'connection-panel' ) . scrollIntoView ( { behavior : 'smooth' , block : 'center' } ) ) ; $ ( 'refresh-button' ) . addEventListener ( 'click' , refresh ) ;
$ ( 'connect-button' ) . addEventListener ( 'click' , connect ) ; $ ( 'connect-hero' ) . addEventListener ( 'click' , ( ) => $ ( 'connection-panel' ) . scrollIntoView ( { behavior : 'smooth' , block : 'center' } ) ) ; $ ( 'refresh-button' ) . addEventListener ( 'click' , refresh ) ;
$ ( 'close-task-drawer' ) . addEventListener ( 'click' , closeTaskDrawer ) ; $ ( 'task-drawer-backdrop' ) . addEventListener ( 'click' , event => { if ( event . target . id === 'task-drawer-backdrop' ) closeTaskDrawer ( ) ; } ) ; $ ( 'action-cancel' ) . addEventListener ( 'click' , closeActionForm ) ; $ ( 'task-action-form' ) . addEventListener ( 'submit' , submitAction ) ; $ ( 'task-cancel-button' ) . addEventListener ( 'click' , ( ) => openActionForm ( 'cancel' ) ) ; $ ( 'task-retry-button' ) . addEventListener ( 'click' , ( ) => openActionForm ( 'retry' ) ) ; $ ( 'close-schedule-drawer' ) . addEventListener ( 'click' , closeScheduleDrawer ) ; $ ( 'schedule-drawer-backdrop' ) . addEventListener ( 'click' , event => { if ( event . target . id === 'schedule-drawer-backdrop' ) closeScheduleDrawer ( ) ; } ) ; $ ( 'schedule-control-button' ) . addEventListener ( 'click' , openScheduleAction ) ; $ ( 'schedule-action-cancel' ) . addEventListener ( 'click' , closeScheduleAction ) ; $ ( 'schedule-action-form' ) . addEventListener ( 'submit' , submitScheduleAction ) ; $ ( 'close-workflow-drawer' ) . addEventListener ( 'click' , closeWorkflowDrawer ) ; $ ( 'workflow-drawer-backdrop' ) . addEventListener ( 'click' , event => { if ( event . target . id === 'workflow-drawer-backdrop' ) closeWorkflowDrawer ( ) ; } ) ; $ ( 'workflow-control-button' ) . addEventListener ( 'click' , ( ) => openWorkflowAction ( state . selectedWorkflow ? . paused ? 'RESUME' : 'PAUSE' ) ) ; $ ( 'workflow-cancel-button' ) . addEventListener ( 'click' , ( ) => openWorkflowAction ( 'CANCEL' ) ) ; $ ( 'workflow-action-cancel' ) . addEventListener ( 'click' , closeWorkflowAction ) ; $ ( 'workflow-action-form' ) . addEventListener ( 'submit' , submitWorkflowAction ) ; $ ( 'close-subscription-drawer' ) . addEventListener ( 'click' , closeSubscriptionDrawer ) ; $ ( 'subscription-drawer-backdrop' ) . addEventListener ( 'click' , event => { if ( event . target . id === 'subscription-drawer-backdrop' ) closeSubscriptionDrawer ( ) ; } ) ; $ ( 'subscription-control-button' ) . addEventListener ( 'click' , openSubscriptionAction ) ; $ ( 'subscription-action-cancel' ) . addEventListener ( 'click' , closeSubscriptionAction ) ; $ ( 'subscription-action-form' ) . addEventListener ( 'submit' , submitSubscriptionAction ) ; $ ( 'subscription-retry-cancel' ) . addEventListener ( 'click' , closeSubscriptionRetry ) ; $ ( 'subscription-retry-form' ) . addEventListener ( 'submit' , submitSubscriptionRetry ) ; document . addEventListener ( 'keydown' , event => { if ( event . key === 'Escape' ) { if ( ! $ ( 'task-action-form' ) . hidden ) closeActionForm ( ) ; else if ( ! $ ( 'schedule-action-form' ) . hidden ) closeScheduleAction ( ) ; else if ( ! $ ( 'workflow-action-form' ) . hidden ) closeWorkflowAction ( ) ; else if ( ! $ ( 'subscription-action-form' ) . hidden ) closeSubscriptionAction ( ) ; else if ( ! $ ( 'subscription-retry-form' ) . hidden ) closeSubscriptionRetry ( ) ; else if ( ! $ ( 'task-drawer-backdrop' ) . hidden ) closeTaskDrawer ( ) ; else if ( ! $ ( 'schedule-drawer-backdrop' ) . hidden ) closeScheduleDrawer ( ) ; else if ( ! $ ( 'workflow-drawer-backdrop' ) . hidden ) closeWorkflowDrawer ( ) ; else if ( ! $ ( 'subscription-drawer-backdrop' ) . hidden ) closeSubscriptionDrawer ( ) ; } } ) ;
$ ( 'close-task-drawer' ) . addEventListener ( 'click' , closeTaskDrawer ) ; $ ( 'task-drawer-backdrop' ) . addEventListener ( 'click' , event => { if ( event . target . id === 'task-drawer-backdrop' ) closeTaskDrawer ( ) ; } ) ; $ ( 'action-cancel' ) . addEventListener ( 'click' , closeActionForm ) ; $ ( 'task-action-form' ) . addEventListener ( 'submit' , submitAction ) ; $ ( 'task-cancel-button' ) . addEventListener ( 'click' , ( ) => openActionForm ( 'cancel' ) ) ; $ ( 'task-retry-button' ) . addEventListener ( 'click' , ( ) => openActionForm ( 'retry' ) ) ; $ ( 'close-schedule-drawer' ) . addEventListener ( 'click' , closeScheduleDrawer ) ; $ ( 'schedule-drawer-backdrop' ) . addEventListener ( 'click' , event => { if ( event . target . id === 'schedule-drawer-backdrop' ) closeScheduleDrawer ( ) ; } ) ; $ ( 'schedule-control-button' ) . addEventListener ( 'click' , openScheduleAction ) ; $ ( 'schedule-action-cancel' ) . addEventListener ( 'click' , closeScheduleAction ) ; $ ( 'schedule-action-form' ) . addEventListener ( 'submit' , submitScheduleAction ) ; $ ( 'close-workflow-drawer' ) . addEventListener ( 'click' , closeWorkflowDrawer ) ; $ ( 'workflow-drawer-backdrop' ) . addEventListener ( 'click' , event => { if ( event . target . id === 'workflow-drawer-backdrop' ) closeWorkflowDrawer ( ) ; } ) ; $ ( 'workflow-control-button' ) . addEventListener ( 'click' , ( ) => openWorkflowAction ( state . selectedWorkflow ? . paused ? 'RESUME' : 'PAUSE' ) ) ; $ ( 'workflow-cancel-button' ) . addEventListener ( 'click' , ( ) => openWorkflowAction ( 'CANCEL' ) ) ; $ ( 'workflow-action-cancel' ) . addEventListener ( 'click' , closeWorkflowAction ) ; $ ( 'workflow-action-form' ) . addEventListener ( 'submit' , submitWorkflowAction ) ; $ ( 'close-subscription-drawer' ) . addEventListener ( 'click' , closeSubscriptionDrawer ) ; $ ( 'subscription-drawer-backdrop' ) . addEventListener ( 'click' , event => { if ( event . target . id === 'subscription-drawer-backdrop' ) closeSubscriptionDrawer ( ) ; } ) ; $ ( 'subscription-control-button' ) . addEventListener ( 'click' , openSubscriptionAction ) ; $ ( 'subscription-action-cancel' ) . addEventListener ( 'click' , closeSubscriptionAction ) ; $ ( 'subscription-action-form' ) . addEventListener ( 'submit' , submitSubscriptionAction ) ; $ ( 'subscription-retry-cancel' ) . addEventListener ( 'click' , closeSubscriptionRetry ) ; $ ( 'subscription-retry-form' ) . addEventListener ( 'submit' , submitSubscriptionRetry ) ; document . addEventListener ( 'keydown' , event => { if ( event . key === 'Escape' ) { if ( ! $ ( 'task-action-form' ) . hidden ) closeActionForm ( ) ; else if ( ! $ ( 'schedule-action-form' ) . hidden ) closeScheduleAction ( ) ; else if ( ! $ ( 'workflow-action-form' ) . hidden ) closeWorkflowAction ( ) ; else if ( ! $ ( 'subscription-action-form' ) . hidden ) closeSubscriptionAction ( ) ; else if ( ! $ ( 'subscription-retry-form' ) . hidden ) closeSubscriptionRetry ( ) ; else if ( ! $ ( 'task-drawer-backdrop' ) . hidden ) closeTaskDrawer ( ) ; else if ( ! $ ( 'schedule-drawer-backdrop' ) . hidden ) closeScheduleDrawer ( ) ; else if ( ! $ ( 'workflow-drawer-backdrop' ) . hidden ) closeWorkflowDrawer ( ) ; else if ( ! $ ( 'subscription-drawer-backdrop' ) . hidden ) closeSubscriptionDrawer ( ) ; } } ) ;
$ ( 'workflow-recovery-actions' ) . addEventListener ( 'click' , event => { const button = event . target . closest ( '[data-recovery-action]' ) ; if ( ! button ) return ; let context = { } ; try { context = JSON . parse ( button . dataset . recoveryContext || '{}' ) ; } catch { showToast ( '恢复按钮上下文无效,请重新读取流程' ) ; return ; } openWorkflowRecovery ( button . dataset . recoveryAction , context ) ; } ) ; $ ( 'workflow-recovery-cancel' ) . addEventListener ( 'click' , closeWorkflowRecovery ) ; $ ( 'workflow-recovery-form' ) . addEventListener ( 'submit' , submitWorkflowRecovery ) ;
document . addEventListener ( 'keydown' , event => { if ( event . key === 'Escape' && ! $ ( 'workflow-recovery-form' ) . hidden ) closeWorkflowRecovery ( ) ; } ) ;
$ ( 'reveal-token' ) . addEventListener ( 'click' , ( ) => { const field = $ ( 'token' ) ; field . type = field . type === 'password' ? 'text' : 'password' ; $ ( 'reveal-token' ) . textContent = field . type === 'password' ? '显示' : '隐藏' ; } ) ;
$ ( 'reveal-token' ) . addEventListener ( 'click' , ( ) => { const field = $ ( 'token' ) ; field . type = field . type === 'password' ? 'text' : 'password' ; $ ( 'reveal-token' ) . textContent = field . type === 'password' ? '显示' : '隐藏' ; } ) ;
$ ( 'credential-file' ) . addEventListener ( 'change' , event => { const file = event . target . files ? . [ 0 ] ; if ( ! file ) return ; const reader = new FileReader ( ) ; reader . onload = ( ) => { try { const credential = JSON . parse ( reader . result ) ; $ ( 'token' ) . value = credential . token || '' ; $ ( 'queue-id' ) . value = credential . queue _id || '' ; showToast ( '凭据已读取,只保留在当前页面内' ) ; } catch { showToast ( '凭据 JSON 无法解析' ) ; } } ; reader . readAsText ( file ) ; } ) ;
$ ( 'credential-file' ) . addEventListener ( 'change' , event => { const file = event . target . files ? . [ 0 ] ; if ( ! file ) return ; const reader = new FileReader ( ) ; reader . onload = ( ) => { try { const credential = JSON . parse ( reader . result ) ; $ ( 'token' ) . value = credential . token || '' ; $ ( 'queue-id' ) . value = credential . queue _id || '' ; showToast ( '凭据已读取,只保留在当前页面内' ) ; } catch { showToast ( '凭据 JSON 无法解析' ) ; } } ; reader . readAsText ( file ) ; } ) ;
document . querySelectorAll ( '[data-view]' ) . forEach ( button => button . addEventListener ( 'click' , event => { event . preventDefault ( ) ; setView ( button . dataset . view ) ; } ) ) ;
document . querySelectorAll ( '[data-view]' ) . forEach ( button => button . addEventListener ( 'click' , event => { event . preventDefault ( ) ; setView ( button . dataset . view ) ; } ) ) ;