From 0ad66d8b7e69fa6db185dee9af54ef0026e3f9a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=AC=E6=98=8E?= <83812544+Ed1s0nZ@users.noreply.github.com> Date: Fri, 3 Jul 2026 22:39:55 +0800 Subject: [PATCH] Add files via upload --- web/static/css/style.css | 412 ++++++++++++++++++++++++++++++++----- web/static/i18n/en-US.json | 8 + web/static/i18n/zh-CN.json | 8 + web/static/js/modal.js | 1 + web/static/js/workflows.js | 275 ++++++++++++++++++++++--- web/templates/index.html | 48 ++++- 6 files changed, 669 insertions(+), 83 deletions(-) diff --git a/web/static/css/style.css b/web/static/css/style.css index f3f008d3..4637a817 100644 --- a/web/static/css/style.css +++ b/web/static/css/style.css @@ -30170,15 +30170,23 @@ html[data-theme="dark"] .form-group select { /* Workflow drag editor */ .workflow-page-content { display: grid; - grid-template-columns: 280px minmax(0, 1fr) 320px; + grid-template-columns: + clamp(200px, 16vw, 260px) + minmax(0, 1fr) + clamp(220px, 20vw, 300px); gap: 14px; min-height: calc(100vh - 150px); + width: 100%; + max-width: 100%; + box-sizing: border-box; } .workflow-sidebar, .workflow-properties, .workflow-main { min-width: 0; + max-width: 100%; + overflow: hidden; } .workflow-panel, @@ -30218,31 +30226,139 @@ html[data-theme="dark"] .form-group select { } .workflow-list-item { - display: grid; - gap: 4px; + display: flex; + align-items: center; + gap: 8px; width: 100%; - text-align: left; - padding: 10px 12px; + padding: 8px 8px 8px 10px; border: 1px solid var(--border-color); - border-radius: 8px; + border-radius: 10px; background: var(--bg-primary); color: var(--text-primary); + transition: border-color 0.15s ease, box-shadow 0.15s ease, background 0.15s ease; +} + +.workflow-list-item.is-active { + border-color: var(--accent-color); + background: rgba(59, 130, 246, 0.08); + box-shadow: inset 3px 0 0 var(--accent-color); +} + +.workflow-list-item:hover { + border-color: rgba(59, 130, 246, 0.45); + background: rgba(59, 130, 246, 0.05); +} + +.workflow-list-main { + flex: 1; + min-width: 0; + display: grid; + gap: 3px; + text-align: left; + padding: 2px 0; + border: none; + background: transparent; + color: inherit; cursor: pointer; } -.workflow-list-item:hover, -.workflow-list-item.is-active { - border-color: var(--accent-color); - background: rgba(59, 130, 246, 0.10); +.workflow-list-actions { + display: flex; + align-items: center; + gap: 6px; + flex-shrink: 0; + padding-right: 2px; +} + +.workflow-list-edit { + width: 28px; + height: 28px; + padding: 0; + border-radius: 7px; +} + +.workflow-switch { + position: relative; + display: inline-flex; + width: 34px; + height: 20px; + flex-shrink: 0; +} + +.workflow-switch input { + opacity: 0; + width: 0; + height: 0; + position: absolute; +} + +.workflow-switch-slider { + position: absolute; + inset: 0; + background: rgba(100, 116, 139, 0.35); + border-radius: 999px; + transition: background 0.2s ease; + cursor: pointer; +} + +.workflow-switch-slider::before { + content: ''; + position: absolute; + width: 14px; + height: 14px; + left: 3px; + top: 3px; + background: #fff; + border-radius: 50%; + box-shadow: 0 1px 2px rgba(15, 23, 42, 0.18); + transition: transform 0.2s ease; +} + +.workflow-switch input:checked + .workflow-switch-slider { + background: var(--accent-color); +} + +.workflow-switch input:checked + .workflow-switch-slider::before { + transform: translateX(14px); +} + +.workflow-switch input:focus-visible + .workflow-switch-slider { + outline: 2px solid rgba(59, 130, 246, 0.45); + outline-offset: 2px; +} + +.workflow-switch--modal { + width: 42px; + height: 24px; +} + +.workflow-switch--modal .workflow-switch-slider::before { + width: 18px; + height: 18px; + top: 3px; + left: 3px; +} + +.workflow-switch--modal input:checked + .workflow-switch-slider::before { + transform: translateX(18px); } .workflow-list-title { font-weight: 700; + font-size: 13px; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } .workflow-list-meta { color: var(--text-secondary); - font-size: 12px; + font-size: 11px; + line-height: 1.3; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } .workflow-node-palette { @@ -30269,54 +30385,223 @@ html[data-theme="dark"] .form-group select { display: grid; grid-template-rows: auto minmax(520px, 1fr); gap: 14px; + min-width: 0; } .workflow-meta-bar { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + align-items: center; + gap: 0; + padding: 8px 10px 8px 12px; + min-height: 44px; + box-sizing: border-box; + background: var(--bg-primary); + box-shadow: inset 0 -1px 0 rgba(15, 23, 42, 0.06); +} + +.workflow-canvas-header { + min-width: 0; + padding-right: 12px; +} + +.workflow-canvas-heading { display: flex; - align-items: flex-end; - justify-content: space-between; - gap: 12px; - padding: 12px; + align-items: center; + gap: 8px; + min-width: 0; + max-width: 100%; + padding: 5px 12px; + min-height: 32px; + box-sizing: border-box; + border-radius: 7px; + background: var(--bg-secondary); + border: 1px solid var(--border-color); + overflow: hidden; } -.workflow-meta-fields { - display: grid; - grid-template-columns: minmax(140px, 220px) minmax(180px, 260px) minmax(200px, 1fr) auto; - gap: 10px; - flex: 1; - align-items: end; +.workflow-canvas-title { + margin: 0; + font-size: 15px; + font-weight: 700; + color: var(--text-primary); + line-height: 1.3; + flex-shrink: 0; + max-width: 40%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } -.workflow-meta-fields label { - display: grid; - gap: 5px; +.workflow-canvas-subtitle { + font-size: 12px; color: var(--text-secondary); + line-height: 1.3; + min-width: 0; + flex: 1; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.workflow-canvas-subtitle::before { + content: '·'; + margin-right: 8px; + opacity: 0.45; +} + +.workflow-canvas-title.is-disabled { + opacity: 0.7; +} + +.workflow-meta-modal-content { + max-width: 340px; + width: calc(100vw - 32px); + margin: 12vh auto auto; + height: auto !important; + max-height: none !important; +} + +.workflow-meta-modal-content .modal-header.workflow-meta-modal-header { + padding: 8px 12px !important; + min-height: 0; + box-shadow: none; +} + +.workflow-meta-modal-content .modal-header.workflow-meta-modal-header h2 { + font-size: 13px; + font-weight: 700; + line-height: 1.2; +} + +.workflow-meta-modal-content .modal-body.workflow-meta-modal-body { + display: grid; + gap: 6px; + padding: 8px 12px !important; + flex: 0 0 auto !important; + overflow: visible; +} + +.workflow-meta-modal-content .modal-footer.workflow-meta-modal-footer { + padding: 6px 12px !important; + gap: 6px; + flex-shrink: 0; +} + +.workflow-meta-modal-content .workflow-meta-field.form-group { + gap: 2px; + margin: 0 !important; +} + +.workflow-meta-modal-content .workflow-meta-field label { + font-size: 11px; + font-weight: 600; + line-height: 1.2; + margin: 0; + color: var(--text-secondary); +} + +.workflow-meta-modal-content .workflow-meta-field .form-input { + padding: 5px 8px; + font-size: 13px; + line-height: 1.3; + min-height: 0; + height: 30px; + box-sizing: border-box; +} + +.workflow-meta-modal-content .workflow-meta-id-hint { + margin: 1px 0 0; + font-size: 10px; + line-height: 1.25; +} + +.workflow-meta-modal-content .workflow-meta-id-locked { + display: flex; + align-items: center; + min-height: 30px; + padding: 4px 8px; + border: 1px dashed var(--border-color); + border-radius: 6px; + background: var(--bg-secondary); + box-sizing: border-box; +} + +.workflow-meta-modal-content .workflow-meta-id-locked[hidden] { + display: none !important; +} + +.workflow-meta-modal-content .workflow-meta-id-input[hidden] { + display: none !important; +} + +.workflow-meta-modal-content .workflow-meta-id-locked code { + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; + font-size: 12px; + color: var(--text-primary); + word-break: break-all; + line-height: 1.2; +} + +.workflow-meta-id-group.is-locked .workflow-meta-id-hint { + display: none; +} + +.workflow-meta-modal-content .workflow-meta-enable-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + margin: 0; + padding: 4px 0 0; + border-top: 1px solid var(--border-color); +} + +.workflow-meta-modal-content .workflow-meta-enable-label { font-size: 12px; font-weight: 600; -} - -.workflow-meta-fields input[type="text"] { - width: 100%; - padding: 8px 10px; - border: 1px solid var(--border-color); - border-radius: 7px; - background: var(--bg-primary); + line-height: 1.2; color: var(--text-primary); } -.workflow-enabled-toggle { - display: flex !important; - align-items: center; - gap: 8px !important; - white-space: nowrap; - padding-bottom: 8px; +.workflow-meta-modal-content .workflow-switch--modal { + width: 36px; + height: 20px; +} + +.workflow-meta-modal-content .workflow-switch--modal .workflow-switch-slider::before { + width: 14px; + height: 14px; + top: 3px; + left: 3px; +} + +.workflow-meta-modal-content .workflow-switch--modal input:checked + .workflow-switch-slider::before { + transform: translateX(16px); +} + +.form-required { + color: #ef4444; } .workflow-toolbar { display: flex; - flex-wrap: wrap; + flex-wrap: nowrap; justify-content: flex-end; - gap: 8px; + align-items: center; + gap: 6px; + flex-shrink: 0; + white-space: nowrap; + padding-left: 12px; + border-left: 1px solid var(--border-color); + min-height: 32px; +} + +.workflow-toolbar .btn-small { + min-height: 30px; + padding: 5px 11px; + font-size: 12px; + font-weight: 600; } .workflow-toolbar .active { @@ -30352,10 +30637,27 @@ html[data-theme="dark"] .form-group select { } .workflow-properties { + display: flex; + flex-direction: column; + min-height: 0; + align-self: stretch; padding-bottom: 12px; overflow: hidden; } +.workflow-properties > .workflow-panel-header { + flex-shrink: 0; +} + +.workflow-properties > .workflow-property-empty, +.workflow-properties > .workflow-property-form { + flex: 1; + min-height: 0; + min-width: 0; + overflow-x: hidden; + overflow-y: auto; +} + .workflow-property-empty { margin: 14px; padding: 18px; @@ -30425,18 +30727,36 @@ html[data-theme="dark"] .form-group select { cursor: pointer; } -@media (max-width: 1200px) { +@media (max-width: 1400px) { .workflow-page-content { - grid-template-columns: 240px minmax(0, 1fr); + grid-template-columns: clamp(180px, 14vw, 220px) minmax(0, 1fr); } .workflow-properties { grid-column: 1 / -1; + grid-row: 2; + max-height: min(42vh, 420px); } - .workflow-meta-bar, - .workflow-meta-fields { - display: grid; - grid-template-columns: 1fr; + .workflow-sidebar { + grid-row: 1; + } + + .workflow-main { + grid-row: 1; + } +} + +@media (max-width: 900px) { + .workflow-page-content { + grid-template-columns: minmax(0, 1fr); + } + + .workflow-sidebar { + grid-column: 1 / -1; + } + + .workflow-main { + grid-column: 1 / -1; } } diff --git a/web/static/i18n/en-US.json b/web/static/i18n/en-US.json index c9a577f4..109be1e6 100644 --- a/web/static/i18n/en-US.json +++ b/web/static/i18n/en-US.json @@ -2937,6 +2937,14 @@ "metaName": "Name", "metaDescription": "Description", "metaEnabled": "Enabled", + "metaIdHint": "Cannot be changed after creation; used for API and role binding", + "metaModalTitle": "Workflow details", + "editMeta": "Edit", + "untitled": "Untitled workflow", + "toggleEnabled": "Enable/disable", + "metaEnabledHint": "Disabled workflows cannot be auto-triggered by roles", + "enabledUpdated": "Enabled status updated", + "enabledUpdateFailed": "Failed to update enabled status", "namePlaceholder": "Basic Web scan", "descriptionPlaceholder": "Optional", "connect": "Connect", diff --git a/web/static/i18n/zh-CN.json b/web/static/i18n/zh-CN.json index 871e8c61..040ff776 100644 --- a/web/static/i18n/zh-CN.json +++ b/web/static/i18n/zh-CN.json @@ -2925,6 +2925,14 @@ "metaName": "名称", "metaDescription": "描述", "metaEnabled": "启用", + "metaIdHint": "创建后不可修改,用于 API 与角色绑定", + "metaModalTitle": "流程信息", + "editMeta": "编辑", + "untitled": "未命名流程", + "toggleEnabled": "启用/禁用", + "metaEnabledHint": "禁用后无法被角色自动触发", + "enabledUpdated": "启用状态已更新", + "enabledUpdateFailed": "更新启用状态失败", "namePlaceholder": "基础 Web 扫描", "descriptionPlaceholder": "可选", "connect": "连线", diff --git a/web/static/js/modal.js b/web/static/js/modal.js index 1a8e5dcd..65f65db4 100644 --- a/web/static/js/modal.js +++ b/web/static/js/modal.js @@ -13,6 +13,7 @@ 'agent-md-modal', 'batch-manage-modal', 'create-group-modal', + 'workflow-meta-modal', 'login-overlay', ]); diff --git a/web/static/js/workflows.js b/web/static/js/workflows.js index 767dd011..3192f491 100644 --- a/web/static/js/workflows.js +++ b/web/static/js/workflows.js @@ -45,6 +45,8 @@ const AGENT_MODES = ['eino_single', 'deep', 'plan_execute', 'supervisor']; + const WORKFLOW_EDIT_ICON = ''; + function esc(text) { if (typeof escapeHtml === 'function') return escapeHtml(text == null ? '' : String(text)); return String(text == null ? '' : text) @@ -191,6 +193,19 @@ empty.style.display = cy.nodes().length ? 'none' : 'flex'; } + let workflowResizeObserver = null; + + function setupWorkflowResizeObserver(container) { + if (workflowResizeObserver || typeof ResizeObserver === 'undefined' || !container) return; + workflowResizeObserver = new ResizeObserver(function () { + if (cy) cy.resize(); + }); + const canvasWrap = container.closest('.workflow-canvas-wrap'); + const pageContent = container.closest('.workflow-page-content'); + if (canvasWrap) workflowResizeObserver.observe(canvasWrap); + if (pageContent) workflowResizeObserver.observe(pageContent); + } + function initCy() { const container = document.getElementById('workflow-canvas'); if (!container || typeof cytoscape !== 'function') return; @@ -291,6 +306,7 @@ deleteWorkflowSelection(); } }); + setupWorkflowResizeObserver(container); } async function loadWorkflows(includeDisabled) { @@ -329,6 +345,79 @@ return workflowToolOptions; } + function readWorkflowMetaFromForm() { + const idEl = document.getElementById('workflow-id'); + const nameEl = document.getElementById('workflow-name'); + const descEl = document.getElementById('workflow-description'); + const enabledEl = document.getElementById('workflow-enabled'); + return { + id: idEl ? idEl.value.trim() : '', + name: nameEl ? nameEl.value.trim() : '', + description: descEl ? descEl.value.trim() : '', + enabled: enabledEl ? enabledEl.checked : true + }; + } + + function updateWorkflowCanvasTitle() { + const titleEl = document.getElementById('workflow-canvas-title'); + const subtitleEl = document.getElementById('workflow-canvas-subtitle'); + if (!titleEl) return; + const meta = readWorkflowMetaFromForm(); + const wf = workflows.find(item => item.id === currentWorkflowId); + if (!meta.name && !meta.id) { + titleEl.textContent = _t('workflows.untitled'); + } else { + titleEl.textContent = meta.name || meta.id; + } + titleEl.classList.toggle('is-disabled', !meta.enabled); + titleEl.title = meta.description || ''; + if (subtitleEl) { + const parts = []; + if (meta.id) parts.push(meta.id); + if (wf && wf.version) parts.push(`v${wf.version}`); + parts.push(meta.enabled ? _t('workflows.statusEnabled') : _t('workflows.statusDisabled')); + subtitleEl.textContent = parts.join(' · '); + subtitleEl.hidden = !parts.length; + } + } + + function syncWorkflowMetaIdField(locked, id) { + const idEl = document.getElementById('workflow-id'); + const lockedEl = document.getElementById('workflow-id-locked'); + const displayEl = document.getElementById('workflow-id-display'); + const hintEl = document.querySelector('.workflow-meta-id-hint'); + const idGroup = document.getElementById('workflow-meta-id-group'); + if (!idEl) return; + idEl.value = id || ''; + if (locked) { + idEl.hidden = true; + idEl.disabled = true; + if (lockedEl) lockedEl.hidden = false; + if (displayEl) displayEl.textContent = id || ''; + if (hintEl) hintEl.hidden = true; + if (idGroup) idGroup.classList.add('is-locked'); + } else { + idEl.hidden = false; + idEl.disabled = false; + if (lockedEl) lockedEl.hidden = true; + if (displayEl) displayEl.textContent = ''; + if (hintEl) hintEl.hidden = false; + if (idGroup) idGroup.classList.remove('is-locked'); + } + } + + function syncWorkflowMetaForm(wf) { + const nameEl = document.getElementById('workflow-name'); + const descEl = document.getElementById('workflow-description'); + const enabledEl = document.getElementById('workflow-enabled'); + if (!nameEl || !descEl || !enabledEl) return; + syncWorkflowMetaIdField(!!wf.id, wf.id || ''); + nameEl.value = wf.name || ''; + descEl.value = wf.description || ''; + enabledEl.checked = wf.enabled !== false; + updateWorkflowCanvasTitle(); + } + function renderWorkflowList() { const list = document.getElementById('workflow-list'); if (!list) return; @@ -336,12 +425,28 @@ list.innerHTML = '
' + esc(_t('workflows.emptyList')) + '
'; return; } - list.innerHTML = workflows.map(wf => ` - - `).join(''); + list.innerHTML = workflows.map(wf => { + const encodedId = encodeURIComponent(wf.id); + const isActive = wf.id === currentWorkflowId; + const toggleTitle = esc(_t('workflows.toggleEnabled')); + const editTitle = esc(_t('workflows.editMeta')); + const enabled = wf.enabled !== false; + return ` +
+ +
+ + +
+
+ `; + }).join(''); } function nextNodeId(type) { @@ -372,19 +477,12 @@ } function fillWorkflowForm(wf) { + const data = wf || {}; + syncWorkflowMetaForm(data); + currentWorkflowId = data.id ? data.id : ''; initCy(); - const idEl = document.getElementById('workflow-id'); - const nameEl = document.getElementById('workflow-name'); - const descEl = document.getElementById('workflow-description'); - const enabledEl = document.getElementById('workflow-enabled'); - if (!idEl || !nameEl || !descEl || !enabledEl || !cy) return; - idEl.value = wf.id || ''; - idEl.disabled = !!wf.id; - nameEl.value = wf.name || ''; - descEl.value = wf.description || ''; - enabledEl.checked = wf.enabled !== false; - currentWorkflowId = wf.id || ''; - const graph = parseGraph(wf.graph_json || wf.graph || defaultGraph()); + if (!cy) return; + const graph = parseGraph(data.graph_json || data.graph || defaultGraph()); resetSequences(graph); cy.elements().remove(); cy.add(graphToElements(graph)); @@ -411,8 +509,6 @@ if (deleteBtn) deleteBtn.hidden = true; return; } - cy.elements().unselect(); - selectedElement.select(); empty.hidden = true; form.hidden = false; if (title) title.textContent = selectedElement.isNode() ? _t('workflows.nodeProperties') : _t('workflows.edgeProperties'); @@ -420,6 +516,8 @@ deleteBtn.hidden = false; deleteBtn.textContent = selectedElement.isNode() ? _t('workflows.deleteNode') : _t('workflows.deleteEdge'); } + cy.elements().unselect(); + selectedElement.select(); const typeWrap = document.getElementById('workflow-prop-type-wrap'); const label = document.getElementById('workflow-prop-label'); const type = document.getElementById('workflow-prop-type'); @@ -699,12 +797,18 @@ if (list) list.innerHTML = '
' + esc(_t('common.loading')) + '
'; try { await loadWorkflows(true); - renderWorkflowList(); - if (!currentWorkflowId && workflows.length) { + if (currentWorkflowId) { + const wf = workflows.find(item => item.id === currentWorkflowId); + if (wf) { + syncWorkflowMetaForm(wf); + } + } else if (workflows.length) { fillWorkflowForm(workflows[0]); - } else if (!workflows.length) { + } else { newWorkflowDraft(); + return; } + renderWorkflowList(); } catch (error) { if (list) list.innerHTML = `
${esc(error.message)}
`; if (typeof showNotification === 'function') showNotification(error.message, 'error'); @@ -712,6 +816,7 @@ }; window.newWorkflowDraft = function () { + currentWorkflowId = ''; fillWorkflowForm({ id: '', name: '', @@ -719,6 +824,8 @@ enabled: true, graph_json: defaultGraph() }); + syncWorkflowMetaIdField(false, ''); + openWorkflowMetaModal(); }; window.selectWorkflow = function (id) { @@ -726,6 +833,100 @@ if (wf) fillWorkflowForm(wf); }; + window.openWorkflowMetaModal = function () { + const nameEl = document.getElementById('workflow-name'); + const idEl = document.getElementById('workflow-id'); + if (currentWorkflowId) { + syncWorkflowMetaIdField(true, currentWorkflowId); + } else { + syncWorkflowMetaIdField(false, idEl ? idEl.value.trim() : ''); + } + if (typeof openAppModal === 'function') { + openAppModal('workflow-meta-modal', { + focusEl: currentWorkflowId ? nameEl : (idEl && !idEl.hidden ? idEl : nameEl) + }); + } + }; + + window.closeWorkflowMetaModal = function () { + if (typeof closeAppModal === 'function') { + closeAppModal('workflow-meta-modal'); + } + }; + + window.applyWorkflowMetaModal = function () { + const meta = readWorkflowMetaFromForm(); + if (!meta.id || !meta.name) { + if (typeof showNotification === 'function') { + showNotification(_t('workflows.idNameRequired'), 'error'); + } + return; + } + updateWorkflowCanvasTitle(); + renderWorkflowList(); + closeWorkflowMetaModal(); + }; + + window.editWorkflowFromList = function (id) { + if (id !== currentWorkflowId) { + selectWorkflow(id); + } + openWorkflowMetaModal(); + }; + + window.toggleWorkflowEnabled = async function (id, enabled) { + const wf = workflows.find(item => item.id === id); + if (!wf) return; + const previous = wf.enabled !== false; + wf.enabled = enabled; + if (id === currentWorkflowId) { + const enabledEl = document.getElementById('workflow-enabled'); + if (enabledEl) enabledEl.checked = enabled; + updateWorkflowCanvasTitle(); + } + renderWorkflowList(); + let graph = defaultGraph(); + if (id === currentWorkflowId && cy) { + graph = elementsToGraph(); + } else { + graph = parseGraph(wf.graph_json || wf.graph || defaultGraph()); + } + try { + const response = await apiFetch(`/api/workflows/${encodeURIComponent(id)}`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + id: wf.id, + name: wf.name, + description: wf.description || '', + enabled, + graph + }) + }); + if (!response.ok) { + const err = await response.json().catch(() => ({})); + throw new Error(err.error || _t('workflows.enabledUpdateFailed')); + } + if (typeof showNotification === 'function') { + showNotification(_t('workflows.enabledUpdated'), 'success'); + } + if (typeof loadWorkflowOptionsForRoleModal === 'function') { + await loadWorkflowOptionsForRoleModal(); + } + } catch (error) { + wf.enabled = previous; + if (id === currentWorkflowId) { + const enabledEl = document.getElementById('workflow-enabled'); + if (enabledEl) enabledEl.checked = previous; + updateWorkflowCanvasTitle(); + } + renderWorkflowList(); + if (typeof showNotification === 'function') { + showNotification(error.message || _t('workflows.enabledUpdateFailed'), 'error'); + } + } + }; + function validateWorkflowGraph(graph) { const errors = []; const nodes = graph.nodes || []; @@ -772,12 +973,12 @@ window.saveWorkflowDraft = async function () { initCy(); - const id = document.getElementById('workflow-id').value.trim(); - const name = document.getElementById('workflow-name').value.trim(); - const description = document.getElementById('workflow-description').value.trim(); - const enabled = document.getElementById('workflow-enabled').checked; - if (!id || !name) { - showNotification(_t('workflows.idNameRequired'), 'error'); + const meta = readWorkflowMetaFromForm(); + if (!meta.id || !meta.name) { + if (typeof showNotification === 'function') { + showNotification(_t('workflows.idNameRequired'), 'error'); + } + openWorkflowMetaModal(); return; } const graph = elementsToGraph(); @@ -791,7 +992,13 @@ const response = await apiFetch(url, { method, headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ id, name, description, enabled, graph }) + body: JSON.stringify({ + id: meta.id, + name: meta.name, + description: meta.description, + enabled: meta.enabled, + graph + }) }); if (!response.ok) { const err = await response.json().catch(() => ({})); @@ -799,7 +1006,9 @@ return; } const data = await response.json(); - currentWorkflowId = data.workflow && data.workflow.id ? data.workflow.id : id; + currentWorkflowId = data.workflow && data.workflow.id ? data.workflow.id : meta.id; + syncWorkflowMetaIdField(true, currentWorkflowId); + closeWorkflowMetaModal(); showNotification(_t('workflows.saved'), 'success'); await refreshWorkflows(); if (typeof loadWorkflowOptionsForRoleModal === 'function') { @@ -808,7 +1017,8 @@ }; window.deleteCurrentWorkflow = async function () { - const id = currentWorkflowId || document.getElementById('workflow-id').value.trim(); + const meta = readWorkflowMetaFromForm(); + const id = currentWorkflowId || meta.id; if (!id) { showNotification(_t('workflows.selectToDelete'), 'warning'); return; @@ -984,6 +1194,7 @@ connectBtn.textContent = connectMode ? _t('workflows.connecting') : _t('workflows.connect'); } refreshCanvasLabels(); + updateWorkflowCanvasTitle(); renderWorkflowList(); if (selectedElement && selectedElement.length) { selectWorkflowElement(selectedElement); diff --git a/web/templates/index.html b/web/templates/index.html index 18307c0a..9b0e900a 100644 --- a/web/templates/index.html +++ b/web/templates/index.html @@ -2583,11 +2583,11 @@
-
- - - - +
+
+

未命名流程

+ +
@@ -4037,6 +4037,44 @@ window.ELK = elk; } + +