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 @@