Files
CyberStrikeAI/web/static/js/workflow-package-client.js
T
chenchen acfacfe1b3 feat(工作流):支持本地图编排策略包导入导出 (#195)
* docs: define local workflow package mvp

* docs: fix workflow package api contract

* feat: add local workflow package mvp

* feat(workflow): add package API client

* feat(workflow): add package import interface

* feat(workflow): connect package import flow

* fix(workflow): map package validation errors

* fix(workflow): handle import key generation errors

* feat(workflow): localize package import states

* fix(workflow): reset package import modal on open

---------

Co-authored-by: ruanmingchen <“ruanm@chenchen”>
2026-07-14 11:28:37 +08:00

118 lines
4.9 KiB
JavaScript

(function (root, factory) {
const client = factory(root);
if (typeof module === 'object' && module.exports) module.exports = client;
if (root) root.WorkflowPackageClient = client;
})(typeof globalThis !== 'undefined' ? globalThis : this, function (root) {
const ACTIONS_BY_CONFLICT = {
none: ['create'],
identical: ['keep_existing'],
id_conflict: ['keep_existing', 'overwrite', 'rename']
};
class WorkflowPackageError extends Error {
constructor(code, message, details) {
super(message || code || 'WFPKG_REQUEST_FAILED');
this.name = 'WorkflowPackageError';
this.code = code || '';
this.details = details || {};
}
}
function allowedActions(conflictState) {
return (ACTIONS_BY_CONFLICT[conflictState] || []).slice();
}
function buildImportRequest(input) {
const data = input || {};
const inspectionId = String(data.inspectionId || '').trim();
const action = String(data.action || '').trim();
const newWorkflowId = String(data.newWorkflowId || '').trim();
const confirmOverwrite = data.confirmOverwrite === true;
if (!inspectionId) throw new WorkflowPackageError('WFPKG_INSPECTION_REQUIRED', '缺少预检记录');
if (!['create', 'keep_existing', 'overwrite', 'rename'].includes(action)) {
throw new WorkflowPackageError('WFPKG_INVALID_ACTION', '导入处理方式无效');
}
if (action === 'rename' && !newWorkflowId) {
throw new WorkflowPackageError('WFPKG_INVALID_RENAME_ID', '请输入新的工作流 ID');
}
if (action !== 'rename' && newWorkflowId) {
throw new WorkflowPackageError('WFPKG_INVALID_ACTION', '当前处理方式不允许填写新的工作流 ID');
}
if (action === 'overwrite' && !confirmOverwrite) {
throw new WorkflowPackageError('WFPKG_OVERWRITE_CONFIRMATION_REQUIRED', '请确认覆盖本地工作流');
}
return {
inspection_id: inspectionId,
resolution: {
action: action,
new_workflow_id: action === 'rename' ? newWorkflowId : ''
},
confirm_overwrite: action === 'overwrite'
};
}
function createIdempotencyKey() {
const cryptoApi = root && root.crypto;
if (!cryptoApi || typeof cryptoApi.randomUUID !== 'function') {
throw new WorkflowPackageError('WFPKG_IDEMPOTENCY_KEY_REQUIRED', '浏览器无法生成导入请求标识');
}
return cryptoApi.randomUUID();
}
async function readApiError(response) {
const payload = await response.json().catch(function () { return {}; });
const error = payload && payload.error ? payload.error : {};
return {
code: error.code || '',
message: error.message || '',
details: error.details || {}
};
}
async function readJsonOrThrow(response) {
if (response.ok) return response.json();
const error = await readApiError(response);
throw new WorkflowPackageError(error.code, error.message, error.details);
}
async function createInspection(apiFetch, file) {
if (!file) throw new WorkflowPackageError('WFPKG_FILE_REQUIRED', '请选择本地图编排包');
const body = new FormData();
body.append('file', file, file.name || 'workflow.csapkg.zip');
const response = await apiFetch('/api/workflow-package-inspections', { method: 'POST', body: body });
return readJsonOrThrow(response);
}
async function getInspection(apiFetch, inspectionId) {
const response = await apiFetch('/api/workflow-package-inspections/' + encodeURIComponent(inspectionId));
return readJsonOrThrow(response);
}
async function applyImport(apiFetch, request, idempotencyKey) {
if (!idempotencyKey) throw new WorkflowPackageError('WFPKG_IDEMPOTENCY_KEY_REQUIRED', '缺少导入请求标识');
const response = await apiFetch('/api/workflow-package-imports', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Idempotency-Key': idempotencyKey },
body: JSON.stringify(request)
});
return readJsonOrThrow(response);
}
async function getImport(apiFetch, importId) {
const response = await apiFetch('/api/workflow-package-imports/' + encodeURIComponent(importId));
return readJsonOrThrow(response);
}
return {
WorkflowPackageError: WorkflowPackageError,
allowedActions: allowedActions,
buildImportRequest: buildImportRequest,
createIdempotencyKey: createIdempotencyKey,
readApiError: readApiError,
createInspection: createInspection,
getInspection: getInspection,
applyImport: applyImport,
getImport: getImport
};
});