v0.11.2
This commit is contained in:
+3
-1
@@ -329,7 +329,9 @@ async function api(path, options = {}) {
|
||||
if (!response.ok) {
|
||||
let message = tr('error.http', { status: response.status });
|
||||
try { message = (await response.json()).error || message; } catch (_) { }
|
||||
throw new Error(message);
|
||||
const error = new Error(message);
|
||||
error.status = response.status;
|
||||
throw error;
|
||||
}
|
||||
if (response.status === 204) return null;
|
||||
return response.json();
|
||||
|
||||
+32
-11
@@ -97,17 +97,17 @@ function flowDefaultConfig(kind) {
|
||||
function renderFlows() {
|
||||
const host = $('#flowList'); if (!host) return;
|
||||
const count = $('#flowListCount'); if (count) count.textContent = tr('flow.listCount', { count: app.flows.length });
|
||||
host.innerHTML = app.flows.length ? app.flows.map(flow => `<article class="list-card flow-card ${flow.enabled ? '' : 'is-disabled'}">
|
||||
<div class="list-card-head"><div><h3>${esc(flow.name)}</h3><p>${esc(flow.description || tr('flow.defaultDescription'))}</p></div><button type="button" class="zone-enable-toggle ${flow.enabled ? 'active' : ''}" data-action="toggle-flow-enabled" data-id="${esc(flow.id)}" data-value="${flow.enabled ? 'false' : 'true'}" aria-label="${esc(tr(flow.enabled ? 'flow.disable' : 'flow.enable'))}" title="${esc(tr('flow.quickToggleHint'))}"><span>${flow.enabled ? '✓' : '○'}</span>${esc(flow.enabled ? tr('common.enabled') : tr('common.disabled'))}</button></div>
|
||||
host.innerHTML = app.flows.length ? app.flows.map(flow => `<article class="list-card flow-card ${flow.enabled && !flow.draft ? '' : 'is-disabled'} ${flow.draft ? 'is-draft' : ''}">
|
||||
<div class="list-card-head"><div><h3>${esc(flow.name)}${flow.draft ? ` <span class="badge flow-draft-badge">${esc(tr('flow.draft'))}</span>` : ''}</h3><p>${esc(flow.description || tr('flow.defaultDescription'))}</p></div>${flow.draft ? `<button type="button" class="zone-enable-toggle" disabled title="${esc(tr('flow.draftDisabledHint'))}"><span>✎</span>${esc(tr('common.disabled'))}</button>` : `<button type="button" class="zone-enable-toggle ${flow.enabled ? 'active' : ''}" data-action="toggle-flow-enabled" data-id="${esc(flow.id)}" data-value="${flow.enabled ? 'false' : 'true'}" aria-label="${esc(tr(flow.enabled ? 'flow.disable' : 'flow.enable'))}" title="${esc(tr('flow.quickToggleHint'))}"><span>${flow.enabled ? '✓' : '○'}</span>${esc(flow.enabled ? tr('common.enabled') : tr('common.disabled'))}</button>`}</div>
|
||||
<div class="card-stats"><div class="card-stat"><small>${esc(tr('flow.blocks'))}</small><strong>${flow.nodes?.length || 0}</strong></div><div class="card-stat"><small>${esc(tr('nav.schedules'))}</small><strong>${flow.compiled_schedule_ids?.length || 0}</strong></div><div class="card-stat"><small>${esc(tr('nav.automations'))}</small><strong>${flow.compiled_automation_ids?.length || 0}</strong></div></div>
|
||||
<div class="card-footer"><small>${esc(tr('flow.compileCount', { schedules: flow.compiled_schedule_ids?.length || 0, automations: flow.compiled_automation_ids?.length || 0 }))}</small><div class="card-menu"><button data-action="edit-flow" data-id="${esc(flow.id)}">${esc(tr('flow.openEditor'))}</button><button class="danger" data-action="delete-flow" data-id="${esc(flow.id)}">${esc(tr('actions.delete'))}</button></div></div>
|
||||
<div class="card-footer"><small>${esc(flow.draft ? tr('flow.draftNoExecution') : tr('flow.compileCount', { schedules: flow.compiled_schedule_ids?.length || 0, automations: flow.compiled_automation_ids?.length || 0 }))}</small><div class="card-menu"><button data-action="edit-flow" data-id="${esc(flow.id)}">${esc(tr('flow.openEditor'))}</button><button class="danger" data-action="delete-flow" data-id="${esc(flow.id)}">${esc(tr('actions.delete'))}</button></div></div>
|
||||
</article>`).join('') : `<div class="empty"><strong>${esc(tr('flow.emptyTitle'))}</strong>${esc(tr('flow.emptyText'))}</div>`;
|
||||
}
|
||||
|
||||
|
||||
function flowDraftFrom(flow) {
|
||||
return flow ? JSON.parse(JSON.stringify(flow)) : {
|
||||
id: '', revision: 0, name: tr('flow.newDefaultName'), enabled: true, description: '', nodes: [], edges: [], summary: '', compiled_schedule_ids: [], compiled_automation_ids: [],
|
||||
id: '', revision: 0, name: tr('flow.newDefaultName'), enabled: true, draft: false, description: '', nodes: [], edges: [], summary: '', compiled_schedule_ids: [], compiled_automation_ids: [],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -326,7 +326,8 @@ function renderFlowEditor() {
|
||||
const selectionCount = $('#flowSelectionCount'); if (selectionCount) selectionCount.textContent = (app.flowSelectedNodeIds || []).length ? tr('flow.selectedCount', { count:(app.flowSelectedNodeIds || []).length }) : '';
|
||||
renderFlowEdges(); renderFlowInspector(); renderFlowInterpretation(); renderFlowRuntimeInfo(); refreshFlowSharedInputCurrentValues();
|
||||
const status = $('#flowCompileStatus');
|
||||
status.textContent = tr('flow.compileCount', { schedules: draft.compiled_schedule_ids?.length || 0, automations: draft.compiled_automation_ids?.length || 0 });
|
||||
status.textContent = draft.draft ? tr('flow.draftStatus') : tr('flow.compileCount', { schedules: draft.compiled_schedule_ids?.length || 0, automations: draft.compiled_automation_ids?.length || 0 });
|
||||
status.classList.toggle('flow-draft-badge', draft.draft === true);
|
||||
}
|
||||
|
||||
function renderFlowEdges() {
|
||||
@@ -527,6 +528,7 @@ function flowSourcePayload() {
|
||||
return {
|
||||
name: ($('#flowName')?.value || app.flowDraft.name || '').trim(),
|
||||
enabled: $('#flowEnabled')?.checked !== false,
|
||||
draft: app.flowDraft.draft === true,
|
||||
description: app.flowDraft.description || '',
|
||||
nodes: app.flowDraft.nodes || [], edges: app.flowDraft.edges || [],
|
||||
};
|
||||
@@ -898,16 +900,34 @@ async function openFlowLogs() {
|
||||
} catch (error) { $('#flowTestResults').innerHTML = `<div class="empty compact"><strong>${esc(tr('flow.logsFailed'))}</strong><span>${esc(error.message)}</span></div>`; }
|
||||
}
|
||||
|
||||
async function persistFlowDraft(body) {
|
||||
return api(app.flowDraft.id ? `/api/flows/${encodeURIComponent(app.flowDraft.id)}` : '/api/flows', { method: app.flowDraft.id ? 'PUT' : 'POST', body });
|
||||
}
|
||||
|
||||
async function applySavedFlow(saved, messageKey) {
|
||||
const index = app.flows.findIndex(flow => flow.id === saved.id); if (index >= 0) app.flows[index] = saved; else app.flows.push(saved);
|
||||
app.flowDraft = flowDraftFrom(saved); app.flowDirty = false;
|
||||
$('#flowEnabled').checked = saved.enabled === true;
|
||||
renderFlows(); renderFlowEditor(); updateBrowserUrl(`/flows/${saved.id}`, true); await loadBootstrap(); toast(tr(messageKey));
|
||||
}
|
||||
|
||||
async function saveFlow() {
|
||||
if (!app.flowDraft) return;
|
||||
const name = $('#flowName').value.trim(); if (!name) return toast(tr('flow.nameRequired'), true);
|
||||
const body = { name, enabled: $('#flowEnabled').checked, description: app.flowDraft.description || '', nodes: app.flowDraft.nodes, edges: app.flowDraft.edges };
|
||||
const body = { name, enabled: $('#flowEnabled').checked, draft: false, description: app.flowDraft.description || '', nodes: app.flowDraft.nodes, edges: app.flowDraft.edges };
|
||||
if (app.flowDraft.id) body.expected_revision = Number(app.flowDraft.revision || 0);
|
||||
try {
|
||||
const saved = await api(app.flowDraft.id ? `/api/flows/${encodeURIComponent(app.flowDraft.id)}` : '/api/flows', { method: app.flowDraft.id ? 'PUT' : 'POST', body });
|
||||
const index = app.flows.findIndex(flow => flow.id === saved.id); if (index >= 0) app.flows[index] = saved; else app.flows.push(saved);
|
||||
app.flowDraft = flowDraftFrom(saved); app.flowDirty = false; renderFlows(); renderFlowEditor(); updateBrowserUrl(`/flows/${saved.id}`, true); await loadBootstrap(); toast(tr('flow.saved'));
|
||||
} catch (error) { toast(error.message, true); }
|
||||
const saved = await persistFlowDraft(body);
|
||||
await applySavedFlow(saved, 'flow.saved');
|
||||
} catch (error) {
|
||||
if (error.status !== 400) return toast(error.message, true);
|
||||
const saveDraft = confirm(tr('flow.saveAsDraftConfirm', { reason:error.message }));
|
||||
if (!saveDraft) return toast(error.message, true);
|
||||
try {
|
||||
const saved = await persistFlowDraft({ ...body, enabled:false, draft:true });
|
||||
await applySavedFlow(saved, 'flow.savedAsDraft');
|
||||
} catch (draftError) { toast(draftError.message, true); }
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteFlow(id) {
|
||||
@@ -918,9 +938,10 @@ async function deleteFlow(id) {
|
||||
|
||||
async function toggleFlowEnabled(id, enabled) {
|
||||
const flow = app.flows.find(item => item.id === id); if (!flow) return;
|
||||
if (flow.draft) return toast(tr('flow.draftCannotEnable'), true);
|
||||
const toggle = $(`[data-action="toggle-flow-enabled"][data-id="${CSS.escape(id)}"]`); if (toggle?.disabled) return;
|
||||
if (toggle) toggle.disabled = true;
|
||||
const body = { name:flow.name, enabled, description:flow.description || '', nodes:flow.nodes || [], edges:flow.edges || [], expected_revision:Number(flow.revision || 0) };
|
||||
const body = { name:flow.name, enabled, draft:false, description:flow.description || '', nodes:flow.nodes || [], edges:flow.edges || [], expected_revision:Number(flow.revision || 0) };
|
||||
try {
|
||||
const saved = await api(`/api/flows/${encodeURIComponent(id)}`, { method:'PUT', body });
|
||||
const index = app.flows.findIndex(item => item.id === id); if (index >= 0) app.flows[index] = saved;
|
||||
|
||||
+5
-1
@@ -6180,7 +6180,11 @@ body.flow-editor-open { overflow:hidden; }
|
||||
.flow-editor-bar { min-height:76px; display:flex; align-items:center; justify-content:space-between; gap:20px; padding:10px 18px; border-bottom:1px solid var(--line); background:var(--surface); }
|
||||
.flow-editor-title { display:flex; align-items:center; gap:12px; min-width:0; }
|
||||
.flow-editor-title > div { display:grid; gap:2px; min-width:0; }
|
||||
.flow-editor-title input { border:0; background:transparent; color:var(--text); font-weight:750; font-size:1.12rem; padding:2px 0; min-width:280px; outline:none; }
|
||||
.flow-editor-title input { border:1px solid var(--line-strong); background:var(--input-bg); color:var(--text); font-weight:750; font-size:1.12rem; padding:7px 10px; min-width:280px; outline:none; border-radius:9px; box-shadow:inset 0 0 0 1px transparent; transition:border-color .15s ease,box-shadow .15s ease,background .15s ease; }
|
||||
.flow-editor-title input:hover { border-color:var(--accent-border); background:var(--surface-2); }
|
||||
.flow-editor-title input:focus { border-color:var(--accent); box-shadow:0 0 0 3px var(--accent-soft); background:var(--input-bg); }
|
||||
.flow-draft-badge { border-color:var(--orange); background:color-mix(in srgb,var(--orange) 14%,var(--surface)); color:var(--text); }
|
||||
.flow-card.is-draft { border-style:dashed; }
|
||||
.flow-editor-actions { display:flex; align-items:center; gap:10px; }
|
||||
.flow-enabled { display:flex; align-items:center; gap:8px; color:var(--muted); }
|
||||
.flow-editor-body { flex:1; min-height:0; display:grid; grid-template-columns:220px minmax(420px,1fr) 300px; }
|
||||
|
||||
Reference in New Issue
Block a user