mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-10-19 22:17:42 +02:00
fix: keep related fact views in place and clarify import outcomes
This commit is contained in:
1 parent
fa4e5ef4cb
commit
bb96a084e0
9 files changed
+96
-20
No files matched your search
@@ -3975,6 +3975,8 @@
|
||||
"continueOverwrite": "Continue to overwrite confirmation",
|
||||
"confirmRename": "Confirm save as new ID",
|
||||
"confirmCreate": "Confirm create",
|
||||
"confirmSkip": "Confirm skip import",
|
||||
"confirmKeep": "Confirm keep local version",
|
||||
"confirmComplete": "Confirm completion"
|
||||
},
|
||||
"result": {
|
||||
@@ -3984,6 +3986,8 @@
|
||||
"keptExisting": "The local workflow was kept; no changes were made.",
|
||||
"skippedIdentical": "The same local content already exists; import was skipped.",
|
||||
"complete": "Import completed.",
|
||||
"skippedAction": "Import skipped",
|
||||
"keptAction": "Local version kept",
|
||||
"completedAction": "Import completed"
|
||||
},
|
||||
"exportSelectSaved": "Choose a saved workflow before exporting."
|
||||
|
||||
@@ -3987,6 +3987,8 @@
|
||||
"continueOverwrite": "К подтверждению перезаписи",
|
||||
"confirmRename": "Подтвердить сохранение с новым ID",
|
||||
"confirmCreate": "Подтвердить создание",
|
||||
"confirmSkip": "Подтвердить пропуск импорта",
|
||||
"confirmKeep": "Подтвердить сохранение локальной версии",
|
||||
"confirmComplete": "Подтвердить завершение"
|
||||
},
|
||||
"result": {
|
||||
@@ -3996,6 +3998,8 @@
|
||||
"keptExisting": "Локальный сценарий оставлен без изменений.",
|
||||
"skippedIdentical": "Такое локальное содержимое уже есть; импорт пропущен.",
|
||||
"complete": "Импорт завершён.",
|
||||
"skippedAction": "Импорт пропущен",
|
||||
"keptAction": "Локальная версия сохранена",
|
||||
"completedAction": "Импорт завершён"
|
||||
},
|
||||
"exportSelectSaved": "Перед экспортом выберите сохранённый сценарий."
|
||||
|
||||
@@ -3963,6 +3963,8 @@
|
||||
"continueOverwrite": "继续确认覆盖",
|
||||
"confirmRename": "确认另存",
|
||||
"confirmCreate": "确认创建",
|
||||
"confirmSkip": "确认跳过导入",
|
||||
"confirmKeep": "确认保留本地版本",
|
||||
"confirmComplete": "确认完成"
|
||||
},
|
||||
"result": {
|
||||
@@ -3972,6 +3974,8 @@
|
||||
"keptExisting": "已保留本地工作流,未写入任何更改。",
|
||||
"skippedIdentical": "本地已存在相同内容,已跳过导入。",
|
||||
"complete": "导入已完成。",
|
||||
"skippedAction": "已跳过导入",
|
||||
"keptAction": "已保留本地版本",
|
||||
"completedAction": "导入已完成"
|
||||
},
|
||||
"exportSelectSaved": "请先选择已保存的工作流后再导出。"
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
const fs = require('node:fs');
|
||||
const vm = require('node:vm');
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const source = fs.readFileSync('web/static/js/projects.js', 'utf8');
|
||||
function harness(facts, selection, ok = true) {
|
||||
const opened = [], requests = [], messages = [];
|
||||
const c = vm.createContext({ currentProjectId: 'project1', URLSearchParams, apiFetch: async url => { requests.push(url); return { ok, json: async () => facts }; }, alert: message => messages.push(message), tp: key => key, prompt: () => selection, viewProjectFactBody: async key => opened.push(key), switchProjectTab: () => { throw Error('unexpected navigation'); }, loadProjectFacts: () => { throw Error('unexpected list refresh'); }, document: { getElementById: () => { throw Error('unexpected filter reset'); } } });
|
||||
vm.runInContext(source.slice(source.indexOf('async function viewFactsForVulnerability('), source.indexOf('function openProjectsOverlay(')), c);
|
||||
return { c, opened, requests, messages };
|
||||
}
|
||||
test('single related fact opens its detail without changing the page or filters', async () => {
|
||||
const h = harness([{ fact_key: 'evidence' }]);
|
||||
await h.c.viewFactsForVulnerability('v1');
|
||||
assert.deepEqual(h.opened, ['evidence']);
|
||||
assert.match(h.requests[0], /related_vulnerability_id=v1/);
|
||||
});
|
||||
test('no related facts shows a message without opening an empty modal or navigating', async () => {
|
||||
const h = harness([]);
|
||||
await h.c.viewFactsForVulnerability('v1');
|
||||
assert.equal(h.opened.length, 0);
|
||||
assert.deepEqual(h.messages, ['projects.noFactsForVulnerability']);
|
||||
});
|
||||
test('multiple facts open the selected detail and cancellation leaves the page intact', async () => {
|
||||
for (const selection of ['2', null, '', '9']) {
|
||||
const h = harness([{ fact_key: 'one' }, { fact_key: 'two' }], selection);
|
||||
await h.c.viewFactsForVulnerability('v1');
|
||||
assert.deepEqual(h.opened, selection === '2' ? ['two'] : []);
|
||||
}
|
||||
});
|
||||
test('failed related fact request reports the failure without opening a modal', async () => {
|
||||
const h = harness([], null, false);
|
||||
await h.c.viewFactsForVulnerability('v1');
|
||||
assert.deepEqual(h.messages, ['projects.loadRelatedFactsFailed']);
|
||||
assert.equal(h.opened.length, 0);
|
||||
});
|
||||
@@ -1901,28 +1901,16 @@ function openVulnerabilityDetail(vulnId) {
|
||||
|
||||
async function viewFactsForVulnerability(vulnId) {
|
||||
if (!currentProjectId) return;
|
||||
switchProjectTab('facts');
|
||||
const searchEl = document.getElementById('project-facts-search');
|
||||
const catEl = document.getElementById('project-facts-filter-category');
|
||||
const confEl = document.getElementById('project-facts-filter-confidence');
|
||||
const sparseEl = document.getElementById('project-facts-filter-sparse');
|
||||
const hideDepEl = document.getElementById('project-facts-filter-hide-deprecated');
|
||||
if (searchEl) searchEl.value = '';
|
||||
if (catEl) catEl.value = '';
|
||||
if (confEl) confEl.value = '';
|
||||
if (sparseEl) sparseEl.checked = false;
|
||||
if (hideDepEl) hideDepEl.checked = true;
|
||||
const params = new URLSearchParams({ limit: '50', related_vulnerability_id: vulnId });
|
||||
const res = await apiFetch(`/api/projects/${currentProjectId}/facts?${params}`);
|
||||
if (!res.ok) return alert(tp('projects.loadRelatedFactsFailed'));
|
||||
const facts = await res.json();
|
||||
if (!facts.length) {
|
||||
alert(tp('projects.noFactsForVulnerability'));
|
||||
loadProjectFacts();
|
||||
return;
|
||||
}
|
||||
if (facts.length === 1) {
|
||||
viewProjectFactBody(facts[0].fact_key);
|
||||
await viewProjectFactBody(facts[0].fact_key);
|
||||
return;
|
||||
}
|
||||
const pick = prompt(
|
||||
@@ -1933,12 +1921,10 @@ async function viewFactsForVulnerability(vulnId) {
|
||||
}) || `This vulnerability is linked to ${facts.length} facts. Enter index to view:\n${facts.map((f, i) => `${i + 1}. ${f.fact_key}`).join('\n')}`,
|
||||
);
|
||||
if (pick == null || pick === '') {
|
||||
loadProjectFacts();
|
||||
return;
|
||||
}
|
||||
const idx = parseInt(pick, 10) - 1;
|
||||
if (facts[idx]) viewProjectFactBody(facts[idx].fact_key);
|
||||
else loadProjectFacts();
|
||||
if (facts[idx]) await viewProjectFactBody(facts[idx].fact_key);
|
||||
}
|
||||
|
||||
function openProjectsOverlay(id, opts) {
|
||||
|
||||
@@ -80,3 +80,33 @@ test('每次打开导入弹窗都会开始新的导入会话', () => {
|
||||
assert.match(openHandler, /resetWorkflowPackageImport\(\);/);
|
||||
assert.doesNotMatch(openHandler, /restoreWorkflowPackageState\(\)/);
|
||||
});
|
||||
|
||||
test('未写入的导入结果明确显示跳过或保留,不显示导入完成', () => {
|
||||
const vm = require('node:vm');
|
||||
const source = fs.readFileSync('web/static/js/workflows.js', 'utf8');
|
||||
const start = source.indexOf(' function renderWorkflowPackageImportResult(');
|
||||
const end = source.indexOf(' async function restoreWorkflowPackageState(', start);
|
||||
for (const [result, expected] of [['skipped_identical', '已跳过导入'], ['kept_existing', '已保留本地版本'], ['created', '导入已完成'], ['overwritten', '导入已完成'], ['renamed', '导入已完成']]) {
|
||||
const button = {};
|
||||
const c = vm.createContext({ workflowPackageText: (_, fallback) => fallback, renderWorkflowPackageResolution() {}, workflowPackageSubmitBtn: () => button });
|
||||
vm.runInContext(source.slice(start, end), c);
|
||||
c.renderWorkflowPackageImportResult({ result });
|
||||
assert.equal(button.textContent, expected);
|
||||
assert.equal(button.disabled, true);
|
||||
}
|
||||
});
|
||||
|
||||
test('重复包确认明确区分跳过和保留,创建及覆盖入口保持原语义', () => {
|
||||
const vm = require('node:vm');
|
||||
const source = fs.readFileSync('web/static/js/workflows.js', 'utf8');
|
||||
const start = source.indexOf(' function renderWorkflowPackageResolution(');
|
||||
const end = source.indexOf(' function resetWorkflowPackageImport(', start);
|
||||
for (const [state, action, expected] of [['identical', 'keep_existing', '确认跳过导入'], ['id_conflict', 'keep_existing', '确认保留本地版本'], ['none', 'create', '确认创建'], ['id_conflict', 'overwrite', '继续确认覆盖'], ['id_conflict', 'rename', '确认另存']]) {
|
||||
const button = {};
|
||||
const c = vm.createContext({ workflowPackageState: { inspection: { conflict: { state } }, resolutionAction: action, riskChoicesVisible: true, newWorkflowId: 'copy' }, workflowPackageResolutionEl: () => ({}), workflowPackageSubmitBtn: () => button, workflowPackageClient: () => ({ allowedActions: () => [action] }), workflowPackageText: (_, fallback) => fallback, esc: value => value, workflowPackageResolutionCard: () => '', workflowPackageSetStep() {} });
|
||||
vm.runInContext(source.slice(start, end), c);
|
||||
c.renderWorkflowPackageResolution();
|
||||
assert.equal(button.textContent, expected);
|
||||
assert.equal(button.disabled, false);
|
||||
}
|
||||
});
|
||||
@@ -2719,7 +2719,10 @@
|
||||
submit.disabled = !action || renameIncomplete;
|
||||
if (action === 'overwrite') submit.textContent = workflowPackageText('workflows.package.resolution.continueOverwrite', '继续确认覆盖');
|
||||
else if (action === 'rename') submit.textContent = workflowPackageText('workflows.package.resolution.confirmRename', '确认另存');
|
||||
else submit.textContent = action === 'create' ? workflowPackageText('workflows.package.resolution.confirmCreate', '确认创建') : workflowPackageText('workflows.package.resolution.confirmComplete', '确认完成');
|
||||
else if (action === 'create') submit.textContent = workflowPackageText('workflows.package.resolution.confirmCreate', '确认创建');
|
||||
else submit.textContent = conflictState === 'identical'
|
||||
? workflowPackageText('workflows.package.resolution.confirmSkip', '确认跳过导入')
|
||||
: workflowPackageText('workflows.package.resolution.confirmKeep', '确认保留本地版本');
|
||||
}
|
||||
workflowPackageSetStep('import');
|
||||
}
|
||||
@@ -2775,7 +2778,11 @@
|
||||
const submit = workflowPackageSubmitBtn();
|
||||
if (submit) {
|
||||
submit.disabled = true;
|
||||
submit.textContent = workflowPackageText('workflows.package.result.completedAction', '导入已完成');
|
||||
submit.textContent = result === 'skipped_identical'
|
||||
? workflowPackageText('workflows.package.result.skippedAction', '已跳过导入')
|
||||
: result === 'kept_existing'
|
||||
? workflowPackageText('workflows.package.result.keptAction', '已保留本地版本')
|
||||
: workflowPackageText('workflows.package.result.completedAction', '导入已完成');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user