- Add human task dspy: human_complete, human_list, approval_approve, approval_reject - Add task_restart.dspy for restarting completed/failed tasks - Enhance task_detail.ui: inline human task forms, approval buttons, restart - New step_panel.ui: view/modify step artifacts with cascade rerun - Update task_list.ui: Tabular with search_form filter by pipeline/state - Update index.ui: embed task list Tabular inline - Update load_path.py: register new dspy/ui paths
251 lines
14 KiB
XML
251 lines
14 KiB
XML
{% set ns = namespace() %}
|
|
{% set task_data = get_task_data(request) %}
|
|
{% if not task_data.success %}
|
|
{
|
|
"widgettype": "VBox",
|
|
"options": {"width": "100%", "padding": "16px"},
|
|
"subwidgets": [
|
|
{
|
|
"widgettype": "Button",
|
|
"options": {"label": "返回列表"},
|
|
"binds": [{
|
|
"wid": "self", "event": "click", "actiontype": "urlwidget",
|
|
"target": "app.pipeline_task_content",
|
|
"options": {"url": "{{entire_url('task_list.ui')}}"},
|
|
"mode": "replace"
|
|
}]
|
|
},
|
|
{"widgettype": "Title", "options": {"text": "任务详情", "cfontsize": 20}},
|
|
{"widgettype": "Text", "options": {"text": "{{task_data.message or '加载失败'}}"}}
|
|
]
|
|
}
|
|
{% else %}
|
|
{% set task = task_data.task %}
|
|
{% set steps = task.steps or [] %}
|
|
{% set state_labels = {'submitted':'已提交','running':'运行中','completed':'已完成','failed':'失败','paused':'已暂停','cancelled':'已取消','waiting':'等待人工'} %}
|
|
{% set step_state_labels = {'pending':'待执行','running':'执行中','completed':'已完成','failed':'失败','skipped':'已跳过','waiting':'等待人工','rejected':'已驳回'} %}
|
|
{% set is_done = task.state in ('completed', 'failed', 'cancelled') %}
|
|
{
|
|
"widgettype": "VBox",
|
|
"options": {"width": "100%", "padding": "16px"},
|
|
"subwidgets": [
|
|
{
|
|
"widgettype": "HBox",
|
|
"options": {"width": "100%", "marginBottom": "12px", "gap": "12px", "alignItems": "center"},
|
|
"subwidgets": [
|
|
{
|
|
"widgettype": "Button",
|
|
"options": {"label": "返回列表"},
|
|
"binds": [{
|
|
"wid": "self", "event": "click", "actiontype": "urlwidget",
|
|
"target": "app.pipeline_task_content",
|
|
"options": {"url": "{{entire_url('index.ui')}}"},
|
|
"mode": "replace"
|
|
}]
|
|
},
|
|
{"widgettype": "Title", "options": {"text": "任务详情 - {{json.dumps(task.title or '', ensure_ascii=False)}}", "cfontsize": 18}},
|
|
{"widgettype": "Text", "options": {"text": "状态: {{state_labels.get(task.state, task.state)}} | 版本: v{{task.current_version or 1}}"}}
|
|
]
|
|
},
|
|
{
|
|
"widgettype": "HBox",
|
|
"options": {"width": "100%", "marginBottom": "12px", "gap": "8px"},
|
|
"subwidgets": [
|
|
{% if task.state == 'running' %}
|
|
{
|
|
"widgettype": "Button",
|
|
"options": {"label": "暂停", "css": "warning"},
|
|
"binds": [{
|
|
"wid": "self", "event": "click", "actiontype": "script",
|
|
"target": "self",
|
|
"script": "var u='{{entire_url('api/task_control.dspy')}}';var fd=new URLSearchParams();fd.append('task_id','{{task.id}}');fd.append('action','pause');fetch(u,{method:'POST',body:fd,credentials:'include'}).then(function(r){return r.json()}).then(function(d){if(d.success){location.reload()}else{alert(d.message)}})"
|
|
}]
|
|
},
|
|
{% endif %}
|
|
{% if task.state == 'paused' %}
|
|
{
|
|
"widgettype": "Button",
|
|
"options": {"label": "恢复", "css": "primary"},
|
|
"binds": [{
|
|
"wid": "self", "event": "click", "actiontype": "script",
|
|
"target": "self",
|
|
"script": "var u='{{entire_url('api/task_control.dspy')}}';var fd=new URLSearchParams();fd.append('task_id','{{task.id}}');fd.append('action','resume');fetch(u,{method:'POST',body:fd,credentials:'include'}).then(function(r){return r.json()}).then(function(d){if(d.success){location.reload()}else{alert(d.message)}})"
|
|
}]
|
|
},
|
|
{% endif %}
|
|
{% if not is_done %}
|
|
{
|
|
"widgettype": "Button",
|
|
"options": {"label": "取消", "css": "danger"},
|
|
"binds": [{
|
|
"wid": "self", "event": "click", "actiontype": "script",
|
|
"target": "self",
|
|
"script": "if(!confirm('确定取消任务?'))return;var u='{{entire_url('api/task_control.dspy')}}';var fd=new URLSearchParams();fd.append('task_id','{{task.id}}');fd.append('action','cancel');fetch(u,{method:'POST',body:fd,credentials:'include'}).then(function(r){return r.json()}).then(function(d){if(d.success){location.reload()}else{alert(d.message)}})"
|
|
}]
|
|
},
|
|
{% endif %}
|
|
{% if is_done %}
|
|
{
|
|
"widgettype": "Button",
|
|
"options": {"label": "重新开始", "css": "primary"},
|
|
"binds": [{
|
|
"wid": "self", "event": "click", "actiontype": "script",
|
|
"target": "self",
|
|
"script": "if(!confirm('确定重新开始?'))return;var u='{{entire_url('api/task_restart.dspy')}}';var fd=new URLSearchParams();fd.append('task_id','{{task.id}}');fetch(u,{method:'POST',body:fd,credentials:'include'}).then(function(r){return r.json()}).then(function(d){if(d.success){location.reload()}else{alert(d.message)}})"
|
|
}]
|
|
}
|
|
{% endif %}
|
|
]
|
|
},
|
|
{
|
|
"widgettype": "Text",
|
|
"options": {"text": "步骤列表 ({{steps|length}} 个步骤)", "cfontsize": 14}
|
|
},
|
|
{% for step in steps %}
|
|
{
|
|
"widgettype": "VBox",
|
|
"options": {"width": "100%", "bgcolor": "var(--card-bg)", "padding": "12px", "marginBottom": "8px"},
|
|
"subwidgets": [
|
|
{
|
|
"widgettype": "HBox",
|
|
"options": {"width": "100%", "justifyContent": "space-between", "alignItems": "center", "marginBottom": "8px"},
|
|
"subwidgets": [
|
|
{
|
|
"widgettype": "Text",
|
|
"options": {"text": "{{json.dumps(step.display_name or step.step_name, ensure_ascii=False)}}{% if step.state == 'waiting' %} ⚠{% endif %}", "cfontsize": 14}
|
|
},
|
|
{
|
|
"widgettype": "Text",
|
|
"options": {"text": "{{step_state_labels.get(step.state, step.state)}}", "color": "{% if step.state == 'completed' %}#4caf50{% elif step.state == 'waiting' %}#ff9800{% elif step.state == 'failed' %}#f44336{% else %}#888{% endif %}"}
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"widgettype": "Text",
|
|
"options": {"text": "类型: {{step.step_type or '-'}} | 依赖: {{json.dumps(step.deps if step.deps is string else (step.deps or [])|join(',') or '无', ensure_ascii=False)}}"}
|
|
},
|
|
{% if step.state == 'waiting' and step.human_task %}
|
|
{
|
|
"widgettype": "HBox",
|
|
"options": {"width": "100%", "marginTop": "8px", "gap": "8px", "alignItems": "flex-start"},
|
|
"subwidgets": [
|
|
{
|
|
"widgettype": "VBox",
|
|
"options": {"width": "50%"},
|
|
"subwidgets": [
|
|
{"widgettype": "Text", "options": {"text": "⚠ 人工任务: {{step.human_task.task_type or '-'}}", "color": "#ff9800"}},
|
|
{"widgettype": "Text", "options": {"text": "处理状态: {{step.human_task.status or '-'}}"}},
|
|
{% if step.human_task.assignee_role %}
|
|
{"widgettype": "Text", "options": {"text": "处理角色: {{step.human_task.assignee_role}}"}},
|
|
{% endif %}
|
|
{% if step.human_task.form_schema %}
|
|
{"widgettype": "Text", "options": {"text": "表单: {{json.dumps(step.human_task.form_schema, ensure_ascii=False)}}"}},
|
|
{% endif %}
|
|
{
|
|
"widgettype": "Button",
|
|
"options": {"label": "查看产物", "css": "text"},
|
|
"binds": [{
|
|
"wid": "self", "event": "click", "actiontype": "urlwidget",
|
|
"target": "app.pipeline_task_content",
|
|
"options": {"url": "{{entire_url('step_panel.ui')}}?task_id={{task.id}}&step_name={{step.step_name}}"},
|
|
"mode": "replace"
|
|
}]
|
|
}
|
|
]
|
|
},
|
|
{% if step.human_task.task_type == 'human_task' and step.human_task.status == 'pending' %}
|
|
{
|
|
"widgettype": "Form",
|
|
"options": {
|
|
"width": "50%",
|
|
"name": "human_form_{{loop.index}}",
|
|
"submit_url": "{{entire_url('api/human_complete.dspy')}}",
|
|
"fields": [
|
|
{"name": "task_id", "uitype": "hide", "value": "{{task.id}}"},
|
|
{"name": "step_name", "uitype": "hide", "value": "{{step.step_name}}"},
|
|
{"name": "result", "uitype": "text", "label": "处理结果 (JSON)", "required": true, "placeholder": "{\"status\":\"ok\",\"note\":\"已完成\"}"}
|
|
]
|
|
},
|
|
"binds": [{
|
|
"wid": "self", "event": "submited",
|
|
"actiontype": "script",
|
|
"target": "self",
|
|
"script": "location.reload()"
|
|
}]
|
|
},
|
|
{% elif step.human_task.task_type == 'approval_gate' and step.human_task.status == 'pending' %}
|
|
{
|
|
"widgettype": "VBox",
|
|
"options": {"width": "50%", "gap": "8px"},
|
|
"subwidgets": [
|
|
{
|
|
"widgettype": "Button",
|
|
"options": {"label": "✓ 通过", "css": "primary"},
|
|
"binds": [{
|
|
"wid": "self", "event": "click", "actiontype": "script",
|
|
"target": "self",
|
|
"script": "var u='{{entire_url('api/approval_approve.dspy')}}';var fd=new URLSearchParams();fd.append('task_id','{{task.id}}');fd.append('step_name','{{step.step_name}}');fetch(u,{method:'POST',body:fd,credentials:'include'}).then(function(r){return r.json()}).then(function(d){if(d.success){location.reload()}else{alert(d.message)}})"
|
|
}]
|
|
},
|
|
{
|
|
"widgettype": "Button",
|
|
"options": {"label": "✗ 驳回", "css": "danger"},
|
|
"binds": [{
|
|
"wid": "self", "event": "click", "actiontype": "script",
|
|
"target": "self",
|
|
"script": "var reason=prompt('驳回理由:');if(!reason)return;var u='{{entire_url('api/approval_reject.dspy')}}';var fd=new URLSearchParams();fd.append('task_id','{{task.id}}');fd.append('step_name','{{step.step_name}}');fd.append('comments',reason);fetch(u,{method:'POST',body:fd,credentials:'include'}).then(function(r){return r.json()}).then(function(d){if(d.success){location.reload()}else{alert(d.message)}})"
|
|
}]
|
|
}
|
|
]
|
|
},
|
|
{% endif %}
|
|
{"widgettype": "Filler"}
|
|
]
|
|
},
|
|
{% elif step.state in ('completed', 'failed') %}
|
|
{
|
|
"widgettype": "HBox",
|
|
"options": {"width": "100%", "marginTop": "8px", "gap": "8px"},
|
|
"subwidgets": [
|
|
{
|
|
"widgettype": "Button",
|
|
"options": {"label": "查看产物", "css": "text"},
|
|
"binds": [{
|
|
"wid": "self", "event": "click", "actiontype": "urlwidget",
|
|
"target": "app.pipeline_task_content",
|
|
"options": {"url": "{{entire_url('step_panel.ui')}}?task_id={{task.id}}&step_name={{step.step_name}}"},
|
|
"mode": "replace"
|
|
}]
|
|
},
|
|
{
|
|
"widgettype": "Button",
|
|
"options": {"label": "修改并重跑", "css": "warning"},
|
|
"binds": [{
|
|
"wid": "self", "event": "click", "actiontype": "urlwidget",
|
|
"target": "app.pipeline_task_content",
|
|
"options": {"url": "{{entire_url('step_panel.ui')}}?task_id={{task.id}}&step_name={{step.step_name}}&mode=edit"},
|
|
"mode": "replace"
|
|
}]
|
|
}
|
|
]
|
|
},
|
|
{% else %}
|
|
{
|
|
"widgettype": "Button",
|
|
"options": {"label": "查看详情", "css": "text"},
|
|
"binds": [{
|
|
"wid": "self", "event": "click", "actiontype": "urlwidget",
|
|
"target": "app.pipeline_task_content",
|
|
"options": {"url": "{{entire_url('step_panel.ui')}}?task_id={{task.id}}&step_name={{step.step_name}}"},
|
|
"mode": "replace"
|
|
}]
|
|
},
|
|
{% endif %}
|
|
{"widgettype": "Filler"}
|
|
]
|
|
}{% if not loop.last %},{% endif %}
|
|
{% endfor %}
|
|
]
|
|
}
|
|
{% endif %}
|