mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-09-30 21:20:12 +02:00
Add files via upload
This commit is contained in:
5 files changed
+78
-93
No files matched your search
+16
-43
@@ -22684,18 +22684,29 @@ button.chat-files-dropdown-item:hover:not(:disabled) {
|
|||||||
}
|
}
|
||||||
.projects-detail-title-row {
|
.projects-detail-title-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: flex-start;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
.projects-detail-title {
|
.projects-detail-title {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 1.375rem;
|
font-size: 1.375rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #0f172a;
|
color: #0f172a;
|
||||||
letter-spacing: -0.02em;
|
letter-spacing: -0.02em;
|
||||||
|
line-height: 1.35;
|
||||||
|
word-break: break-word;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.projects-status-pill {
|
.projects-status-pill {
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-top: 4px;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
font-size: 0.6875rem;
|
font-size: 0.6875rem;
|
||||||
@@ -22717,56 +22728,18 @@ button.chat-files-dropdown-item:hover:not(:disabled) {
|
|||||||
font-size: 0.8125rem;
|
font-size: 0.8125rem;
|
||||||
color: #94a3b8;
|
color: #94a3b8;
|
||||||
}
|
}
|
||||||
.projects-detail-desc-block {
|
.projects-detail-desc {
|
||||||
margin: 10px 0 0;
|
margin: 10px 0 0;
|
||||||
max-width: min(640px, 100%);
|
max-width: min(640px, 100%);
|
||||||
}
|
|
||||||
.projects-detail-desc {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
color: #475569;
|
color: #475569;
|
||||||
line-height: 1.55;
|
line-height: 1.55;
|
||||||
white-space: pre-wrap;
|
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
overflow-wrap: anywhere;
|
overflow-wrap: anywhere;
|
||||||
}
|
display: -webkit-box;
|
||||||
.projects-detail-desc.is-collapsed {
|
-webkit-box-orient: vertical;
|
||||||
max-height: 4.65em;
|
-webkit-line-clamp: 3;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.projects-detail-desc.is-collapsed::after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
height: 1.4em;
|
|
||||||
background: linear-gradient(to bottom, rgba(255, 255, 255, 0), #fff 85%);
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
.projects-detail-desc.is-expanded {
|
|
||||||
max-height: min(240px, 32vh);
|
|
||||||
overflow-y: auto;
|
|
||||||
overscroll-behavior: contain;
|
|
||||||
padding-right: 4px;
|
|
||||||
}
|
|
||||||
.projects-detail-desc-toggle {
|
|
||||||
display: inline-block;
|
|
||||||
margin-top: 6px;
|
|
||||||
padding: 0;
|
|
||||||
border: none;
|
|
||||||
background: none;
|
|
||||||
color: #0066ff;
|
|
||||||
font-size: 0.8125rem;
|
|
||||||
font-weight: 500;
|
|
||||||
cursor: pointer;
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
.projects-detail-desc-toggle:hover,
|
|
||||||
.projects-detail-desc-toggle:focus-visible {
|
|
||||||
text-decoration: underline;
|
|
||||||
outline: none;
|
|
||||||
}
|
}
|
||||||
.projects-description-textarea {
|
.projects-description-textarea {
|
||||||
max-height: 200px;
|
max-height: 200px;
|
||||||
|
|||||||
@@ -286,6 +286,8 @@
|
|||||||
"status": "Status",
|
"status": "Status",
|
||||||
"modalNewTitle": "New project",
|
"modalNewTitle": "New project",
|
||||||
"modalNewSubtitle": "After creation, bind conversations to share fact board across chats",
|
"modalNewSubtitle": "After creation, bind conversations to share fact board across chats",
|
||||||
|
"modalEditTitle": "Edit project",
|
||||||
|
"modalEditSubtitle": "Update project name and description",
|
||||||
"projectName": "Project name",
|
"projectName": "Project name",
|
||||||
"projectNamePlaceholder": "e.g. Client A Web pentest",
|
"projectNamePlaceholder": "e.g. Client A Web pentest",
|
||||||
"projectDescription": "Project description",
|
"projectDescription": "Project description",
|
||||||
@@ -411,6 +413,7 @@
|
|||||||
"dangerZoneHint": "Archived projects are hidden unless 'Show archived' is enabled; deletion removes all facts permanently.",
|
"dangerZoneHint": "Archived projects are hidden unless 'Show archived' is enabled; deletion removes all facts permanently.",
|
||||||
"archiveRestore": "Archive / Restore",
|
"archiveRestore": "Archive / Restore",
|
||||||
"archiveProject": "Archive",
|
"archiveProject": "Archive",
|
||||||
|
"editProject": "Edit",
|
||||||
"restoreProjectActive": "Restore to active",
|
"restoreProjectActive": "Restore to active",
|
||||||
"projectActions": "Project actions",
|
"projectActions": "Project actions",
|
||||||
"deleteProject": "Delete project",
|
"deleteProject": "Delete project",
|
||||||
|
|||||||
@@ -274,6 +274,8 @@
|
|||||||
"status": "状态",
|
"status": "状态",
|
||||||
"modalNewTitle": "新建项目",
|
"modalNewTitle": "新建项目",
|
||||||
"modalNewSubtitle": "创建后可绑定对话,跨会话共享事实黑板",
|
"modalNewSubtitle": "创建后可绑定对话,跨会话共享事实黑板",
|
||||||
|
"modalEditTitle": "编辑项目",
|
||||||
|
"modalEditSubtitle": "修改项目名称与描述",
|
||||||
"projectName": "项目名称",
|
"projectName": "项目名称",
|
||||||
"projectNamePlaceholder": "例如:某客户 Web 渗透",
|
"projectNamePlaceholder": "例如:某客户 Web 渗透",
|
||||||
"projectDescription": "项目描述",
|
"projectDescription": "项目描述",
|
||||||
@@ -399,6 +401,7 @@
|
|||||||
"dangerZoneHint": "归档后需在列表勾选「显示已归档」才能查看;删除将清除全部事实且不可恢复。",
|
"dangerZoneHint": "归档后需在列表勾选「显示已归档」才能查看;删除将清除全部事实且不可恢复。",
|
||||||
"archiveRestore": "归档 / 恢复",
|
"archiveRestore": "归档 / 恢复",
|
||||||
"archiveProject": "归档",
|
"archiveProject": "归档",
|
||||||
|
"editProject": "编辑",
|
||||||
"restoreProjectActive": "恢复为进行中",
|
"restoreProjectActive": "恢复为进行中",
|
||||||
"projectActions": "项目操作",
|
"projectActions": "项目操作",
|
||||||
"deleteProject": "删除项目",
|
"deleteProject": "删除项目",
|
||||||
|
|||||||
+52
-46
@@ -12,7 +12,6 @@ let _projectsFetchPromise = null;
|
|||||||
|
|
||||||
const PROJECT_ACTIVE_KEY = 'cyberstrike.activeProjectId';
|
const PROJECT_ACTIVE_KEY = 'cyberstrike.activeProjectId';
|
||||||
const PROJECT_DESCRIPTION_MAX_LENGTH = 4000;
|
const PROJECT_DESCRIPTION_MAX_LENGTH = 4000;
|
||||||
const PROJECT_DESC_COLLAPSE_THRESHOLD = 180;
|
|
||||||
|
|
||||||
function tp(key, opts) {
|
function tp(key, opts) {
|
||||||
if (typeof window.t === 'function') return window.t(key, opts);
|
if (typeof window.t === 'function') return window.t(key, opts);
|
||||||
@@ -625,57 +624,27 @@ function clampProjectDescription(text) {
|
|||||||
return s.slice(0, PROJECT_DESCRIPTION_MAX_LENGTH);
|
return s.slice(0, PROJECT_DESCRIPTION_MAX_LENGTH);
|
||||||
}
|
}
|
||||||
|
|
||||||
function projectDescriptionNeedsToggle(text) {
|
function renderProjectDetailTitle(name) {
|
||||||
const s = (text || '').trim();
|
const titleEl = document.getElementById('projects-detail-title');
|
||||||
if (!s) return false;
|
if (!titleEl) return;
|
||||||
if (s.length > PROJECT_DESC_COLLAPSE_THRESHOLD) return true;
|
const text = (name || '').trim() || tp('projects.defaultProjectName');
|
||||||
return s.split('\n').length > 3;
|
titleEl.textContent = text;
|
||||||
|
titleEl.title = text;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderProjectDetailDesc(desc) {
|
function renderProjectDetailDesc(desc) {
|
||||||
const blockEl = document.getElementById('projects-detail-desc-block');
|
|
||||||
const descEl = document.getElementById('projects-detail-desc');
|
const descEl = document.getElementById('projects-detail-desc');
|
||||||
const toggleEl = document.getElementById('projects-detail-desc-toggle');
|
if (!descEl) return;
|
||||||
if (!descEl || !blockEl) return;
|
|
||||||
const text = (desc || '').trim();
|
const text = (desc || '').trim();
|
||||||
if (!text) {
|
if (!text) {
|
||||||
blockEl.hidden = true;
|
descEl.hidden = true;
|
||||||
descEl.textContent = '';
|
descEl.textContent = '';
|
||||||
descEl.className = 'projects-detail-desc is-collapsed';
|
descEl.removeAttribute('title');
|
||||||
if (toggleEl) {
|
|
||||||
toggleEl.hidden = true;
|
|
||||||
toggleEl.dataset.expanded = 'false';
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
descEl.textContent = text;
|
descEl.textContent = text;
|
||||||
blockEl.hidden = false;
|
descEl.title = text;
|
||||||
descEl.classList.remove('is-expanded');
|
descEl.hidden = false;
|
||||||
descEl.classList.add('is-collapsed');
|
|
||||||
if (toggleEl) {
|
|
||||||
const needsToggle = projectDescriptionNeedsToggle(text);
|
|
||||||
toggleEl.hidden = !needsToggle;
|
|
||||||
toggleEl.textContent = tp('projects.descExpand');
|
|
||||||
toggleEl.dataset.expanded = 'false';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleProjectDetailDesc() {
|
|
||||||
const descEl = document.getElementById('projects-detail-desc');
|
|
||||||
const toggleEl = document.getElementById('projects-detail-desc-toggle');
|
|
||||||
if (!descEl || !toggleEl || toggleEl.hidden) return;
|
|
||||||
const expanded = toggleEl.dataset.expanded === 'true';
|
|
||||||
if (expanded) {
|
|
||||||
descEl.classList.add('is-collapsed');
|
|
||||||
descEl.classList.remove('is-expanded');
|
|
||||||
toggleEl.textContent = tp('projects.descExpand');
|
|
||||||
toggleEl.dataset.expanded = 'false';
|
|
||||||
} else {
|
|
||||||
descEl.classList.remove('is-collapsed');
|
|
||||||
descEl.classList.add('is-expanded');
|
|
||||||
toggleEl.textContent = tp('projects.descCollapse');
|
|
||||||
toggleEl.dataset.expanded = 'true';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateProjectStatusPill(status) {
|
function updateProjectStatusPill(status) {
|
||||||
@@ -731,8 +700,7 @@ async function selectProject(id) {
|
|||||||
const res = await apiFetch(`/api/projects/${id}`);
|
const res = await apiFetch(`/api/projects/${id}`);
|
||||||
if (!res.ok) throw new Error(tp('projects.projectNotFound'));
|
if (!res.ok) throw new Error(tp('projects.projectNotFound'));
|
||||||
const p = await res.json();
|
const p = await res.json();
|
||||||
const titleEl = document.getElementById('projects-detail-title');
|
renderProjectDetailTitle(p.name);
|
||||||
if (titleEl) titleEl.textContent = p.name || tp('projects.defaultProjectName');
|
|
||||||
document.getElementById('project-edit-name').value = p.name || '';
|
document.getElementById('project-edit-name').value = p.name || '';
|
||||||
document.getElementById('project-edit-description').value = p.description || '';
|
document.getElementById('project-edit-description').value = p.description || '';
|
||||||
document.getElementById('project-edit-scope').value = p.scope_json || '';
|
document.getElementById('project-edit-scope').value = p.scope_json || '';
|
||||||
@@ -743,8 +711,7 @@ async function selectProject(id) {
|
|||||||
updateProjectStatusPill(p.status || 'active');
|
updateProjectStatusPill(p.status || 'active');
|
||||||
const metaEl = document.getElementById('projects-detail-meta');
|
const metaEl = document.getElementById('projects-detail-meta');
|
||||||
if (metaEl) metaEl.textContent = tpFmt('projects.updatedPrefix', `Updated ${formatProjectTime(p.updated_at)}`, { time: formatProjectTime(p.updated_at) });
|
if (metaEl) metaEl.textContent = tpFmt('projects.updatedPrefix', `Updated ${formatProjectTime(p.updated_at)}`, { time: formatProjectTime(p.updated_at) });
|
||||||
const descEl = document.getElementById('projects-detail-desc');
|
renderProjectDetailDesc(p.description);
|
||||||
if (descEl) renderProjectDetailDesc(p.description);
|
|
||||||
projectNameById[p.id] = p.name || p.id;
|
projectNameById[p.id] = p.name || p.id;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn(e);
|
console.warn(e);
|
||||||
@@ -1246,6 +1213,33 @@ function showNewProjectModal() {
|
|||||||
openProjectsOverlay('project-modal');
|
openProjectsOverlay('project-modal');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function showEditProjectModal(projectId) {
|
||||||
|
if (!projectId) return;
|
||||||
|
window._projectModalFromChat = false;
|
||||||
|
window._projectModalEditId = projectId;
|
||||||
|
document.getElementById('project-modal-title').textContent = tp('projects.modalEditTitle');
|
||||||
|
const sub = document.getElementById('project-modal-subtitle');
|
||||||
|
if (sub) sub.textContent = tp('projects.modalEditSubtitle');
|
||||||
|
const submitBtn = document.getElementById('project-modal-submit-btn');
|
||||||
|
if (submitBtn) submitBtn.textContent = tp('projects.saveChanges');
|
||||||
|
let p = findProjectById(projectId);
|
||||||
|
if (!p) {
|
||||||
|
try {
|
||||||
|
const res = await apiFetch(`/api/projects/${encodeURIComponent(projectId)}`);
|
||||||
|
if (!res.ok) throw new Error(tp('projects.projectNotFound'));
|
||||||
|
p = await res.json();
|
||||||
|
} catch (e) {
|
||||||
|
alert(e.message || tp('projects.projectNotFound'));
|
||||||
|
window._projectModalEditId = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.getElementById('project-modal-name').value = p.name || '';
|
||||||
|
document.getElementById('project-modal-description').value = p.description || '';
|
||||||
|
openProjectsOverlay('project-modal');
|
||||||
|
setTimeout(() => document.getElementById('project-modal-name')?.focus(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
/** 从对话区「选择项目」面板打开新建项目,创建成功后自动绑定当前对话 */
|
/** 从对话区「选择项目」面板打开新建项目,创建成功后自动绑定当前对话 */
|
||||||
function showNewProjectModalFromChat() {
|
function showNewProjectModalFromChat() {
|
||||||
closeChatProjectPanel();
|
closeChatProjectPanel();
|
||||||
@@ -1285,6 +1279,7 @@ async function saveProjectModal() {
|
|||||||
|
|
||||||
function closeProjectModal() {
|
function closeProjectModal() {
|
||||||
window._projectModalFromChat = false;
|
window._projectModalFromChat = false;
|
||||||
|
window._projectModalEditId = null;
|
||||||
closeProjectsOverlay('project-modal');
|
closeProjectsOverlay('project-modal');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1390,8 +1385,10 @@ function showProjectListActionMenu(event, projectId) {
|
|||||||
const p = findProjectById(projectId);
|
const p = findProjectById(projectId);
|
||||||
if (!p) return;
|
if (!p) return;
|
||||||
_projectListMenuTargetId = projectId;
|
_projectListMenuTargetId = projectId;
|
||||||
|
const editText = document.getElementById('projects-list-menu-edit-text');
|
||||||
const archiveText = document.getElementById('projects-list-menu-archive-text');
|
const archiveText = document.getElementById('projects-list-menu-archive-text');
|
||||||
const deleteText = document.getElementById('projects-list-menu-delete-text');
|
const deleteText = document.getElementById('projects-list-menu-delete-text');
|
||||||
|
if (editText) editText.textContent = tp('projects.editProject');
|
||||||
if (archiveText) {
|
if (archiveText) {
|
||||||
archiveText.textContent = p.status === 'archived'
|
archiveText.textContent = p.status === 'archived'
|
||||||
? tp('projects.restoreProjectActive')
|
? tp('projects.restoreProjectActive')
|
||||||
@@ -1461,6 +1458,13 @@ async function toggleProjectArchiveFromListMenu() {
|
|||||||
await toggleProjectArchiveById(projectId);
|
await toggleProjectArchiveById(projectId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function editProjectFromListMenu() {
|
||||||
|
const projectId = _projectListMenuTargetId;
|
||||||
|
closeProjectListActionMenu();
|
||||||
|
if (!projectId) return;
|
||||||
|
showEditProjectModal(projectId);
|
||||||
|
}
|
||||||
|
|
||||||
async function deleteProjectFromListMenu() {
|
async function deleteProjectFromListMenu() {
|
||||||
const projectId = _projectListMenuTargetId;
|
const projectId = _projectListMenuTargetId;
|
||||||
closeProjectListActionMenu();
|
closeProjectListActionMenu();
|
||||||
@@ -1899,6 +1903,7 @@ if (document.readyState === 'loading') {
|
|||||||
|
|
||||||
window.initProjectsPage = initProjectsPage;
|
window.initProjectsPage = initProjectsPage;
|
||||||
window.showNewProjectModal = showNewProjectModal;
|
window.showNewProjectModal = showNewProjectModal;
|
||||||
|
window.showEditProjectModal = showEditProjectModal;
|
||||||
window.showNewProjectModalFromChat = showNewProjectModalFromChat;
|
window.showNewProjectModalFromChat = showNewProjectModalFromChat;
|
||||||
window.saveProjectModal = saveProjectModal;
|
window.saveProjectModal = saveProjectModal;
|
||||||
window.closeProjectModal = closeProjectModal;
|
window.closeProjectModal = closeProjectModal;
|
||||||
@@ -1914,6 +1919,7 @@ window.saveProjectSettings = saveProjectSettings;
|
|||||||
window.archiveCurrentProject = archiveCurrentProject;
|
window.archiveCurrentProject = archiveCurrentProject;
|
||||||
window.deleteCurrentProject = deleteCurrentProject;
|
window.deleteCurrentProject = deleteCurrentProject;
|
||||||
window.showProjectListActionMenu = showProjectListActionMenu;
|
window.showProjectListActionMenu = showProjectListActionMenu;
|
||||||
|
window.editProjectFromListMenu = editProjectFromListMenu;
|
||||||
window.toggleProjectArchiveFromListMenu = toggleProjectArchiveFromListMenu;
|
window.toggleProjectArchiveFromListMenu = toggleProjectArchiveFromListMenu;
|
||||||
window.deleteProjectFromListMenu = deleteProjectFromListMenu;
|
window.deleteProjectFromListMenu = deleteProjectFromListMenu;
|
||||||
window.refreshChatProjectSelector = refreshChatProjectSelector;
|
window.refreshChatProjectSelector = refreshChatProjectSelector;
|
||||||
|
|||||||
@@ -1476,10 +1476,7 @@
|
|||||||
<span id="projects-detail-status" class="projects-status-pill projects-status-pill--active" data-i18n="projects.statusActive">进行中</span>
|
<span id="projects-detail-status" class="projects-status-pill projects-status-pill--active" data-i18n="projects.statusActive">进行中</span>
|
||||||
</div>
|
</div>
|
||||||
<p id="projects-detail-meta" class="projects-detail-meta"></p>
|
<p id="projects-detail-meta" class="projects-detail-meta"></p>
|
||||||
<div id="projects-detail-desc-block" class="projects-detail-desc-block" hidden>
|
<p id="projects-detail-desc" class="projects-detail-desc" hidden></p>
|
||||||
<p id="projects-detail-desc" class="projects-detail-desc is-collapsed"></p>
|
|
||||||
<button type="button" id="projects-detail-desc-toggle" class="projects-detail-desc-toggle" hidden onclick="toggleProjectDetailDesc()"></button>
|
|
||||||
</div>
|
|
||||||
<div class="projects-detail-stats" id="projects-detail-stats">
|
<div class="projects-detail-stats" id="projects-detail-stats">
|
||||||
<span class="projects-stat-chip" id="project-stat-facts">0 条事实</span>
|
<span class="projects-stat-chip" id="project-stat-facts">0 条事实</span>
|
||||||
<span class="projects-stat-chip" id="project-stat-vulns">0 个漏洞</span>
|
<span class="projects-stat-chip" id="project-stat-vulns">0 个漏洞</span>
|
||||||
@@ -3783,6 +3780,9 @@
|
|||||||
|
|
||||||
<!-- 项目列表操作菜单 -->
|
<!-- 项目列表操作菜单 -->
|
||||||
<div id="projects-list-action-menu" class="context-menu" style="display: none;" role="menu">
|
<div id="projects-list-action-menu" class="context-menu" style="display: none;" role="menu">
|
||||||
|
<div id="projects-list-menu-edit" class="context-menu-item" onclick="editProjectFromListMenu()">
|
||||||
|
<span id="projects-list-menu-edit-text"></span>
|
||||||
|
</div>
|
||||||
<div id="projects-list-menu-archive" class="context-menu-item" onclick="toggleProjectArchiveFromListMenu()">
|
<div id="projects-list-menu-archive" class="context-menu-item" onclick="toggleProjectArchiveFromListMenu()">
|
||||||
<span id="projects-list-menu-archive-text"></span>
|
<span id="projects-list-menu-archive-text"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in new issue
Block a user