mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-07-16 00:47:29 +02:00
Add files via upload
This commit is contained in:
+1021
-57
File diff suppressed because it is too large
Load Diff
@@ -3131,18 +3131,35 @@
|
||||
"enabledUpdateFailed": "Failed to update enabled status",
|
||||
"namePlaceholder": "Basic Web scan",
|
||||
"descriptionPlaceholder": "Optional",
|
||||
"toolbarLabel": "Workflow editing tools",
|
||||
"canvasTools": "Canvas tools",
|
||||
"moreActions": "More",
|
||||
"deleteWorkflow": "Delete workflow",
|
||||
"package": {
|
||||
"importLocal": "Import local package",
|
||||
"export": "Export",
|
||||
"title": "Import local package",
|
||||
"preflightHint": "Preflight does not modify local workflows",
|
||||
"preflightHint": "Review package contents and conflicts before you approve the import",
|
||||
"safeBadge": "Safe preflight",
|
||||
"stepInspection": "Upload and preflight",
|
||||
"stepInspectionHint": "Validate format, content, and version",
|
||||
"stepImport": "Resolve conflict and import",
|
||||
"stepImportHint": "Choose a strategy and finish importing",
|
||||
"stepsLabel": "Import steps",
|
||||
"packageEyebrow": "Local workflow package",
|
||||
"packageTitle": "Choose a package to import",
|
||||
"decisionEyebrow": "Import strategy",
|
||||
"pending": "Pending",
|
||||
"ready": "Ready",
|
||||
"footerNote": "Existing workflows stay unchanged until you confirm",
|
||||
"selectFile": "Choose local package",
|
||||
"dropTitle": "Drop a local package here",
|
||||
"dropOr": "or",
|
||||
"removeFile": "Remove file",
|
||||
"noFile": "No file selected",
|
||||
"fileHint": "One .csapkg.zip package only, up to 10 MiB.",
|
||||
"inspect": "Upload and preflight",
|
||||
"inspecting": "Running safe preflight…",
|
||||
"resolutionTitle": "Resolution",
|
||||
"confirmImport": "Confirm import",
|
||||
"overwriteTitle": "Overwrite local workflow?",
|
||||
@@ -3180,6 +3197,8 @@
|
||||
"none": "No local workflow with the same ID was found. A new workflow can be created."
|
||||
},
|
||||
"summary": {
|
||||
"title": "Preflight result",
|
||||
"passed": "Checks passed",
|
||||
"workflowName": "Workflow name",
|
||||
"unnamedWorkflow": "Untitled workflow",
|
||||
"sourceId": "Source workflow ID",
|
||||
@@ -3191,7 +3210,9 @@
|
||||
},
|
||||
"resolution": {
|
||||
"needInspection": "Choose a local package and complete preflight first.",
|
||||
"createTitle": "Safe to create",
|
||||
"createHint": "Import will create a new local workflow.",
|
||||
"identicalTitle": "No duplicate needed",
|
||||
"identicalHint": "The same content already exists. No duplicate import is needed.",
|
||||
"keepExisting": "Keep local version",
|
||||
"keepExistingHint": "Do not change the current local workflow; the import record is retained.",
|
||||
|
||||
@@ -3119,18 +3119,35 @@
|
||||
"enabledUpdateFailed": "更新启用状态失败",
|
||||
"namePlaceholder": "基础 Web 扫描",
|
||||
"descriptionPlaceholder": "可选",
|
||||
"toolbarLabel": "流程编辑工具",
|
||||
"canvasTools": "画布工具",
|
||||
"moreActions": "更多",
|
||||
"deleteWorkflow": "删除流程",
|
||||
"package": {
|
||||
"importLocal": "导入本地包",
|
||||
"export": "导出",
|
||||
"title": "导入本地包",
|
||||
"preflightHint": "预检不会修改本地工作流",
|
||||
"preflightHint": "先检查包内容与冲突,再由你确认是否导入",
|
||||
"safeBadge": "安全预检",
|
||||
"stepInspection": "上传与预检",
|
||||
"stepInspectionHint": "校验格式、内容与版本",
|
||||
"stepImport": "处理冲突与导入",
|
||||
"stepImportHint": "确认处理方式并完成导入",
|
||||
"stepsLabel": "导入步骤",
|
||||
"packageEyebrow": "本地工作流包",
|
||||
"packageTitle": "选择需要导入的文件",
|
||||
"decisionEyebrow": "导入策略",
|
||||
"pending": "待预检",
|
||||
"ready": "可处理",
|
||||
"footerNote": "导入前不会改动现有工作流",
|
||||
"selectFile": "选择本地包",
|
||||
"dropTitle": "拖拽本地包到这里",
|
||||
"dropOr": "或",
|
||||
"removeFile": "移除文件",
|
||||
"noFile": "未选择文件",
|
||||
"fileHint": "仅支持单个 .csapkg.zip,文件不得超过 10 MiB。",
|
||||
"inspect": "上传并预检",
|
||||
"inspecting": "正在安全预检…",
|
||||
"resolutionTitle": "处理方式",
|
||||
"confirmImport": "确认导入",
|
||||
"overwriteTitle": "确认覆盖本地工作流?",
|
||||
@@ -3168,6 +3185,8 @@
|
||||
"none": "未发现同 ID 的本地工作流,可创建导入。"
|
||||
},
|
||||
"summary": {
|
||||
"title": "预检结果",
|
||||
"passed": "校验通过",
|
||||
"workflowName": "工作流名称",
|
||||
"unnamedWorkflow": "未命名工作流",
|
||||
"sourceId": "源工作流 ID",
|
||||
@@ -3179,7 +3198,9 @@
|
||||
},
|
||||
"resolution": {
|
||||
"needInspection": "请先选择本地包并完成预检。",
|
||||
"createTitle": "可以安全创建",
|
||||
"createHint": "导入后会创建一个新的本地工作流。",
|
||||
"identicalTitle": "无需重复导入",
|
||||
"identicalHint": "内容已经存在,无需重复导入。",
|
||||
"keepExisting": "保留本地版本",
|
||||
"keepExistingHint": "不修改当前本地工作流;导入记录会保留。",
|
||||
|
||||
@@ -12,6 +12,18 @@ test('图编排提供导入、导出和覆盖确认容器', () => {
|
||||
assert.equal(zh.workflows.package.importLocal, '导入本地包');
|
||||
});
|
||||
|
||||
test('本地包上传支持拖拽、键盘选择和选中文件反馈', () => {
|
||||
const html = fs.readFileSync('web/templates/index.html', 'utf8');
|
||||
const workflows = fs.readFileSync('web/static/js/workflows.js', 'utf8');
|
||||
const css = fs.readFileSync('web/static/css/style.css', 'utf8');
|
||||
assert.match(html, /id="workflow-package-dropzone"[\s\S]*?ondrop="onWorkflowPackageDrop\(event\)"/);
|
||||
assert.match(html, /onkeydown="onWorkflowPackageDropzoneKeydown\(event\)"/);
|
||||
assert.match(html, /id="workflow-package-selected-file"/);
|
||||
assert.match(workflows, /window\.onWorkflowPackageDrop = function \(event\)/);
|
||||
assert.match(workflows, /endsWith\('\.csapkg\.zip'\)/);
|
||||
assert.match(css, /\.workflow-package-dropzone\.is-dragging/);
|
||||
});
|
||||
|
||||
test('工作流脚本调用包契约的全部端点与冲突错误码', () => {
|
||||
const workflows = fs.readFileSync('web/static/js/workflows.js', 'utf8');
|
||||
const client = fs.readFileSync('web/static/js/workflow-package-client.js', 'utf8');
|
||||
|
||||
+149
-24
@@ -34,7 +34,8 @@
|
||||
newWorkflowId: '',
|
||||
riskChoicesVisible: false,
|
||||
idempotencyKey: '',
|
||||
requestSignature: ''
|
||||
requestSignature: '',
|
||||
dragDepth: 0
|
||||
};
|
||||
const WORKFLOW_PACKAGE_INSPECTION_STORAGE_KEY = 'csai.workflow-package.inspection-id';
|
||||
const WORKFLOW_PACKAGE_IMPORT_STORAGE_KEY = 'csai.workflow-package.import-id';
|
||||
@@ -826,6 +827,7 @@
|
||||
const toggleTitle = esc(_t('workflows.toggleEnabled'));
|
||||
const editTitle = esc(_t('workflows.editMeta'));
|
||||
const enabled = wf.enabled !== false;
|
||||
const statusText = esc(_t(enabled ? 'workflows.statusEnabled' : 'workflows.statusDisabled'));
|
||||
return `
|
||||
<div class="workflow-list-item ${isActive ? 'is-active' : ''}">
|
||||
<button type="button" class="workflow-list-main" onclick="selectWorkflow(decodeURIComponent('${encodedId}'))">
|
||||
@@ -833,10 +835,10 @@
|
||||
<span class="workflow-list-meta">${esc(wf.id)} · v${wf.version || 1}</span>
|
||||
</button>
|
||||
<div class="workflow-list-actions">
|
||||
<label class="workflow-switch" title="${toggleTitle}" onclick="event.stopPropagation()">
|
||||
<input type="checkbox" ${enabled ? 'checked' : ''} aria-label="${toggleTitle}" onchange="toggleWorkflowEnabled(decodeURIComponent('${encodedId}'), this.checked)">
|
||||
<span class="workflow-switch-slider" aria-hidden="true"></span>
|
||||
</label>
|
||||
<button type="button" class="workflow-status-toggle ${enabled ? 'is-enabled' : 'is-disabled'}" title="${toggleTitle}" aria-label="${toggleTitle}" aria-pressed="${enabled ? 'true' : 'false'}" onclick="event.stopPropagation(); toggleWorkflowEnabled(decodeURIComponent('${encodedId}'), ${enabled ? 'false' : 'true'})">
|
||||
<span class="workflow-status-dot" aria-hidden="true"></span>
|
||||
<span>${statusText}</span>
|
||||
</button>
|
||||
<button type="button" class="btn-icon workflow-list-edit" title="${editTitle}" aria-label="${editTitle}" onclick="event.stopPropagation(); editWorkflowFromList(decodeURIComponent('${encodedId}'))">${WORKFLOW_EDIT_ICON}</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1770,13 +1772,20 @@
|
||||
const btn = document.getElementById('workflow-connect-btn');
|
||||
if (btn) {
|
||||
btn.classList.toggle('active', connectMode);
|
||||
btn.textContent = connectMode ? _t('workflows.connecting') : _t('workflows.connect');
|
||||
btn.setAttribute('aria-pressed', connectMode ? 'true' : 'false');
|
||||
const label = btn.querySelector('.workflow-toolbar-label');
|
||||
if (label) label.textContent = connectMode ? _t('workflows.connecting') : _t('workflows.connect');
|
||||
}
|
||||
if (typeof showNotification === 'function') {
|
||||
showNotification(connectMode ? _t('workflows.connectModeOn') : _t('workflows.connectModeOff'), 'info');
|
||||
}
|
||||
};
|
||||
|
||||
window.closeWorkflowMoreActions = function () {
|
||||
const menu = document.getElementById('workflow-more-actions');
|
||||
if (menu) menu.removeAttribute('open');
|
||||
};
|
||||
|
||||
window.deleteWorkflowSelection = function () {
|
||||
if (!cy) return;
|
||||
const selected = selectedElement && selectedElement.length ? selectedElement : cy.$(':selected');
|
||||
@@ -1907,16 +1916,71 @@
|
||||
return document.getElementById('workflow-package-submit-btn');
|
||||
}
|
||||
|
||||
function workflowPackageFormatFileSize(bytes) {
|
||||
const size = Number(bytes) || 0;
|
||||
if (size < 1024) return `${size} B`;
|
||||
if (size < 1024 * 1024) return `${(size / 1024).toFixed(size < 10 * 1024 ? 1 : 0)} KiB`;
|
||||
return `${(size / (1024 * 1024)).toFixed(1)} MiB`;
|
||||
}
|
||||
|
||||
function workflowPackageUpdateFileUI() {
|
||||
const file = workflowPackageState.file;
|
||||
const selected = document.getElementById('workflow-package-selected-file');
|
||||
const name = document.getElementById('workflow-package-file-name');
|
||||
const size = document.getElementById('workflow-package-file-size');
|
||||
const inspect = document.getElementById('workflow-package-inspect-btn');
|
||||
if (selected) selected.hidden = !file;
|
||||
if (name) name.textContent = file ? file.name : '';
|
||||
if (size) size.textContent = file ? workflowPackageFormatFileSize(file.size) : '';
|
||||
if (inspect) inspect.disabled = !file;
|
||||
}
|
||||
|
||||
function workflowPackageSetDragging(active) {
|
||||
const dropzone = document.getElementById('workflow-package-dropzone');
|
||||
if (dropzone) dropzone.classList.toggle('is-dragging', active);
|
||||
}
|
||||
|
||||
function workflowPackageSelectFile(file) {
|
||||
resetWorkflowPackageImport();
|
||||
if (!file) {
|
||||
renderWorkflowPackageResolution();
|
||||
workflowPackageSetStep('inspection');
|
||||
return;
|
||||
}
|
||||
if (file.size > 10 * 1024 * 1024) {
|
||||
displayWorkflowPackageError({ code: 'WFPKG_FILE_TOO_LARGE' });
|
||||
workflowPackageSetStep('inspection');
|
||||
return;
|
||||
}
|
||||
if (!String(file.name || '').toLowerCase().endsWith('.csapkg.zip')) {
|
||||
displayWorkflowPackageError({ code: 'WFPKG_UNSUPPORTED_FORMAT' });
|
||||
workflowPackageSetStep('inspection');
|
||||
return;
|
||||
}
|
||||
workflowPackageState.file = file;
|
||||
workflowPackageUpdateFileUI();
|
||||
renderWorkflowPackageResolution();
|
||||
workflowPackageSetStep('inspection');
|
||||
}
|
||||
|
||||
function workflowPackageSetStep(step) {
|
||||
const inspectionStep = document.getElementById('workflow-package-step-inspection');
|
||||
const importStep = document.getElementById('workflow-package-step-import');
|
||||
if (inspectionStep) inspectionStep.classList.toggle('is-active', step === 'inspection');
|
||||
if (importStep) importStep.classList.toggle('is-active', step === 'import');
|
||||
if (inspectionStep) inspectionStep.classList.toggle('is-complete', step === 'import');
|
||||
const decisionStatus = document.querySelector('#workflow-package-import-modal .workflow-package-decision-status');
|
||||
if (decisionStatus) {
|
||||
decisionStatus.classList.toggle('is-ready', step === 'import');
|
||||
decisionStatus.textContent = step === 'import'
|
||||
? workflowPackageText('workflows.package.ready', '可处理')
|
||||
: workflowPackageText('workflows.package.pending', '待预检');
|
||||
}
|
||||
}
|
||||
|
||||
function workflowPackageSetStatus(target, message, type) {
|
||||
if (!target) return;
|
||||
target.innerHTML = `<div class="workflow-package-status${type ? ' is-' + esc(type) : ''}">${esc(message)}</div>`;
|
||||
target.innerHTML = `<div class="workflow-package-status${type ? ' is-' + esc(type) : ''}"><span class="workflow-package-status-icon" aria-hidden="true"></span><span>${esc(message)}</span></div>`;
|
||||
}
|
||||
|
||||
function workflowPackageErrorMessage(error) {
|
||||
@@ -1975,9 +2039,9 @@
|
||||
[workflowPackageText('workflows.package.summary.expiresAt', '预检有效期'), inspection.expires_at || '—']
|
||||
];
|
||||
if (!target) return;
|
||||
target.innerHTML = `<div class="workflow-package-status is-${conflictCopy.type}">${esc(conflictCopy.message)}</div>` + rows.map(function (row) {
|
||||
target.innerHTML = `<div class="workflow-package-summary-heading"><span>${esc(workflowPackageText('workflows.package.summary.title', '预检结果'))}</span><b>${esc(workflowPackageText('workflows.package.summary.passed', '校验通过'))}</b></div><div class="workflow-package-status is-${conflictCopy.type}"><span class="workflow-package-status-icon" aria-hidden="true"></span><span>${esc(conflictCopy.message)}</span></div><div class="workflow-package-summary-grid">` + rows.map(function (row) {
|
||||
return `<div class="workflow-package-summary-row"><span>${esc(row[0])}</span><code title="${esc(row[1])}">${esc(row[1])}</code></div>`;
|
||||
}).join('');
|
||||
}).join('') + '</div>';
|
||||
}
|
||||
|
||||
function workflowPackageResolutionCard(action, title, description, selected) {
|
||||
@@ -2004,11 +2068,11 @@
|
||||
}
|
||||
const action = workflowPackageState.resolutionAction;
|
||||
let html = '';
|
||||
if (message) html += `<div class="workflow-package-status${type ? ' is-' + esc(type) : ''}">${esc(message)}</div>`;
|
||||
if (message) html += `<div class="workflow-package-status${type ? ' is-' + esc(type) : ''}"><span class="workflow-package-status-icon" aria-hidden="true"></span><span>${esc(message)}</span></div>`;
|
||||
if (conflictState === 'none') {
|
||||
html += `<div class="workflow-package-status is-success">${esc(workflowPackageText('workflows.package.resolution.createHint', '导入后会创建一个新的本地工作流。'))}</div>`;
|
||||
html += `<div class="workflow-package-resolution-hero is-success"><span class="workflow-package-resolution-icon" aria-hidden="true">✓</span><div><strong>${esc(workflowPackageText('workflows.package.resolution.createTitle', '可以安全创建'))}</strong><small>${esc(workflowPackageText('workflows.package.resolution.createHint', '导入后会创建一个新的本地工作流。'))}</small></div></div>`;
|
||||
} else if (conflictState === 'identical') {
|
||||
html += `<div class="workflow-package-status is-success">${esc(workflowPackageText('workflows.package.resolution.identicalHint', '内容已经存在,无需重复导入。'))}</div>`;
|
||||
html += `<div class="workflow-package-resolution-hero is-success"><span class="workflow-package-resolution-icon" aria-hidden="true">✓</span><div><strong>${esc(workflowPackageText('workflows.package.resolution.identicalTitle', '无需重复导入'))}</strong><small>${esc(workflowPackageText('workflows.package.resolution.identicalHint', '内容已经存在,无需重复导入。'))}</small></div></div>`;
|
||||
} else {
|
||||
html += workflowPackageResolutionCard('keep_existing', workflowPackageText('workflows.package.resolution.keepExisting', '保留本地版本'), workflowPackageText('workflows.package.resolution.keepExistingHint', '不修改当前本地工作流;导入记录会保留。'), action === 'keep_existing');
|
||||
if (!workflowPackageState.riskChoicesVisible) {
|
||||
@@ -2040,6 +2104,7 @@
|
||||
workflowPackageState.riskChoicesVisible = false;
|
||||
workflowPackageState.idempotencyKey = '';
|
||||
workflowPackageState.requestSignature = '';
|
||||
workflowPackageState.dragDepth = 0;
|
||||
if (!keepInspection) {
|
||||
workflowPackageState.inspection = null;
|
||||
workflowPackageState.resolutionAction = '';
|
||||
@@ -2047,9 +2112,9 @@
|
||||
}
|
||||
workflowPackageStorageSet(WORKFLOW_PACKAGE_IMPORT_STORAGE_KEY, '');
|
||||
const input = document.getElementById('workflow-package-file-input');
|
||||
const name = document.getElementById('workflow-package-file-name');
|
||||
if (input) input.value = '';
|
||||
if (name) name.textContent = workflowPackageText('workflows.package.noFile', '未选择文件');
|
||||
workflowPackageSetDragging(false);
|
||||
workflowPackageUpdateFileUI();
|
||||
}
|
||||
|
||||
function displayWorkflowPackageError(error) {
|
||||
@@ -2119,7 +2184,7 @@
|
||||
renderWorkflowPackageInspection();
|
||||
renderWorkflowPackageResolution();
|
||||
workflowPackageSetStep('inspection');
|
||||
if (typeof openAppModal === 'function') openAppModal('workflow-package-import-modal', { focusEl: document.getElementById('workflow-package-inspect-btn') });
|
||||
if (typeof openAppModal === 'function') openAppModal('workflow-package-import-modal', { focusEl: document.getElementById('workflow-package-dropzone') });
|
||||
if (typeof window.applyTranslations === 'function') window.applyTranslations(document.getElementById('workflow-package-import-modal'));
|
||||
};
|
||||
|
||||
@@ -2130,13 +2195,54 @@
|
||||
|
||||
window.onWorkflowPackageFileSelected = function () {
|
||||
const input = document.getElementById('workflow-package-file-input');
|
||||
const name = document.getElementById('workflow-package-file-name');
|
||||
const file = input && input.files ? input.files[0] : null;
|
||||
resetWorkflowPackageImport();
|
||||
workflowPackageState.file = file || null;
|
||||
if (name) name.textContent = file ? file.name : workflowPackageText('workflows.package.noFile', '未选择文件');
|
||||
renderWorkflowPackageResolution();
|
||||
workflowPackageSetStep('inspection');
|
||||
workflowPackageSelectFile(file);
|
||||
};
|
||||
|
||||
window.openWorkflowPackageFilePicker = function () {
|
||||
const input = document.getElementById('workflow-package-file-input');
|
||||
if (input) input.click();
|
||||
};
|
||||
|
||||
window.onWorkflowPackageDropzoneKeydown = function (event) {
|
||||
if (!event || (event.key !== 'Enter' && event.key !== ' ')) return;
|
||||
event.preventDefault();
|
||||
window.openWorkflowPackageFilePicker();
|
||||
};
|
||||
|
||||
window.onWorkflowPackageDragEnter = function (event) {
|
||||
if (event) event.preventDefault();
|
||||
workflowPackageState.dragDepth += 1;
|
||||
workflowPackageSetDragging(true);
|
||||
};
|
||||
|
||||
window.onWorkflowPackageDragOver = function (event) {
|
||||
if (!event) return;
|
||||
event.preventDefault();
|
||||
if (event.dataTransfer) event.dataTransfer.dropEffect = 'copy';
|
||||
workflowPackageSetDragging(true);
|
||||
};
|
||||
|
||||
window.onWorkflowPackageDragLeave = function (event) {
|
||||
if (event) event.preventDefault();
|
||||
workflowPackageState.dragDepth = Math.max(0, workflowPackageState.dragDepth - 1);
|
||||
if (!workflowPackageState.dragDepth) workflowPackageSetDragging(false);
|
||||
};
|
||||
|
||||
window.onWorkflowPackageDrop = function (event) {
|
||||
if (event) event.preventDefault();
|
||||
workflowPackageState.dragDepth = 0;
|
||||
workflowPackageSetDragging(false);
|
||||
const files = event && event.dataTransfer && event.dataTransfer.files;
|
||||
workflowPackageSelectFile(files && files.length ? files[0] : null);
|
||||
};
|
||||
|
||||
window.clearWorkflowPackageFile = function (event) {
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
workflowPackageSelectFile(null);
|
||||
};
|
||||
|
||||
window.inspectWorkflowPackage = async function () {
|
||||
@@ -2147,7 +2253,12 @@
|
||||
displayWorkflowPackageError({ code: 'WFPKG_REQUEST_FAILED', message: workflowPackageText('workflows.package.errors.serviceUnavailable', '导入服务尚未准备好,请刷新页面后重试。') });
|
||||
return;
|
||||
}
|
||||
if (button) button.disabled = true;
|
||||
if (button) {
|
||||
button.disabled = true;
|
||||
button.classList.add('is-loading');
|
||||
button.setAttribute('aria-busy', 'true');
|
||||
button.textContent = workflowPackageText('workflows.package.inspecting', '正在安全预检…');
|
||||
}
|
||||
try {
|
||||
const data = await client.createInspection(apiFetch, workflowPackageState.file);
|
||||
workflowPackageState.inspection = data.inspection || data;
|
||||
@@ -2161,7 +2272,12 @@
|
||||
} catch (error) {
|
||||
displayWorkflowPackageError(error);
|
||||
} finally {
|
||||
if (button) button.disabled = false;
|
||||
if (button) {
|
||||
button.disabled = !workflowPackageState.file;
|
||||
button.classList.remove('is-loading');
|
||||
button.removeAttribute('aria-busy');
|
||||
button.textContent = workflowPackageText('workflows.package.inspect', '上传并预检');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2327,7 +2443,9 @@
|
||||
});
|
||||
const connectBtn = document.getElementById('workflow-connect-btn');
|
||||
if (connectBtn) {
|
||||
connectBtn.textContent = connectMode ? _t('workflows.connecting') : _t('workflows.connect');
|
||||
connectBtn.setAttribute('aria-pressed', connectMode ? 'true' : 'false');
|
||||
const label = connectBtn.querySelector('.workflow-toolbar-label');
|
||||
if (label) label.textContent = connectMode ? _t('workflows.connecting') : _t('workflows.connect');
|
||||
}
|
||||
refreshCanvasLabels();
|
||||
updateWorkflowCanvasTitle();
|
||||
@@ -2351,4 +2469,11 @@
|
||||
document.addEventListener('languagechange', function () {
|
||||
refreshWorkflowsI18n();
|
||||
});
|
||||
|
||||
document.addEventListener('click', function (event) {
|
||||
const menu = document.getElementById('workflow-more-actions');
|
||||
if (menu && menu.open && !menu.contains(event.target)) {
|
||||
menu.removeAttribute('open');
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
+96
-23
@@ -2657,14 +2657,42 @@
|
||||
<span id="workflow-canvas-subtitle" class="workflow-canvas-subtitle" hidden></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="workflow-toolbar">
|
||||
<button class="btn-secondary btn-small" data-require-permission="workflow:write" type="button" onclick="toggleWorkflowConnectMode()" id="workflow-connect-btn" data-i18n="workflows.connect">连线</button>
|
||||
<button class="btn-secondary btn-small" data-require-permission="workflow:delete" type="button" onclick="deleteWorkflowSelection()" data-i18n="workflows.deleteSelected">删除选中</button>
|
||||
<button class="btn-secondary btn-small" type="button" onclick="layoutWorkflowGraph()" data-i18n="workflows.autoLayout">自动布局</button>
|
||||
<button class="btn-secondary btn-small" data-require-permission="workflow:execute" type="button" onclick="dryRunWorkflowDraft()" data-i18n="workflows.dryRun">试运行</button>
|
||||
<button class="btn-secondary btn-small" data-require-permission="workflow:read" type="button" id="workflow-package-export-btn" onclick="exportCurrentWorkflowPackage()" data-i18n="workflows.package.export">导出</button>
|
||||
<button class="btn-secondary btn-small" data-require-permission="workflow:delete" onclick="deleteCurrentWorkflow()" data-i18n="common.delete">删除</button>
|
||||
<button class="btn-primary btn-small" data-require-permission="workflow:write" onclick="saveWorkflowDraft()" data-i18n="common.save">保存</button>
|
||||
<div class="workflow-toolbar" role="toolbar" aria-label="流程编辑工具" data-i18n="workflows.toolbarLabel" data-i18n-attr="aria-label" data-i18n-skip-text="true">
|
||||
<div class="workflow-toolbar-group" role="group" aria-label="画布工具" data-i18n="workflows.canvasTools" data-i18n-attr="aria-label" data-i18n-skip-text="true">
|
||||
<button class="btn-secondary btn-small workflow-toolbar-button" data-require-permission="workflow:write" type="button" onclick="toggleWorkflowConnectMode()" id="workflow-connect-btn" aria-pressed="false">
|
||||
<svg aria-hidden="true" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="6" cy="6" r="2.5"/><circle cx="18" cy="18" r="2.5"/><path d="M8 8l8 8"/></svg>
|
||||
<span class="workflow-toolbar-label" data-i18n="workflows.connect">连线</span>
|
||||
</button>
|
||||
<button class="btn-secondary btn-small workflow-toolbar-button" type="button" onclick="layoutWorkflowGraph()">
|
||||
<svg aria-hidden="true" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="3" width="6" height="5" rx="1"/><rect x="14" y="16" width="6" height="5" rx="1"/><path d="M7 8v4h10v4"/></svg>
|
||||
<span class="workflow-toolbar-label" data-i18n="workflows.autoLayout">自动布局</span>
|
||||
</button>
|
||||
</div>
|
||||
<button class="btn-secondary btn-small workflow-toolbar-button workflow-run-button" data-require-permission="workflow:execute" type="button" onclick="dryRunWorkflowDraft()">
|
||||
<svg aria-hidden="true" width="15" height="15" viewBox="0 0 24 24" fill="currentColor"><path d="M8 5.4v13.2a1 1 0 0 0 1.55.84l9.2-6.6a1 1 0 0 0 0-1.68l-9.2-6.6A1 1 0 0 0 8 5.4Z"/></svg>
|
||||
<span class="workflow-toolbar-label" data-i18n="workflows.dryRun">试运行</span>
|
||||
</button>
|
||||
<details class="workflow-more-actions" id="workflow-more-actions" data-require-permission-any="workflow:read workflow:delete">
|
||||
<summary class="btn-secondary btn-small workflow-more-actions-trigger" aria-haspopup="menu">
|
||||
<svg aria-hidden="true" width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><circle cx="5" cy="12" r="1.8"/><circle cx="12" cy="12" r="1.8"/><circle cx="19" cy="12" r="1.8"/></svg>
|
||||
<span class="workflow-toolbar-label" data-i18n="workflows.moreActions">更多</span>
|
||||
</summary>
|
||||
<div class="workflow-more-actions-menu" role="menu">
|
||||
<button data-require-permission="workflow:read" type="button" id="workflow-package-export-btn" role="menuitem" onclick="closeWorkflowMoreActions(); exportCurrentWorkflowPackage()">
|
||||
<svg aria-hidden="true" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v12m0 0 4-4m-4 4-4-4"/><path d="M5 19h14"/></svg>
|
||||
<span data-i18n="workflows.package.export">导出</span>
|
||||
</button>
|
||||
<div class="workflow-more-actions-divider" role="separator"></div>
|
||||
<button class="workflow-more-actions-danger" data-require-permission="workflow:delete" type="button" role="menuitem" onclick="closeWorkflowMoreActions(); deleteCurrentWorkflow()">
|
||||
<svg aria-hidden="true" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 7h16M9 7V4h6v3m3 0-1 13H7L6 7"/><path d="M10 11v5m4-5v5"/></svg>
|
||||
<span data-i18n="workflows.deleteWorkflow">删除流程</span>
|
||||
</button>
|
||||
</div>
|
||||
</details>
|
||||
<button class="btn-primary btn-small workflow-save-button" data-require-permission="workflow:write" type="button" onclick="saveWorkflowDraft()">
|
||||
<svg aria-hidden="true" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 4h12l2 2v14H5z"/><path d="M8 4v6h8V4M8 20v-6h8v6"/></svg>
|
||||
<span data-i18n="common.save">保存</span>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
<section class="workflow-canvas-wrap" ondragover="workflowCanvasDragOver(event)" ondrop="workflowCanvasDrop(event)">
|
||||
@@ -4895,35 +4923,75 @@
|
||||
<div id="workflow-package-import-modal" class="modal workflow-package-modal" style="display: none;" role="dialog" aria-modal="true" aria-labelledby="workflow-package-import-title">
|
||||
<div class="modal-content workflow-package-modal-content">
|
||||
<div class="modal-header workflow-package-modal-header">
|
||||
<div>
|
||||
<h2 id="workflow-package-import-title" data-i18n="workflows.package.title">导入本地包</h2>
|
||||
<p class="workflow-package-subtitle" data-i18n="workflows.package.preflightHint">预检不会修改本地工作流</p>
|
||||
<div class="workflow-package-title-group">
|
||||
<span class="workflow-package-title-icon" aria-hidden="true">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v12"/><path d="m7 10 5 5 5-5"/><path d="M5 19h14"/></svg>
|
||||
</span>
|
||||
<div>
|
||||
<div class="workflow-package-title-line">
|
||||
<h2 id="workflow-package-import-title" data-i18n="workflows.package.title">导入本地包</h2>
|
||||
<span class="workflow-package-safe-badge" data-i18n="workflows.package.safeBadge">安全预检</span>
|
||||
</div>
|
||||
<p class="workflow-package-subtitle" data-i18n="workflows.package.preflightHint">先检查包内容与冲突,再由你确认是否导入</p>
|
||||
</div>
|
||||
</div>
|
||||
<span class="modal-close" onclick="closeWorkflowPackageImportModal()" aria-label="关闭" data-i18n="common.close" data-i18n-attr="aria-label">×</span>
|
||||
</div>
|
||||
<div class="workflow-package-steps" aria-label="导入步骤" data-i18n="workflows.package.stepsLabel" data-i18n-attr="aria-label">
|
||||
<span class="is-active" id="workflow-package-step-inspection"><b>1</b><span data-i18n="workflows.package.stepInspection">上传与预检</span></span>
|
||||
<span id="workflow-package-step-import"><b>2</b><span data-i18n="workflows.package.stepImport">处理冲突与导入</span></span>
|
||||
<span class="is-active" id="workflow-package-step-inspection"><b>1</b><span><strong data-i18n="workflows.package.stepInspection">上传与预检</strong><small data-i18n="workflows.package.stepInspectionHint">校验格式、内容与版本</small></span></span>
|
||||
<span id="workflow-package-step-import"><b>2</b><span><strong data-i18n="workflows.package.stepImport">处理冲突与导入</strong><small data-i18n="workflows.package.stepImportHint">确认处理方式并完成导入</small></span></span>
|
||||
</div>
|
||||
<div class="modal-body workflow-package-modal-body">
|
||||
<section class="workflow-package-pane workflow-package-pane--inspection">
|
||||
<div class="workflow-package-file-row">
|
||||
<input id="workflow-package-file-input" type="file" accept=".csapkg.zip,application/zip" hidden onchange="onWorkflowPackageFileSelected()">
|
||||
<button type="button" class="btn-secondary" onclick="document.getElementById('workflow-package-file-input').click()" data-i18n="workflows.package.selectFile">选择本地包</button>
|
||||
<span id="workflow-package-file-name" class="workflow-package-file-name" data-i18n="workflows.package.noFile">未选择文件</span>
|
||||
<div class="workflow-package-pane-heading">
|
||||
<div>
|
||||
<span class="workflow-package-eyebrow" data-i18n="workflows.package.packageEyebrow">本地工作流包</span>
|
||||
<h3 data-i18n="workflows.package.packageTitle">选择需要导入的文件</h3>
|
||||
</div>
|
||||
<span class="workflow-package-format-pill">.CSAPKG.ZIP</span>
|
||||
</div>
|
||||
<p class="workflow-package-file-hint" data-i18n="workflows.package.fileHint">仅支持单个 .csapkg.zip,文件不得超过 10 MiB。</p>
|
||||
<button id="workflow-package-inspect-btn" type="button" class="btn-primary" onclick="inspectWorkflowPackage()" data-i18n="workflows.package.inspect">上传并预检</button>
|
||||
<input id="workflow-package-file-input" type="file" accept=".csapkg.zip,application/zip" hidden onchange="onWorkflowPackageFileSelected()">
|
||||
<div id="workflow-package-dropzone" class="workflow-package-dropzone" role="button" tabindex="0" aria-controls="workflow-package-file-input" aria-describedby="workflow-package-file-hint" onclick="openWorkflowPackageFilePicker()" onkeydown="onWorkflowPackageDropzoneKeydown(event)" ondragenter="onWorkflowPackageDragEnter(event)" ondragover="onWorkflowPackageDragOver(event)" ondragleave="onWorkflowPackageDragLeave(event)" ondrop="onWorkflowPackageDrop(event)">
|
||||
<span class="workflow-package-upload-icon" aria-hidden="true">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 16V4"/><path d="m7 9 5-5 5 5"/><path d="M5 14v4a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-4"/></svg>
|
||||
</span>
|
||||
<span class="workflow-package-dropzone-copy">
|
||||
<strong data-i18n="workflows.package.dropTitle">拖拽本地包到这里</strong>
|
||||
<span><span data-i18n="workflows.package.dropOr">或</span> <span class="workflow-package-browse" data-i18n="workflows.package.selectFile">浏览文件</span></span>
|
||||
</span>
|
||||
</div>
|
||||
<div id="workflow-package-selected-file" class="workflow-package-selected-file" hidden>
|
||||
<span class="workflow-package-file-badge" aria-hidden="true">ZIP</span>
|
||||
<span class="workflow-package-file-details">
|
||||
<strong id="workflow-package-file-name" class="workflow-package-file-name"></strong>
|
||||
<small id="workflow-package-file-size"></small>
|
||||
</span>
|
||||
<button type="button" class="workflow-package-file-remove" onclick="clearWorkflowPackageFile(event)" aria-label="移除文件" title="移除文件" data-i18n="workflows.package.removeFile" data-i18n-attr="aria-label,title" data-i18n-skip-text="true">×</button>
|
||||
</div>
|
||||
<p id="workflow-package-file-hint" class="workflow-package-file-hint" data-i18n="workflows.package.fileHint">仅支持单个 .csapkg.zip,文件不得超过 10 MiB。</p>
|
||||
<button id="workflow-package-inspect-btn" type="button" class="btn-primary workflow-package-inspect-btn" onclick="inspectWorkflowPackage()" disabled data-i18n="workflows.package.inspect">上传并预检</button>
|
||||
<div id="workflow-package-inspection" class="workflow-package-summary" aria-live="polite"></div>
|
||||
</section>
|
||||
<section class="workflow-package-pane workflow-package-pane--resolution">
|
||||
<h3 data-i18n="workflows.package.resolutionTitle">处理方式</h3>
|
||||
<div class="workflow-package-pane-heading">
|
||||
<div>
|
||||
<span class="workflow-package-eyebrow" data-i18n="workflows.package.decisionEyebrow">导入策略</span>
|
||||
<h3 data-i18n="workflows.package.resolutionTitle">处理方式</h3>
|
||||
</div>
|
||||
<span class="workflow-package-decision-status" data-i18n="workflows.package.pending">待预检</span>
|
||||
</div>
|
||||
<div id="workflow-package-resolution" class="workflow-package-resolution" aria-live="polite"></div>
|
||||
</section>
|
||||
</div>
|
||||
<div class="modal-footer workflow-package-modal-footer">
|
||||
<button type="button" class="btn-secondary" onclick="closeWorkflowPackageImportModal()" data-i18n="common.cancel">取消</button>
|
||||
<button id="workflow-package-submit-btn" type="button" class="btn-primary" onclick="submitWorkflowPackageImport()" disabled data-i18n="workflows.package.confirmImport">确认导入</button>
|
||||
<div class="workflow-package-footer-note">
|
||||
<span aria-hidden="true">✓</span>
|
||||
<span data-i18n="workflows.package.footerNote">导入前不会改动现有工作流</span>
|
||||
</div>
|
||||
<div class="workflow-package-footer-actions">
|
||||
<button type="button" class="btn-secondary" onclick="closeWorkflowPackageImportModal()" data-i18n="common.cancel">取消</button>
|
||||
<button id="workflow-package-submit-btn" type="button" class="btn-primary" onclick="submitWorkflowPackageImport()" disabled data-i18n="workflows.package.confirmImport">确认导入</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -5930,7 +5998,12 @@
|
||||
</section>
|
||||
</div>
|
||||
<div class="modal-footer robot-binding-footer">
|
||||
<span>如需配置专用服务账号,请前往“系统设置 → 机器人设置”。</span>
|
||||
<div class="robot-binding-service-hint">
|
||||
<span class="robot-binding-service-hint-icon" aria-hidden="true">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none"><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.09a2 2 0 0 1 1 1.74v.5a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.38a2 2 0 0 0-.73-2.73l-.15-.09a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2Z" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/><circle cx="12" cy="12" r="3" stroke="currentColor" stroke-width="1.8"/></svg>
|
||||
</span>
|
||||
<span>如需配置专用服务账号,请前往“系统设置 → 机器人设置”。</span>
|
||||
</div>
|
||||
<button type="button" class="btn-secondary" onclick="closeRobotAccountBinding()">关闭</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user