mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-07-13 07:36:35 +02:00
Add files via upload
This commit is contained in:
+2354
-5
File diff suppressed because it is too large
Load Diff
+366
-4
@@ -37,6 +37,15 @@
|
||||
"language": "Interface language",
|
||||
"backToDashboard": "Back to dashboard",
|
||||
"userMenu": "User menu",
|
||||
"userMenuFor": "User menu: {{name}}",
|
||||
"currentUser": "Current user",
|
||||
"unknownUser": "Unknown user",
|
||||
"scopeAll": "All resources",
|
||||
"scopeAssigned": "Assigned resources",
|
||||
"scopeOwn": "Own resources",
|
||||
"scopeUnknown": "Unknown scope",
|
||||
"rolesCount": "{{count}} roles",
|
||||
"permissionsCount": "{{count}} permissions",
|
||||
"version": "Current version",
|
||||
"toggleSidebar": "Collapse/expand sidebar"
|
||||
},
|
||||
@@ -59,7 +68,9 @@
|
||||
"login": {
|
||||
"title": "Sign in to CyberStrikeAI",
|
||||
"titlePrefix": "Sign in to",
|
||||
"subtitle": "Enter the access password from config",
|
||||
"subtitle": "Sign in with a platform account. The default administrator is admin.",
|
||||
"usernameLabel": "Username",
|
||||
"usernamePlaceholder": "admin",
|
||||
"passwordLabel": "Password",
|
||||
"passwordPlaceholder": "Enter password",
|
||||
"submit": "Sign in"
|
||||
@@ -95,7 +106,8 @@
|
||||
"c2Tasks": "Tasks",
|
||||
"c2Payloads": "Payload",
|
||||
"c2Events": "Events",
|
||||
"c2Profiles": "Traffic profiles"
|
||||
"c2Profiles": "Traffic profiles",
|
||||
"platformRbac": "Platform Access"
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Dashboard",
|
||||
@@ -812,6 +824,7 @@
|
||||
"running": "Running...",
|
||||
"completed": "Completed",
|
||||
"execFailed": "Execution failed",
|
||||
"resultMissing": "Result record missing",
|
||||
"toolSucceeded": "Succeeded",
|
||||
"toolFailed": "Failed"
|
||||
},
|
||||
@@ -1236,6 +1249,7 @@
|
||||
"robots": "Bots",
|
||||
"terminal": "Terminal",
|
||||
"security": "Security",
|
||||
"rbac": "Platform permissions",
|
||||
"audit": "Audit logs",
|
||||
"infocollect": "Recon"
|
||||
},
|
||||
@@ -1404,7 +1418,7 @@
|
||||
"sessionExpired": "Session expired, please sign in again",
|
||||
"unauthorized": "Unauthorized",
|
||||
"enterPassword": "Please enter password",
|
||||
"loginFailedCheck": "Sign-in failed, please check the password",
|
||||
"loginFailedCheck": "Sign-in failed, please check the username or password",
|
||||
"loginFailedRetry": "Sign-in failed, please try again later",
|
||||
"loggedOut": "Signed out"
|
||||
},
|
||||
@@ -3466,6 +3480,7 @@
|
||||
"paginationLast": "Last"
|
||||
},
|
||||
"payloads": {
|
||||
"386": "386",
|
||||
"title": "Payload Generator",
|
||||
"oneliner": "Oneliner Payload",
|
||||
"build": "Build Beacon",
|
||||
@@ -3485,7 +3500,6 @@
|
||||
"darwin": "macOS",
|
||||
"amd64": "AMD64",
|
||||
"arm64": "ARM64",
|
||||
"386": "386",
|
||||
"arm": "ARM",
|
||||
"onelinerDesc": "Generate a one-line reverse shell for the target (Bash / Python / PowerShell / Curl).",
|
||||
"buildDesc": "Cross-compile a full Beacon binary for Linux / Windows / macOS.",
|
||||
@@ -3568,5 +3582,353 @@
|
||||
"confirmDelete": "Delete this profile?",
|
||||
"submitCreate": "Create"
|
||||
}
|
||||
},
|
||||
"rbac": {
|
||||
"title": "Platform Access",
|
||||
"pageSubtitle": "Manage members, roles, and accessible data in one place.",
|
||||
"createUser": "Add Member",
|
||||
"memberManagement": "Member Management",
|
||||
"memberManagementHint": "Assign roles and data scope to members",
|
||||
"roleManagement": "Role Management",
|
||||
"roleManagementHint": "Define permission sets and default visibility",
|
||||
"users": "Users",
|
||||
"usersHint": "Select an account to manage roles and resource scope.",
|
||||
"searchUsers": "Search username or display name",
|
||||
"metricUsers": "Accounts",
|
||||
"metricEnabled": "Enabled",
|
||||
"metricRoles": "Roles",
|
||||
"metricAssignments": "Resource Grants",
|
||||
"metricUsersPill": "accounts",
|
||||
"metricEnabledPill": "enabled accounts",
|
||||
"metricRolesPill": "roles",
|
||||
"metricAssignmentsPill": "grants",
|
||||
"selectUser": "Select User",
|
||||
"selectUserHint": "Select a platform user to edit roles, status, and resource grants.",
|
||||
"createRole": "New Role",
|
||||
"tabRoles": "Roles & Permissions",
|
||||
"tabAssignments": "Resource Grants",
|
||||
"tabAudit": "Change History",
|
||||
"roleMembership": "Role Membership",
|
||||
"roleMembershipHint": "Roles control allowed actions; resource scope controls visible data.",
|
||||
"unsavedChanges": "Unsaved changes",
|
||||
"saveRoleChanges": "Save Role Changes",
|
||||
"resourceType": "Resource Type",
|
||||
"resourceId": "Resource ID",
|
||||
"resourceIdPlaceholder": "Enter resource IDs; separate multiple IDs with commas",
|
||||
"searchResource": "Search Real Resources",
|
||||
"searchResourcePlaceholder": "Search by name or ID",
|
||||
"assignSelected": "Grant Selected Resources",
|
||||
"advancedAssignment": "Advanced: Enter Resource IDs",
|
||||
"advancedAssignmentHint": "For known IDs copied from logs or APIs. Resource existence is still validated on submit.",
|
||||
"availableResources": "Available Resources",
|
||||
"availableResourcesHint": "Select on this page; selections persist across pages",
|
||||
"currentAssignments": "Current Explicit Grants",
|
||||
"allResourceTypes": "All Types",
|
||||
"searchAssignments": "Search granted resource names or IDs",
|
||||
"items": "items",
|
||||
"assignmentHelp": "Only Assigned Resources roles need explicit grants. Project grants also cover conversations and vulnerabilities in that project.",
|
||||
"assignResource": "Grant",
|
||||
"roleCatalog": "Role Catalog",
|
||||
"roleCatalogHint": "Define permissions and resource scope first, then assign the role to members.",
|
||||
"searchRoles": "Search roles or permissions",
|
||||
"filterRoles": "Filter Roles",
|
||||
"userModalTitle": "Platform User",
|
||||
"username": "Username",
|
||||
"displayName": "Display Name",
|
||||
"password": "Password",
|
||||
"passwordPlaceholder": "Leave blank when editing to keep unchanged",
|
||||
"enabled": "Enable account",
|
||||
"roles": "Roles",
|
||||
"roleModalTitle": "Platform Role",
|
||||
"roleName": "Role Name",
|
||||
"description": "Description",
|
||||
"scope": "Resource Scope",
|
||||
"permissions": "Permissions",
|
||||
"searchPermissions": "Search permissions, e.g. project:read",
|
||||
"selectVisible": "Select visible",
|
||||
"clearVisible": "Clear visible",
|
||||
"deleteUserConfirm": "Delete this platform user? This will revoke the user's active sessions immediately.",
|
||||
"deleteBuiltinUser": "The built-in admin user cannot be deleted",
|
||||
"deleteRoleConfirm": "Delete this platform role?",
|
||||
"scopeHint": "Resource scope and operation permissions jointly determine effective access.",
|
||||
"builtinAccount": "Built-in account",
|
||||
"statusEnabled": "Enabled",
|
||||
"statusDisabled": "Disabled",
|
||||
"noRoleAssigned": "No role assigned",
|
||||
"noExplicitGrant": "No explicit grants",
|
||||
"notConfigured": "Not configured",
|
||||
"noRoleDescription": "No role description",
|
||||
"noDescription": "No description",
|
||||
"systemBuiltin": "System role",
|
||||
"customRole": "Custom",
|
||||
"operationPermissions": "Operation permissions",
|
||||
"assignedMembers": "Assigned members",
|
||||
"viewPermissions": "View permissions",
|
||||
"effectivePermissions": "Effective permissions",
|
||||
"effectiveScope": "Effective resource scope",
|
||||
"revoke": "Revoke",
|
||||
"retry": "Retry",
|
||||
"alreadyAssigned": "Assigned",
|
||||
"loadingResources": "Loading real resources…",
|
||||
"viewSystemRole": "View System Role",
|
||||
"editRole": "Edit Role",
|
||||
"resourceCount": "{{count}} resources",
|
||||
"permissionCount": "{{count}} permissions",
|
||||
"userCount": "{{count}} users",
|
||||
"confirmDiscardSwitchUser": "Role changes are not saved. Discard them and switch members?",
|
||||
"confirmDiscardEditUser": "Role changes are not saved. Discard them and edit the member profile?",
|
||||
"confirmDiscardSwitchTab": "Role changes are not saved. Discard them and switch tabs?",
|
||||
"confirmDiscardResourceType": "Switching resource type clears the current selection. Continue?",
|
||||
"pendingGrants": "Pending Grants",
|
||||
"clearSelection": "Clear Selection",
|
||||
"confirmGrant": "Confirm Grant",
|
||||
"removePending": "Remove",
|
||||
"selectedCount": "{{count}} selected",
|
||||
"noRoleNoticeTitle": "No role assigned",
|
||||
"noRoleNoticeHint": "Assign a role before configuring resource grants. Without a role, only self-created data is accessible and most features are unavailable.",
|
||||
"assignRolesNow": "Assign Roles",
|
||||
"viewPermissionsDetail": "View details",
|
||||
"hidePermissions": "Hide details",
|
||||
"effectivePermissionsDetail": "Effective permission details",
|
||||
"editUserRolesHint": "Manage roles on the member detail page under Roles & Permissions.",
|
||||
"auditTitle": "Permission change history",
|
||||
"auditHint": "Audit events related to this member's platform access.",
|
||||
"unknownActor": "Unknown actor",
|
||||
"metricUserContext": "Selected member: {{name}} · {{roles}} roles · {{grants}} grants",
|
||||
"deleteUserConfirmDetailed": "Delete member {{name}} (@{{username}})?\n\nThis will revoke active sessions and remove {{roles}} role bindings and {{grants}} resource grants.",
|
||||
"auditActions": {
|
||||
"create_user": "Create platform user",
|
||||
"update_user": "Update platform user",
|
||||
"delete_user": "Delete platform user",
|
||||
"create_role": "Create platform role",
|
||||
"update_role": "Update platform role",
|
||||
"delete_role": "Delete platform role",
|
||||
"assign_resource": "Grant resource access",
|
||||
"delete_resource_assignment": "Revoke resource grant"
|
||||
},
|
||||
"copyId": "Copy ID",
|
||||
"copiedId": "Copied",
|
||||
"resourceIdLabel": "ID",
|
||||
"resourceDetailProject": "Project {{id}}",
|
||||
"pagination": {
|
||||
"previous": "Previous",
|
||||
"next": "Next",
|
||||
"resourcePage": "Page {{page}}",
|
||||
"pageSummary": "Page {{page}} / {{pages}} · {{total}} items",
|
||||
"emptySummary": "0 items"
|
||||
},
|
||||
"resourceTypes": {
|
||||
"project": "Project",
|
||||
"conversation": "Conversation",
|
||||
"vulnerability": "Vulnerability",
|
||||
"webshell": "WebShell Connection",
|
||||
"batch_task": "Batch Task",
|
||||
"c2_listener": "C2 Listener"
|
||||
},
|
||||
"scopes": {
|
||||
"all": {
|
||||
"label": "All Resources",
|
||||
"hint": "Can access all platform data",
|
||||
"option": "All Resources — Access all platform data"
|
||||
},
|
||||
"assigned": {
|
||||
"label": "Assigned Resources",
|
||||
"hint": "Only data explicitly granted by an administrator",
|
||||
"option": "Assigned Resources — Only administrator-granted data"
|
||||
},
|
||||
"own": {
|
||||
"label": "Created by Me",
|
||||
"hint": "Only data created by the member",
|
||||
"option": "Created by Me — Only self-created data"
|
||||
}
|
||||
},
|
||||
"systemRoles": {
|
||||
"admin": { "name": "Administrator", "description": "Full platform administration access" },
|
||||
"operator": { "name": "Operator", "description": "Run daily security workflows without account or core configuration management" },
|
||||
"auditor": { "name": "Auditor", "description": "Read-only access to audits, monitoring, and assets" },
|
||||
"viewer": { "name": "Read-only User", "description": "Read-only access to explicitly granted resources" }
|
||||
},
|
||||
"empty": {
|
||||
"noMatchingUsers": "No matching members",
|
||||
"noUsers": "No platform members yet",
|
||||
"tryAnotherKeyword": "Try another keyword",
|
||||
"addFirstUser": "Use “Add Member” to create the first member",
|
||||
"noMatchingRoles": "No matching roles",
|
||||
"adjustRoleSearch": "Adjust the search or create a custom role",
|
||||
"noRoles": "No roles available",
|
||||
"selectUserFirst": "Select a member first",
|
||||
"noAssignments": "No resource grants",
|
||||
"addAssignmentHint": "Select resources on the left, then use Confirm Grant above",
|
||||
"noMatchingAssignments": "No matching grants",
|
||||
"adjustAssignmentFilter": "Adjust the resource type or search",
|
||||
"noMatchingResources": "No matching real resources",
|
||||
"noMatchingPermissions": "No matching permissions",
|
||||
"noEffectivePermissions": "No effective permissions",
|
||||
"assignRoleFirst": "Assign a role to view permission details",
|
||||
"noAuditLogs": "No change history",
|
||||
"noAuditLogsHint": "Role and grant changes for this member will appear here"
|
||||
},
|
||||
"messages": {
|
||||
"roleChangesSaved": "Role changes saved and effective immediately",
|
||||
"grantPartial": "Added {{created}} grants; {{skipped}} already existed or were duplicated",
|
||||
"grantSuccess": "Granted {{count}} {{resource}} resources"
|
||||
},
|
||||
"errors": {
|
||||
"loadFailed": "Load failed",
|
||||
"loadDataFailed": "Unable to load access-control data",
|
||||
"loadResourcesFailed": "Unable to load resources",
|
||||
"saveFailed": "Save failed",
|
||||
"saveUserFailed": "Unable to save member",
|
||||
"saveRoleFailed": "Unable to save role",
|
||||
"deleteFailed": "Delete failed",
|
||||
"deleteUserFailed": "Unable to delete member",
|
||||
"deleteRoleFailed": "Unable to delete role",
|
||||
"enterResourceId": "Enter at least one resource ID",
|
||||
"tooManyResources": "A maximum of 100 resources can be granted at once",
|
||||
"batchGrantFailed": "Batch grant failed",
|
||||
"grantFailed": "Grant failed",
|
||||
"revokeFailed": "Revoke failed",
|
||||
"loadAuditFailed": "Unable to load audit history"
|
||||
},
|
||||
"permissionModules": {
|
||||
"agent": "Agent Execution",
|
||||
"agents": "Agent Definitions",
|
||||
"attackchain": "Attack Chains",
|
||||
"audit": "Audit",
|
||||
"auth": "Account Security",
|
||||
"c2": "C2",
|
||||
"chat": "Conversations",
|
||||
"config": "System Configuration",
|
||||
"dashboard": "Dashboard",
|
||||
"files": "Files",
|
||||
"fofa": "FOFA",
|
||||
"group": "Conversation Groups",
|
||||
"hitl": "Human-in-the-loop",
|
||||
"knowledge": "Knowledge Base",
|
||||
"mcp": "MCP",
|
||||
"monitor": "Execution Monitor",
|
||||
"notification": "Notifications",
|
||||
"openapi": "OpenAPI",
|
||||
"project": "Projects",
|
||||
"rbac": "Platform Access",
|
||||
"robot": "Robots",
|
||||
"roles": "Testing Roles",
|
||||
"skills": "Skills",
|
||||
"tasks": "Tasks",
|
||||
"terminal": "Terminal",
|
||||
"vulnerability": "Vulnerabilities",
|
||||
"webshell": "WebShell",
|
||||
"workflow": "Workflows"
|
||||
},
|
||||
"permissionDescriptions": {
|
||||
"auth": { "self": "Manage own session and password" },
|
||||
"dashboard": { "read": "View dashboard summaries" },
|
||||
"chat": {
|
||||
"read": "View conversations",
|
||||
"write": "Create and update conversations",
|
||||
"delete": "Delete conversations and turns"
|
||||
},
|
||||
"agent": { "execute": "Run AI agents and workflows" },
|
||||
"hitl": {
|
||||
"read": "View human-in-the-loop queues and logs",
|
||||
"write": "Approve, dismiss, and configure human-in-the-loop requests"
|
||||
},
|
||||
"tasks": {
|
||||
"read": "View task queues",
|
||||
"write": "Create and run task queues",
|
||||
"delete": "Delete task queues"
|
||||
},
|
||||
"project": {
|
||||
"read": "View projects and project facts",
|
||||
"write": "Create and update projects and project facts",
|
||||
"delete": "Delete projects and project facts"
|
||||
},
|
||||
"vulnerability": {
|
||||
"read": "View vulnerabilities",
|
||||
"write": "Create and update vulnerabilities",
|
||||
"delete": "Delete vulnerabilities"
|
||||
},
|
||||
"webshell": {
|
||||
"read": "View WebShell connections",
|
||||
"write": "Manage and use WebShell connections",
|
||||
"delete": "Delete WebShell connections"
|
||||
},
|
||||
"c2": {
|
||||
"read": "View C2 listeners, sessions, tasks, events, and profiles",
|
||||
"write": "Operate C2 listeners, sessions, tasks, payloads, files, and profiles",
|
||||
"delete": "Delete C2 objects"
|
||||
},
|
||||
"mcp": {
|
||||
"read": "View MCP status and external MCP configuration",
|
||||
"write": "Manage external MCP servers and invoke the MCP endpoint"
|
||||
},
|
||||
"knowledge": {
|
||||
"read": "View the knowledge base and retrieval logs",
|
||||
"write": "Create, update, index, and scan the knowledge base",
|
||||
"delete": "Delete knowledge items and retrieval logs"
|
||||
},
|
||||
"skills": {
|
||||
"read": "View Skills and statistics",
|
||||
"write": "Create and update Skills",
|
||||
"delete": "Delete Skills and statistics"
|
||||
},
|
||||
"agents": {
|
||||
"read": "View Markdown agents",
|
||||
"write": "Create and update Markdown agents",
|
||||
"delete": "Delete Markdown agents"
|
||||
},
|
||||
"roles": {
|
||||
"read": "View AI testing roles",
|
||||
"write": "Create and update AI testing roles",
|
||||
"delete": "Delete AI testing roles"
|
||||
},
|
||||
"workflow": {
|
||||
"read": "View workflow definitions and runs",
|
||||
"write": "Create, update, validate, run, and resume workflows",
|
||||
"delete": "Delete workflows"
|
||||
},
|
||||
"config": {
|
||||
"read": "View system configuration",
|
||||
"write": "Update and apply system configuration"
|
||||
},
|
||||
"terminal": { "execute": "Execute terminal commands" },
|
||||
"audit": {
|
||||
"read": "View and export audit logs",
|
||||
"delete": "Delete audit logs"
|
||||
},
|
||||
"rbac": {
|
||||
"read": "View users, platform roles, permissions, and grants",
|
||||
"write": "Manage users, platform roles, permissions, and grants"
|
||||
},
|
||||
"notification": {
|
||||
"read": "View notifications",
|
||||
"write": "Mark notifications as read"
|
||||
},
|
||||
"robot": {
|
||||
"read": "View robot binding status",
|
||||
"write": "Manage robot bindings and test callbacks"
|
||||
},
|
||||
"files": {
|
||||
"read": "View chat uploads",
|
||||
"write": "Upload, edit, and rename chat files",
|
||||
"delete": "Delete chat files"
|
||||
},
|
||||
"attackchain": {
|
||||
"read": "View attack chains",
|
||||
"write": "Regenerate attack chains"
|
||||
},
|
||||
"fofa": { "execute": "Run FOFA searches and parse queries" },
|
||||
"openapi": { "read": "Read OpenAPI aggregation results" },
|
||||
"group": {
|
||||
"read": "View conversation groups",
|
||||
"write": "Create and update conversation groups",
|
||||
"delete": "Delete conversation groups"
|
||||
},
|
||||
"monitor": {
|
||||
"read": "View the execution monitor",
|
||||
"write": "Cancel monitored executions",
|
||||
"delete": "Delete execution-monitor records"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+366
-4
@@ -37,6 +37,15 @@
|
||||
"language": "界面语言",
|
||||
"backToDashboard": "返回仪表盘",
|
||||
"userMenu": "用户菜单",
|
||||
"userMenuFor": "用户菜单:{{name}}",
|
||||
"currentUser": "当前用户",
|
||||
"unknownUser": "未知用户",
|
||||
"scopeAll": "全部资源",
|
||||
"scopeAssigned": "指定资源",
|
||||
"scopeOwn": "自己的资源",
|
||||
"scopeUnknown": "资源范围未知",
|
||||
"rolesCount": "{{count}} 个角色",
|
||||
"permissionsCount": "{{count}} 项权限",
|
||||
"version": "当前版本",
|
||||
"toggleSidebar": "折叠/展开侧边栏"
|
||||
},
|
||||
@@ -59,7 +68,9 @@
|
||||
"login": {
|
||||
"title": "登录 CyberStrikeAI",
|
||||
"titlePrefix": "登录",
|
||||
"subtitle": "请输入配置中的访问密码",
|
||||
"subtitle": "使用平台账号登录,默认管理员为 admin。",
|
||||
"usernameLabel": "用户名",
|
||||
"usernamePlaceholder": "admin",
|
||||
"passwordLabel": "密码",
|
||||
"passwordPlaceholder": "输入登录密码",
|
||||
"submit": "登录"
|
||||
@@ -95,7 +106,8 @@
|
||||
"c2Tasks": "任务",
|
||||
"c2Payloads": "载荷",
|
||||
"c2Events": "事件",
|
||||
"c2Profiles": "流量伪装"
|
||||
"c2Profiles": "流量伪装",
|
||||
"platformRbac": "平台权限"
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "仪表盘",
|
||||
@@ -800,6 +812,7 @@
|
||||
"running": "执行中...",
|
||||
"completed": "已完成",
|
||||
"execFailed": "执行失败",
|
||||
"resultMissing": "结果记录缺失",
|
||||
"toolSucceeded": "成功",
|
||||
"toolFailed": "失败"
|
||||
},
|
||||
@@ -1224,6 +1237,7 @@
|
||||
"robots": "机器人设置",
|
||||
"terminal": "终端",
|
||||
"security": "安全设置",
|
||||
"rbac": "平台权限",
|
||||
"audit": "日志审计",
|
||||
"infocollect": "信息收集"
|
||||
},
|
||||
@@ -1392,7 +1406,7 @@
|
||||
"sessionExpired": "认证已过期,请重新登录",
|
||||
"unauthorized": "未授权访问",
|
||||
"enterPassword": "请输入密码",
|
||||
"loginFailedCheck": "登录失败,请检查密码",
|
||||
"loginFailedCheck": "登录失败,请检查用户名或密码",
|
||||
"loginFailedRetry": "登录失败,请稍后重试",
|
||||
"loggedOut": "已退出登录"
|
||||
},
|
||||
@@ -3454,6 +3468,7 @@
|
||||
"paginationLast": "末页"
|
||||
},
|
||||
"payloads": {
|
||||
"386": "386",
|
||||
"title": "Payload 生成",
|
||||
"oneliner": "单行 Payload",
|
||||
"build": "编译 Beacon",
|
||||
@@ -3473,7 +3488,6 @@
|
||||
"darwin": "macOS",
|
||||
"amd64": "AMD64",
|
||||
"arm64": "ARM64",
|
||||
"386": "386",
|
||||
"arm": "ARM",
|
||||
"onelinerDesc": "快速生成可在目标机直接执行的反弹命令,支持 Bash / Python / PowerShell / Curl",
|
||||
"buildDesc": "交叉编译多平台完整 Beacon 可执行文件,支持 Linux / Windows / macOS",
|
||||
@@ -3556,5 +3570,353 @@
|
||||
"confirmDelete": "确定删除此 Profile?",
|
||||
"submitCreate": "创建"
|
||||
}
|
||||
},
|
||||
"rbac": {
|
||||
"title": "平台权限",
|
||||
"pageSubtitle": "统一管理成员、角色和可访问的数据范围。",
|
||||
"createUser": "添加成员",
|
||||
"memberManagement": "成员管理",
|
||||
"memberManagementHint": "为成员分配角色与数据范围",
|
||||
"roleManagement": "角色管理",
|
||||
"roleManagementHint": "定义权限集合与默认可见范围",
|
||||
"users": "用户",
|
||||
"usersHint": "按账号选择后管理角色与资源范围。",
|
||||
"searchUsers": "搜索用户名或显示名称",
|
||||
"metricUsers": "账号",
|
||||
"metricEnabled": "启用",
|
||||
"metricRoles": "角色",
|
||||
"metricAssignments": "资源授权",
|
||||
"metricUsersPill": "个账号",
|
||||
"metricEnabledPill": "个启用账号",
|
||||
"metricRolesPill": "个角色",
|
||||
"metricAssignmentsPill": "项授权",
|
||||
"selectUser": "选择用户",
|
||||
"selectUserHint": "选择一个平台用户后编辑角色、状态与资源授权。",
|
||||
"createRole": "新建角色",
|
||||
"tabRoles": "角色权限",
|
||||
"tabAssignments": "资源授权",
|
||||
"tabAudit": "变更记录",
|
||||
"roleMembership": "角色成员关系",
|
||||
"roleMembershipHint": "角色决定能做什么,资源范围决定能看到哪些数据。",
|
||||
"unsavedChanges": "有未保存的变更",
|
||||
"saveRoleChanges": "保存角色变更",
|
||||
"resourceType": "资源类型",
|
||||
"resourceId": "资源 ID",
|
||||
"resourceIdPlaceholder": "输入资源 ID;多个 ID 用逗号分隔",
|
||||
"searchResource": "搜索真实资源",
|
||||
"searchResourcePlaceholder": "按名称或 ID 搜索",
|
||||
"assignSelected": "授权所选资源",
|
||||
"advancedAssignment": "高级:直接输入资源 ID",
|
||||
"advancedAssignmentHint": "适用于从日志或 API 复制的已知 ID,提交时仍会验证资源是否存在。",
|
||||
"availableResources": "可授权资源",
|
||||
"availableResourcesHint": "勾选当前页资源,可跨页累计选择",
|
||||
"currentAssignments": "当前单独授权",
|
||||
"allResourceTypes": "全部类型",
|
||||
"searchAssignments": "搜索已授权资源名称或 ID",
|
||||
"items": "项",
|
||||
"assignmentHelp": "仅“指定资源”范围的角色需要单独授权。项目授权会覆盖该项目下的对话和漏洞。",
|
||||
"assignResource": "授权",
|
||||
"roleCatalog": "角色目录",
|
||||
"roleCatalogHint": "先定义角色的操作权限与资源范围,再分配给成员。",
|
||||
"searchRoles": "搜索角色或权限",
|
||||
"filterRoles": "筛选角色",
|
||||
"userModalTitle": "平台用户",
|
||||
"username": "用户名",
|
||||
"displayName": "显示名称",
|
||||
"password": "密码",
|
||||
"passwordPlaceholder": "编辑时留空表示不修改",
|
||||
"enabled": "启用账号",
|
||||
"roles": "角色",
|
||||
"roleModalTitle": "平台角色",
|
||||
"roleName": "角色名称",
|
||||
"description": "描述",
|
||||
"scope": "资源范围",
|
||||
"permissions": "权限",
|
||||
"searchPermissions": "搜索权限,例如 project:read",
|
||||
"selectVisible": "选择可见",
|
||||
"clearVisible": "清空可见",
|
||||
"deleteUserConfirm": "确认删除该平台用户?该操作会立即吊销该用户会话。",
|
||||
"deleteBuiltinUser": "内置管理员不能删除",
|
||||
"deleteRoleConfirm": "确认删除该平台角色?",
|
||||
"scopeHint": "资源范围与操作权限共同决定最终访问能力。",
|
||||
"builtinAccount": "内置账号",
|
||||
"statusEnabled": "启用",
|
||||
"statusDisabled": "停用",
|
||||
"noRoleAssigned": "未分配角色",
|
||||
"noExplicitGrant": "无单独授权",
|
||||
"notConfigured": "未设置",
|
||||
"noRoleDescription": "暂无角色说明",
|
||||
"noDescription": "无描述",
|
||||
"systemBuiltin": "系统内置",
|
||||
"customRole": "自定义",
|
||||
"operationPermissions": "操作权限",
|
||||
"assignedMembers": "已分配成员",
|
||||
"viewPermissions": "查看权限",
|
||||
"effectivePermissions": "有效权限",
|
||||
"effectiveScope": "有效资源范围",
|
||||
"revoke": "撤销",
|
||||
"retry": "重试",
|
||||
"alreadyAssigned": "已授权",
|
||||
"loadingResources": "正在加载真实资源…",
|
||||
"viewSystemRole": "查看系统角色",
|
||||
"editRole": "编辑角色",
|
||||
"resourceCount": "{{count}} 项资源",
|
||||
"permissionCount": "{{count}} 项权限",
|
||||
"userCount": "{{count}} 个用户",
|
||||
"confirmDiscardSwitchUser": "当前角色变更尚未保存,确定放弃并切换成员吗?",
|
||||
"confirmDiscardEditUser": "当前角色变更尚未保存,确定放弃并编辑成员资料吗?",
|
||||
"confirmDiscardSwitchTab": "当前角色变更尚未保存,确定放弃并切换标签吗?",
|
||||
"confirmDiscardResourceType": "切换资源类型将清空当前选择,确定继续吗?",
|
||||
"pendingGrants": "待授权",
|
||||
"clearSelection": "清空选择",
|
||||
"confirmGrant": "确认授权",
|
||||
"removePending": "移除",
|
||||
"selectedCount": "已选 {{count}} 项",
|
||||
"noRoleNoticeTitle": "尚未分配角色",
|
||||
"noRoleNoticeHint": "请先分配角色,再配置资源授权。无角色时仅可访问本人创建的数据,且大部分功能不可用。",
|
||||
"assignRolesNow": "去分配角色",
|
||||
"viewPermissionsDetail": "查看明细",
|
||||
"hidePermissions": "收起明细",
|
||||
"effectivePermissionsDetail": "有效权限明细",
|
||||
"editUserRolesHint": "角色请在成员详情页的「角色权限」标签中统一管理。",
|
||||
"auditTitle": "权限变更记录",
|
||||
"auditHint": "展示该成员相关的平台权限操作审计。",
|
||||
"unknownActor": "未知操作者",
|
||||
"metricUserContext": "当前成员:{{name}} · {{roles}} 个角色 · {{grants}} 项授权",
|
||||
"deleteUserConfirmDetailed": "确认删除成员 {{name}}(@{{username}})?\n\n将立即吊销其会话,并清除 {{roles}} 个角色绑定与 {{grants}} 项资源授权。",
|
||||
"auditActions": {
|
||||
"create_user": "创建平台用户",
|
||||
"update_user": "更新平台用户",
|
||||
"delete_user": "删除平台用户",
|
||||
"create_role": "创建平台角色",
|
||||
"update_role": "更新平台角色",
|
||||
"delete_role": "删除平台角色",
|
||||
"assign_resource": "授权资源访问",
|
||||
"delete_resource_assignment": "撤销资源授权"
|
||||
},
|
||||
"copyId": "复制 ID",
|
||||
"copiedId": "已复制",
|
||||
"resourceIdLabel": "ID",
|
||||
"resourceDetailProject": "所属项目 {{id}}",
|
||||
"pagination": {
|
||||
"previous": "上一页",
|
||||
"next": "下一页",
|
||||
"resourcePage": "第 {{page}} 页",
|
||||
"pageSummary": "第 {{page}} / {{pages}} 页 · 共 {{total}} 项",
|
||||
"emptySummary": "共 0 项"
|
||||
},
|
||||
"resourceTypes": {
|
||||
"project": "项目",
|
||||
"conversation": "对话",
|
||||
"vulnerability": "漏洞",
|
||||
"webshell": "WebShell 连接",
|
||||
"batch_task": "批量任务",
|
||||
"c2_listener": "C2 监听器"
|
||||
},
|
||||
"scopes": {
|
||||
"all": {
|
||||
"label": "全部资源",
|
||||
"hint": "可访问平台内全部数据",
|
||||
"option": "全部资源 — 可访问平台内所有数据"
|
||||
},
|
||||
"assigned": {
|
||||
"label": "指定资源",
|
||||
"hint": "仅访问管理员明确授权的数据",
|
||||
"option": "指定资源 — 仅访问管理员授权的数据"
|
||||
},
|
||||
"own": {
|
||||
"label": "本人创建",
|
||||
"hint": "仅访问自己创建的数据",
|
||||
"option": "本人创建 — 仅访问自己创建的数据"
|
||||
}
|
||||
},
|
||||
"systemRoles": {
|
||||
"admin": { "name": "管理员", "description": "全局管理权限" },
|
||||
"operator": { "name": "操作员", "description": "可执行日常安全工作流,不能管理账号与核心配置" },
|
||||
"auditor": { "name": "审计员", "description": "只读查看审计、监控与资产" },
|
||||
"viewer": { "name": "只读用户", "description": "只读查看被授权资源" }
|
||||
},
|
||||
"empty": {
|
||||
"noMatchingUsers": "没有匹配的成员",
|
||||
"noUsers": "还没有平台成员",
|
||||
"tryAnotherKeyword": "试试其他关键词",
|
||||
"addFirstUser": "点击右上角“添加成员”开始配置",
|
||||
"noMatchingRoles": "没有匹配的角色",
|
||||
"adjustRoleSearch": "调整搜索条件或新建自定义角色",
|
||||
"noRoles": "暂无角色",
|
||||
"selectUserFirst": "请先选择用户",
|
||||
"noAssignments": "暂无资源授权",
|
||||
"addAssignmentHint": "在左侧勾选资源,或使用上方「确认授权」按钮",
|
||||
"noMatchingAssignments": "没有匹配的授权",
|
||||
"adjustAssignmentFilter": "调整资源类型或搜索条件",
|
||||
"noMatchingResources": "没有匹配的真实资源",
|
||||
"noMatchingPermissions": "没有匹配的权限",
|
||||
"noEffectivePermissions": "暂无有效权限",
|
||||
"assignRoleFirst": "分配角色后即可查看权限明细",
|
||||
"noAuditLogs": "暂无变更记录",
|
||||
"noAuditLogsHint": "该成员的角色与授权变更会显示在这里"
|
||||
},
|
||||
"messages": {
|
||||
"roleChangesSaved": "角色变更已保存,权限立即生效",
|
||||
"grantPartial": "新增 {{created}} 项授权,{{skipped}} 项已存在或重复",
|
||||
"grantSuccess": "已授权 {{count}} 项{{resource}}"
|
||||
},
|
||||
"errors": {
|
||||
"loadFailed": "加载失败",
|
||||
"loadDataFailed": "无法加载权限数据",
|
||||
"loadResourcesFailed": "加载资源失败",
|
||||
"saveFailed": "保存失败",
|
||||
"saveUserFailed": "保存用户失败",
|
||||
"saveRoleFailed": "保存角色失败",
|
||||
"deleteFailed": "删除失败",
|
||||
"deleteUserFailed": "删除用户失败",
|
||||
"deleteRoleFailed": "删除角色失败",
|
||||
"enterResourceId": "请输入至少一个资源 ID",
|
||||
"tooManyResources": "一次最多授权 100 个资源",
|
||||
"batchGrantFailed": "批量授权失败",
|
||||
"grantFailed": "授权失败",
|
||||
"revokeFailed": "撤销失败",
|
||||
"loadAuditFailed": "加载审计记录失败"
|
||||
},
|
||||
"permissionModules": {
|
||||
"agent": "智能体",
|
||||
"agents": "智能体定义",
|
||||
"attackchain": "攻击链",
|
||||
"audit": "审计",
|
||||
"auth": "账户安全",
|
||||
"c2": "C2",
|
||||
"chat": "对话",
|
||||
"config": "系统配置",
|
||||
"dashboard": "仪表盘",
|
||||
"files": "文件",
|
||||
"fofa": "FOFA",
|
||||
"group": "对话分组",
|
||||
"hitl": "人机协同",
|
||||
"knowledge": "知识库",
|
||||
"mcp": "MCP",
|
||||
"monitor": "执行监控",
|
||||
"notification": "通知",
|
||||
"openapi": "OpenAPI",
|
||||
"project": "项目",
|
||||
"rbac": "平台权限",
|
||||
"robot": "机器人",
|
||||
"roles": "测试角色",
|
||||
"skills": "Skills",
|
||||
"tasks": "任务",
|
||||
"terminal": "终端",
|
||||
"vulnerability": "漏洞",
|
||||
"webshell": "WebShell",
|
||||
"workflow": "工作流"
|
||||
},
|
||||
"permissionDescriptions": {
|
||||
"auth": { "self": "管理自己的会话和密码" },
|
||||
"dashboard": { "read": "查看仪表盘汇总" },
|
||||
"chat": {
|
||||
"read": "查看对话",
|
||||
"write": "创建和更新对话",
|
||||
"delete": "删除对话和消息轮次"
|
||||
},
|
||||
"agent": { "execute": "运行 AI 智能体和工作流" },
|
||||
"hitl": {
|
||||
"read": "查看人机协同队列和日志",
|
||||
"write": "审批、驳回和配置人机协同"
|
||||
},
|
||||
"tasks": {
|
||||
"read": "查看任务队列",
|
||||
"write": "创建和运行任务队列",
|
||||
"delete": "删除任务队列"
|
||||
},
|
||||
"project": {
|
||||
"read": "查看项目和项目事实",
|
||||
"write": "创建和更新项目及项目事实",
|
||||
"delete": "删除项目和项目事实"
|
||||
},
|
||||
"vulnerability": {
|
||||
"read": "查看漏洞",
|
||||
"write": "创建和更新漏洞",
|
||||
"delete": "删除漏洞"
|
||||
},
|
||||
"webshell": {
|
||||
"read": "查看 WebShell 连接",
|
||||
"write": "管理和使用 WebShell 连接",
|
||||
"delete": "删除 WebShell 连接"
|
||||
},
|
||||
"c2": {
|
||||
"read": "查看 C2 监听器、会话、任务、事件和配置",
|
||||
"write": "操作 C2 监听器、会话、任务、载荷、文件和配置",
|
||||
"delete": "删除 C2 对象"
|
||||
},
|
||||
"mcp": {
|
||||
"read": "查看 MCP 状态和外部 MCP 配置",
|
||||
"write": "管理外部 MCP 服务并调用 MCP 端点"
|
||||
},
|
||||
"knowledge": {
|
||||
"read": "查看知识库和检索日志",
|
||||
"write": "创建、更新、索引和扫描知识库",
|
||||
"delete": "删除知识条目和检索日志"
|
||||
},
|
||||
"skills": {
|
||||
"read": "查看 Skills 和统计信息",
|
||||
"write": "创建和更新 Skills",
|
||||
"delete": "删除 Skills 和统计信息"
|
||||
},
|
||||
"agents": {
|
||||
"read": "查看 Markdown 智能体",
|
||||
"write": "创建和更新 Markdown 智能体",
|
||||
"delete": "删除 Markdown 智能体"
|
||||
},
|
||||
"roles": {
|
||||
"read": "查看 AI 测试角色",
|
||||
"write": "创建和更新 AI 测试角色",
|
||||
"delete": "删除 AI 测试角色"
|
||||
},
|
||||
"workflow": {
|
||||
"read": "查看工作流定义和运行记录",
|
||||
"write": "创建、更新、验证、运行和恢复工作流",
|
||||
"delete": "删除工作流"
|
||||
},
|
||||
"config": {
|
||||
"read": "查看系统配置",
|
||||
"write": "更新并应用系统配置"
|
||||
},
|
||||
"terminal": { "execute": "执行终端命令" },
|
||||
"audit": {
|
||||
"read": "查看和导出审计日志",
|
||||
"delete": "删除审计日志"
|
||||
},
|
||||
"rbac": {
|
||||
"read": "查看用户、平台角色、权限和授权关系",
|
||||
"write": "管理用户、平台角色、权限和授权关系"
|
||||
},
|
||||
"notification": {
|
||||
"read": "查看通知",
|
||||
"write": "将通知标记为已读"
|
||||
},
|
||||
"robot": {
|
||||
"read": "查看机器人绑定状态",
|
||||
"write": "管理机器人绑定并测试回调"
|
||||
},
|
||||
"files": {
|
||||
"read": "查看对话上传文件",
|
||||
"write": "上传、编辑和重命名对话文件",
|
||||
"delete": "删除对话文件"
|
||||
},
|
||||
"attackchain": {
|
||||
"read": "查看攻击链",
|
||||
"write": "重新生成攻击链"
|
||||
},
|
||||
"fofa": { "execute": "执行 FOFA 搜索和查询解析" },
|
||||
"openapi": { "read": "读取 OpenAPI 聚合结果" },
|
||||
"group": {
|
||||
"read": "查看对话分组",
|
||||
"write": "创建和更新对话分组",
|
||||
"delete": "删除对话分组"
|
||||
},
|
||||
"monitor": {
|
||||
"read": "查看执行监控",
|
||||
"write": "取消监控中的执行",
|
||||
"delete": "删除执行监控记录"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+207
-10
@@ -1,6 +1,10 @@
|
||||
const AUTH_STORAGE_KEY = 'cyberstrike-auth';
|
||||
let authToken = null;
|
||||
let authTokenExpiry = null;
|
||||
let authUser = null;
|
||||
let authRoles = [];
|
||||
let authPermissions = new Set();
|
||||
let authScope = '';
|
||||
let authPromise = null;
|
||||
let authPromiseResolvers = [];
|
||||
let isAppInitialized = false;
|
||||
@@ -9,28 +13,42 @@ function isTokenValid() {
|
||||
return !!authToken && authTokenExpiry instanceof Date && authTokenExpiry.getTime() > Date.now();
|
||||
}
|
||||
|
||||
function saveAuth(token, expiresAt) {
|
||||
function saveAuth(token, expiresAt, meta = {}) {
|
||||
const expiry = expiresAt instanceof Date ? expiresAt : new Date(expiresAt);
|
||||
authToken = token;
|
||||
authTokenExpiry = expiry;
|
||||
authUser = meta.user || null;
|
||||
authRoles = Array.isArray(meta.roles) ? meta.roles : [];
|
||||
authPermissions = new Set(Array.isArray(meta.permissions) ? meta.permissions : []);
|
||||
authScope = meta.scope || '';
|
||||
try {
|
||||
localStorage.setItem(AUTH_STORAGE_KEY, JSON.stringify({
|
||||
token,
|
||||
expiresAt: expiry.toISOString(),
|
||||
user: authUser,
|
||||
roles: authRoles,
|
||||
permissions: Array.from(authPermissions),
|
||||
scope: authScope,
|
||||
}));
|
||||
} catch (error) {
|
||||
console.warn('无法持久化认证信息:', error);
|
||||
}
|
||||
renderUserMenuProfile();
|
||||
}
|
||||
|
||||
function clearAuthStorage() {
|
||||
authToken = null;
|
||||
authTokenExpiry = null;
|
||||
authUser = null;
|
||||
authRoles = [];
|
||||
authPermissions = new Set();
|
||||
authScope = '';
|
||||
try {
|
||||
localStorage.removeItem(AUTH_STORAGE_KEY);
|
||||
} catch (error) {
|
||||
console.warn('无法清除认证信息:', error);
|
||||
}
|
||||
renderUserMenuProfile();
|
||||
}
|
||||
|
||||
function loadAuthFromStorage() {
|
||||
@@ -51,6 +69,10 @@ function loadAuthFromStorage() {
|
||||
}
|
||||
authToken = stored.token;
|
||||
authTokenExpiry = expiry;
|
||||
authUser = stored.user || null;
|
||||
authRoles = Array.isArray(stored.roles) ? stored.roles : [];
|
||||
authPermissions = new Set(Array.isArray(stored.permissions) ? stored.permissions : []);
|
||||
authScope = stored.scope || '';
|
||||
return isTokenValid();
|
||||
} catch (error) {
|
||||
console.error('读取认证信息失败:', error);
|
||||
@@ -68,6 +90,7 @@ function resolveAuthPromises(success) {
|
||||
function showLoginOverlay(message = '') {
|
||||
const overlay = document.getElementById('login-overlay');
|
||||
const errorBox = document.getElementById('login-error');
|
||||
const usernameInput = document.getElementById('login-username');
|
||||
const passwordInput = document.getElementById('login-password');
|
||||
if (!overlay) {
|
||||
return;
|
||||
@@ -83,7 +106,9 @@ function showLoginOverlay(message = '') {
|
||||
}
|
||||
}
|
||||
setTimeout(function () {
|
||||
if (passwordInput) {
|
||||
if (usernameInput && !usernameInput.value) {
|
||||
usernameInput.focus();
|
||||
} else if (passwordInput) {
|
||||
passwordInput.focus();
|
||||
}
|
||||
}, 100);
|
||||
@@ -92,6 +117,7 @@ function showLoginOverlay(message = '') {
|
||||
function hideLoginOverlay() {
|
||||
const overlay = document.getElementById('login-overlay');
|
||||
const errorBox = document.getElementById('login-error');
|
||||
const usernameInput = document.getElementById('login-username');
|
||||
const passwordInput = document.getElementById('login-password');
|
||||
closeAppModal('login-overlay');
|
||||
if (errorBox) {
|
||||
@@ -101,6 +127,9 @@ function hideLoginOverlay() {
|
||||
if (passwordInput) {
|
||||
passwordInput.value = '';
|
||||
}
|
||||
if (usernameInput && !authUser) {
|
||||
usernameInput.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
function ensureAuthPromise() {
|
||||
@@ -158,6 +187,13 @@ async function apiFetch(url, options = {}) {
|
||||
: '未授权访问';
|
||||
throw new Error(msg);
|
||||
}
|
||||
if (response.status === 403) {
|
||||
const result = await response.clone().json().catch(() => ({}));
|
||||
const msg = result.error || (typeof window !== 'undefined' && typeof window.t === 'function'
|
||||
? window.t('auth.forbidden')
|
||||
: '权限不足');
|
||||
throw new Error(msg);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
@@ -211,6 +247,7 @@ async function apiUploadWithProgress(url, formData, options = {}) {
|
||||
|
||||
async function submitLogin(event) {
|
||||
event.preventDefault();
|
||||
const usernameInput = document.getElementById('login-username');
|
||||
const passwordInput = document.getElementById('login-password');
|
||||
const errorBox = document.getElementById('login-error');
|
||||
const submitBtn = document.querySelector('.login-submit');
|
||||
@@ -219,6 +256,7 @@ async function submitLogin(event) {
|
||||
return;
|
||||
}
|
||||
|
||||
const username = usernameInput ? usernameInput.value.trim() : '';
|
||||
const password = passwordInput.value.trim();
|
||||
if (!password) {
|
||||
if (errorBox) {
|
||||
@@ -241,7 +279,7 @@ async function submitLogin(event) {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ password }),
|
||||
body: JSON.stringify({ username, password }),
|
||||
});
|
||||
const result = await response.json().catch(() => ({}));
|
||||
if (!response.ok || !result.token) {
|
||||
@@ -255,8 +293,14 @@ async function submitLogin(event) {
|
||||
return;
|
||||
}
|
||||
|
||||
saveAuth(result.token, result.expires_at);
|
||||
saveAuth(result.token, result.expires_at, {
|
||||
user: result.user,
|
||||
roles: result.roles,
|
||||
permissions: result.permissions,
|
||||
scope: result.scope,
|
||||
});
|
||||
hideLoginOverlay();
|
||||
applyRBACToUI();
|
||||
resolveAuthPromises(true);
|
||||
if (!isAppInitialized) {
|
||||
await bootstrapApp();
|
||||
@@ -306,9 +350,151 @@ async function bootstrapApp() {
|
||||
initializeChatUI();
|
||||
isAppInitialized = true;
|
||||
}
|
||||
applyRBACToUI();
|
||||
await refreshAppData();
|
||||
}
|
||||
|
||||
const PAGE_PERMISSION_MAP = {
|
||||
dashboard: 'dashboard:read',
|
||||
chat: 'chat:read',
|
||||
hitl: 'hitl:read',
|
||||
'info-collect': 'fofa:execute',
|
||||
tasks: 'tasks:read',
|
||||
workflows: 'workflow:read',
|
||||
projects: 'project:read',
|
||||
vulnerabilities: 'vulnerability:read',
|
||||
'chat-files': 'files:read',
|
||||
webshell: 'webshell:read',
|
||||
c2: 'c2:read',
|
||||
'c2-listeners': 'c2:read',
|
||||
'c2-sessions': 'c2:read',
|
||||
'c2-tasks': 'c2:read',
|
||||
'c2-payloads': 'c2:read',
|
||||
'c2-events': 'c2:read',
|
||||
'c2-profiles': 'c2:read',
|
||||
mcp: 'mcp:read',
|
||||
'mcp-monitor': 'monitor:read',
|
||||
'mcp-management': 'mcp:read',
|
||||
knowledge: 'knowledge:read',
|
||||
'knowledge-retrieval-logs': 'knowledge:read',
|
||||
'knowledge-management': 'knowledge:read',
|
||||
skills: 'skills:read',
|
||||
'skills-monitor': 'skills:read',
|
||||
'skills-management': 'skills:read',
|
||||
agents: 'agents:read',
|
||||
'agents-management': 'agents:read',
|
||||
roles: 'roles:read',
|
||||
'roles-management': 'roles:read',
|
||||
'platform-rbac': 'rbac:read',
|
||||
settings: 'config:read',
|
||||
};
|
||||
|
||||
function hasPermission(permission) {
|
||||
return !permission || authPermissions.has(permission);
|
||||
}
|
||||
|
||||
function applyRBACToUI() {
|
||||
document.querySelectorAll('[data-page]').forEach((el) => {
|
||||
const page = el.getAttribute('data-page');
|
||||
const permission = PAGE_PERMISSION_MAP[page];
|
||||
if (!permission) return;
|
||||
const allowed = hasPermission(permission);
|
||||
el.hidden = !allowed;
|
||||
el.setAttribute('aria-hidden', allowed ? 'false' : 'true');
|
||||
});
|
||||
const userAvatar = document.querySelector('.user-avatar-btn');
|
||||
if (userAvatar && authUser && authUser.username) {
|
||||
const displayName = getAuthDisplayName();
|
||||
userAvatar.setAttribute('title', displayName);
|
||||
userAvatar.setAttribute('aria-label', authT('header.userMenuFor', '用户菜单:{{name}}', { name: displayName }));
|
||||
}
|
||||
renderUserMenuProfile();
|
||||
}
|
||||
|
||||
function authT(key, fallback, opts = {}) {
|
||||
if (typeof window !== 'undefined' && typeof window.t === 'function') {
|
||||
const translated = window.t(key, opts);
|
||||
if (translated && translated !== key) {
|
||||
return translated;
|
||||
}
|
||||
}
|
||||
return fallback.replace(/\{\{\s*(\w+)\s*\}\}/g, function (_, name) {
|
||||
return Object.prototype.hasOwnProperty.call(opts, name) ? String(opts[name]) : '';
|
||||
});
|
||||
}
|
||||
|
||||
function getAuthDisplayName() {
|
||||
if (!authUser) {
|
||||
return authT('header.unknownUser', '未知用户');
|
||||
}
|
||||
return String(authUser.display_name || authUser.displayName || authUser.username || '').trim()
|
||||
|| authT('header.unknownUser', '未知用户');
|
||||
}
|
||||
|
||||
function getAuthUsername() {
|
||||
if (!authUser) {
|
||||
return '-';
|
||||
}
|
||||
const username = String(authUser.username || '').trim();
|
||||
return username ? `@${username}` : '-';
|
||||
}
|
||||
|
||||
function getScopeLabel(scope) {
|
||||
const normalized = String(scope || '').trim().toLowerCase();
|
||||
const keyMap = {
|
||||
all: 'header.scopeAll',
|
||||
assigned: 'header.scopeAssigned',
|
||||
own: 'header.scopeOwn',
|
||||
};
|
||||
const fallbackMap = {
|
||||
all: '全部资源',
|
||||
assigned: '指定资源',
|
||||
own: '自己的资源',
|
||||
};
|
||||
const key = keyMap[normalized] || 'header.scopeUnknown';
|
||||
const fallback = fallbackMap[normalized] || '资源范围未知';
|
||||
return authT(key, fallback);
|
||||
}
|
||||
|
||||
function renderUserMenuProfile() {
|
||||
const displayNameEl = document.getElementById('user-menu-display-name');
|
||||
const usernameEl = document.getElementById('user-menu-username');
|
||||
const scopeEl = document.getElementById('user-menu-scope');
|
||||
const rolesEl = document.getElementById('user-menu-roles');
|
||||
const permissionsEl = document.getElementById('user-menu-permissions');
|
||||
const avatarBtn = document.getElementById('user-avatar-btn') || document.querySelector('.user-avatar-btn');
|
||||
|
||||
const displayName = getAuthDisplayName();
|
||||
const roleCount = Array.isArray(authRoles) ? authRoles.length : 0;
|
||||
const permissionCount = authPermissions instanceof Set ? authPermissions.size : 0;
|
||||
|
||||
if (displayNameEl) displayNameEl.textContent = displayName;
|
||||
if (usernameEl) usernameEl.textContent = getAuthUsername();
|
||||
if (scopeEl) scopeEl.textContent = getScopeLabel(authScope);
|
||||
if (rolesEl) rolesEl.textContent = authT('header.rolesCount', '{{count}} 个角色', { count: roleCount });
|
||||
if (permissionsEl) permissionsEl.textContent = authT('header.permissionsCount', '{{count}} 项权限', { count: permissionCount });
|
||||
if (avatarBtn && authUser) {
|
||||
avatarBtn.setAttribute('title', displayName);
|
||||
avatarBtn.setAttribute('aria-label', authT('header.userMenuFor', '用户菜单:{{name}}', { name: displayName }));
|
||||
} else if (avatarBtn) {
|
||||
avatarBtn.setAttribute('aria-label', authT('header.userMenu', '用户菜单'));
|
||||
}
|
||||
}
|
||||
|
||||
function setUserMenuOpen(open) {
|
||||
const dropdown = document.getElementById('user-menu-dropdown');
|
||||
const avatarBtn = document.getElementById('user-avatar-btn') || document.querySelector('.user-avatar-btn');
|
||||
if (!dropdown) return;
|
||||
dropdown.style.display = open ? 'block' : 'none';
|
||||
if (avatarBtn) {
|
||||
avatarBtn.classList.toggle('active', open);
|
||||
avatarBtn.setAttribute('aria-expanded', open ? 'true' : 'false');
|
||||
}
|
||||
if (open) {
|
||||
renderUserMenuProfile();
|
||||
}
|
||||
}
|
||||
|
||||
// 通用工具函数
|
||||
function getStatusText(status) {
|
||||
const s = (status && String(status).toLowerCase()) || '';
|
||||
@@ -366,7 +552,15 @@ async function initializeApp() {
|
||||
method: 'GET',
|
||||
});
|
||||
if (response.ok) {
|
||||
const result = await response.json().catch(() => ({}));
|
||||
saveAuth(result.token || authToken, result.expires_at || authTokenExpiry, {
|
||||
user: result.user || authUser,
|
||||
roles: result.roles || authRoles,
|
||||
permissions: result.permissions || Array.from(authPermissions),
|
||||
scope: result.scope || authScope,
|
||||
});
|
||||
hideLoginOverlay();
|
||||
applyRBACToUI();
|
||||
resolveAuthPromises(true);
|
||||
await bootstrapApp();
|
||||
return;
|
||||
@@ -386,7 +580,7 @@ function toggleUserMenu() {
|
||||
if (!dropdown) return;
|
||||
|
||||
const isVisible = dropdown.style.display !== 'none';
|
||||
dropdown.style.display = isVisible ? 'none' : 'block';
|
||||
setUserMenuOpen(!isVisible);
|
||||
}
|
||||
|
||||
// 点击页面其他地方时关闭下拉菜单
|
||||
@@ -397,17 +591,18 @@ document.addEventListener('click', function(event) {
|
||||
if (dropdown && avatarBtn &&
|
||||
!dropdown.contains(event.target) &&
|
||||
!avatarBtn.contains(event.target)) {
|
||||
dropdown.style.display = 'none';
|
||||
setUserMenuOpen(false);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('languagechange', function () {
|
||||
renderUserMenuProfile();
|
||||
});
|
||||
|
||||
// 退出登录
|
||||
async function logout() {
|
||||
// 关闭下拉菜单
|
||||
const dropdown = document.getElementById('user-menu-dropdown');
|
||||
if (dropdown) {
|
||||
dropdown.style.display = 'none';
|
||||
}
|
||||
setUserMenuOpen(false);
|
||||
|
||||
try {
|
||||
// 先尝试调用退出API(如果token有效)
|
||||
@@ -434,5 +629,7 @@ async function logout() {
|
||||
// 导出函数供HTML使用
|
||||
window.toggleUserMenu = toggleUserMenu;
|
||||
window.logout = logout;
|
||||
window.hasPermission = hasPermission;
|
||||
window.applyRBACToUI = applyRBACToUI;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', initializeApp);
|
||||
|
||||
+118
-8
@@ -2458,6 +2458,32 @@ function syncProcessDetailButtonLabels(messageId, expanded) {
|
||||
});
|
||||
}
|
||||
|
||||
/** 懒加载占位提示可点击,与工具栏「展开详情」行为一致 */
|
||||
function bindProcessDetailsLazyHint(hostEl, messageId) {
|
||||
if (!hostEl || !messageId) return;
|
||||
const emptyEl = hostEl.classList && hostEl.classList.contains('progress-timeline-empty')
|
||||
? hostEl
|
||||
: hostEl.querySelector('.progress-timeline-empty');
|
||||
if (!emptyEl || emptyEl.dataset.lazyHintBound === '1') return;
|
||||
emptyEl.dataset.lazyHintBound = '1';
|
||||
emptyEl.classList.add('progress-timeline-lazy-clickable');
|
||||
emptyEl.setAttribute('role', 'button');
|
||||
emptyEl.setAttribute('tabindex', '0');
|
||||
const activate = () => {
|
||||
if (typeof toggleProcessDetails === 'function') {
|
||||
toggleProcessDetails(null, messageId);
|
||||
}
|
||||
};
|
||||
emptyEl.addEventListener('click', activate);
|
||||
emptyEl.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
activate();
|
||||
}
|
||||
});
|
||||
}
|
||||
window.bindProcessDetailsLazyHint = bindProcessDetailsLazyHint;
|
||||
|
||||
// 渲染过程详情
|
||||
// options.append=true 时分页追加;options.markLoaded=false 时保留 lazy 标记(分页加载中)
|
||||
function renderProcessDetails(messageId, processDetails, options) {
|
||||
@@ -2465,6 +2491,13 @@ function renderProcessDetails(messageId, processDetails, options) {
|
||||
const appendMode = !!renderOpts.append;
|
||||
const prependMode = !!renderOpts.prepend;
|
||||
const markLoaded = renderOpts.markLoaded !== false;
|
||||
const toolStatusByProcessDetailId = new Map();
|
||||
if (Array.isArray(renderOpts.toolExecutions)) {
|
||||
renderOpts.toolExecutions.forEach((execution) => {
|
||||
if (!execution || !execution.processDetailId) return;
|
||||
toolStatusByProcessDetailId.set(String(execution.processDetailId), String(execution.status || '').toLowerCase());
|
||||
});
|
||||
}
|
||||
const messageElement = document.getElementById(messageId);
|
||||
if (!messageElement) {
|
||||
return;
|
||||
@@ -2529,6 +2562,7 @@ function renderProcessDetails(messageId, processDetails, options) {
|
||||
const expandLabel = typeof window.t === 'function' ? window.t('chat.expandDetail') : '展开详情';
|
||||
let lazyHint = expandLabel + '(点击后加载迭代详情)';
|
||||
timeline.innerHTML = '<div class="progress-timeline-empty">' + lazyHint + '</div>';
|
||||
bindProcessDetailsLazyHint(timeline, messageId);
|
||||
timeline.classList.remove('expanded');
|
||||
prefetchProcessDetailsSummaryHint(messageId, messageElement);
|
||||
return;
|
||||
@@ -2725,6 +2759,9 @@ function renderProcessDetails(messageId, processDetails, options) {
|
||||
processDetailId: detail.id || '',
|
||||
createdAt: detail.createdAt
|
||||
};
|
||||
if (eventType === 'tool_call' && detail.id && toolStatusByProcessDetailId.has(String(detail.id))) {
|
||||
timelineOpts.toolStatus = toolStatusByProcessDetailId.get(String(detail.id));
|
||||
}
|
||||
if (eventType === 'tool_call' && data._mergedResult) {
|
||||
timelineOpts.mergedResult = data._mergedResult;
|
||||
}
|
||||
@@ -2781,6 +2818,7 @@ function finishProcessDetailsRender(messageElement, processDetails, isLazyNotLoa
|
||||
lazyHint.textContent = (typeof window.t === 'function' ? window.t('chat.expandDetail') : '展开详情') +
|
||||
'(点击后加载完整过程详情)';
|
||||
timeline.appendChild(lazyHint);
|
||||
bindProcessDetailsLazyHint(lazyHint, messageElement.id);
|
||||
}
|
||||
|
||||
const hasPendingHitlInDetails = processDetails.some(d => d && d.eventType === 'hitl_interrupt');
|
||||
@@ -2841,6 +2879,7 @@ function prefetchProcessDetailsSummaryHint(messageId, messageElement) {
|
||||
const empty = timeline.querySelector('.progress-timeline-empty');
|
||||
if (empty) {
|
||||
empty.textContent = hint;
|
||||
bindProcessDetailsLazyHint(timeline, messageId);
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
@@ -2999,16 +3038,42 @@ function normalizeToolExecutionSummaryForButton(raw) {
|
||||
};
|
||||
}
|
||||
|
||||
function setPendingToolExecutionSummaries(messageElement, summaries) {
|
||||
if (!messageElement || !messageElement.dataset || !Array.isArray(summaries)) return;
|
||||
function cacheToolExecutionSummaries(messageElement, summaries) {
|
||||
if (!messageElement || !messageElement.dataset || !Array.isArray(summaries)) return [];
|
||||
const normalized = summaries
|
||||
.map(normalizeToolExecutionSummaryForButton)
|
||||
.filter((item) => item.toolName || item.executionId || item.toolCallId);
|
||||
if (normalized.length > 0) {
|
||||
messageElement.dataset.toolExecutionSummaries = JSON.stringify(normalized);
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function getCachedToolExecutionSummaries(messageElement) {
|
||||
if (!messageElement || !messageElement.dataset || !messageElement.dataset.toolExecutionSummaries) return [];
|
||||
try {
|
||||
const parsed = JSON.parse(messageElement.dataset.toolExecutionSummaries);
|
||||
return Array.isArray(parsed) ? parsed.map(normalizeToolExecutionSummaryForButton) : [];
|
||||
} catch (e) {
|
||||
delete messageElement.dataset.toolExecutionSummaries;
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function setPendingToolExecutionSummaries(messageElement, summaries) {
|
||||
if (!messageElement || !messageElement.dataset || !Array.isArray(summaries)) return;
|
||||
const normalized = cacheToolExecutionSummaries(messageElement, summaries);
|
||||
if (normalized.length > 0) {
|
||||
messageElement.dataset.pendingToolExecutionSummaries = JSON.stringify(normalized);
|
||||
} else {
|
||||
delete messageElement.dataset.pendingToolExecutionSummaries;
|
||||
}
|
||||
const renderedToolList = messageElement.querySelector('.mcp-tool-list');
|
||||
if (normalized.length > 0 && renderedToolList && renderedToolList.querySelector('.mcp-detail-btn[data-exec-id], .mcp-detail-btn[data-tool-summary]')) {
|
||||
appendMcpCallSummaryButtons(messageElement, normalized);
|
||||
delete messageElement.dataset.pendingToolExecutionSummaries;
|
||||
delete messageElement.dataset.pendingMcpExecutionIds;
|
||||
}
|
||||
if (typeof syncMcpToolsToggleButton === 'function') {
|
||||
syncMcpToolsToggleButton(messageElement);
|
||||
}
|
||||
@@ -3202,6 +3267,34 @@ async function focusToolExecutionInProcessDetails(messageElement, summary, index
|
||||
}, 2200);
|
||||
}
|
||||
|
||||
async function resolveToolExecutionSummaryForFocus(messageElement, executionId, index) {
|
||||
const wantedExecutionId = executionId == null ? '' : String(executionId).trim();
|
||||
let summaries = getCachedToolExecutionSummaries(messageElement);
|
||||
let item = wantedExecutionId
|
||||
? summaries.find((summary) => summary.executionId === wantedExecutionId)
|
||||
: summaries[index];
|
||||
if (item && (item.processDetailId || item.toolCallId)) return item;
|
||||
|
||||
const backendId = messageElement && messageElement.dataset
|
||||
? String(messageElement.dataset.backendMessageId || '').trim()
|
||||
: '';
|
||||
if (!backendId || typeof apiFetch !== 'function') return item || null;
|
||||
try {
|
||||
const res = await apiFetch('/api/messages/' + encodeURIComponent(backendId) + '/process-details?summary=1');
|
||||
const payload = await res.json().catch(() => ({}));
|
||||
if (!res.ok || !payload.summary || !Array.isArray(payload.summary.toolExecutions)) {
|
||||
return item || null;
|
||||
}
|
||||
summaries = cacheToolExecutionSummaries(messageElement, payload.summary.toolExecutions);
|
||||
item = wantedExecutionId
|
||||
? summaries.find((summary) => summary.executionId === wantedExecutionId)
|
||||
: summaries[index];
|
||||
return item || null;
|
||||
} catch (e) {
|
||||
return item || null;
|
||||
}
|
||||
}
|
||||
|
||||
function toggleMcpToolList(assistantMessageId) {
|
||||
const messageEl = document.getElementById(assistantMessageId);
|
||||
if (!messageEl) return;
|
||||
@@ -3255,7 +3348,14 @@ function appendMcpCallButtons(messageElement, executionIds) {
|
||||
detailBtn.dataset.execId = execId;
|
||||
detailBtn.dataset.execIndex = String(index + 1);
|
||||
detailBtn.innerHTML = '<span>' + (typeof window.t === 'function' ? window.t('chat.callNumber', { n: index + 1 }) : '调用 #' + (index + 1)) + '</span>';
|
||||
detailBtn.onclick = () => showMCPDetail(execId);
|
||||
detailBtn.onclick = async () => {
|
||||
const summary = await resolveToolExecutionSummaryForFocus(messageElement, execId, index);
|
||||
if (summary && (summary.processDetailId || summary.toolCallId)) {
|
||||
await focusToolExecutionInProcessDetails(messageElement, summary, index);
|
||||
return;
|
||||
}
|
||||
showMCPDetail(execId);
|
||||
};
|
||||
toolList.appendChild(detailBtn);
|
||||
});
|
||||
batchUpdateButtonToolNames(toolList, executionIds);
|
||||
@@ -3273,10 +3373,16 @@ function appendMcpCallSummaryButtons(messageElement, summaries) {
|
||||
const item = normalizeToolExecutionSummaryForButton(raw);
|
||||
const key = item.executionId || item.toolCallId || `${item.toolName || 'tool'}-${index + 1}`;
|
||||
const selector = '.mcp-detail-btn[data-tool-summary="' + CSS.escape(String(key)) + '"]';
|
||||
if (toolList.querySelector(selector) || (item.executionId && toolList.querySelector('.mcp-detail-btn[data-exec-id="' + CSS.escape(String(item.executionId)) + '"]'))) {
|
||||
const existingSummaryBtn = toolList.querySelector(selector);
|
||||
const existingExecBtn = item.executionId
|
||||
? toolList.querySelector('.mcp-detail-btn[data-exec-id="' + CSS.escape(String(item.executionId)) + '"]')
|
||||
: null;
|
||||
if (existingSummaryBtn) {
|
||||
return;
|
||||
}
|
||||
const btn = document.createElement('button');
|
||||
// 历史会话可能先按 executionId 渲染旧按钮,随后摘要才异步到达。
|
||||
// 复用并升级该按钮,避免它永久保留“只看执行详情、不定位上下文”的旧处理器。
|
||||
const btn = existingExecBtn || document.createElement('button');
|
||||
btn.className = 'mcp-detail-btn';
|
||||
btn.dataset.toolSummary = key;
|
||||
if (item.executionId) {
|
||||
@@ -3294,7 +3400,9 @@ function appendMcpCallSummaryButtons(messageElement, summaries) {
|
||||
};
|
||||
}
|
||||
renderToolExecutionButtonContent(btn, item.toolName || (typeof window.t === 'function' ? window.t('chat.unknownTool') : '未知工具'), String(index + 1), item.status);
|
||||
toolList.appendChild(btn);
|
||||
if (!existingExecBtn) {
|
||||
toolList.appendChild(btn);
|
||||
}
|
||||
});
|
||||
syncMcpToolsToggleButton(messageElement);
|
||||
}
|
||||
@@ -3363,7 +3471,8 @@ function getToolExecutionStatusLabel(status) {
|
||||
failed: 'mcpMonitor.statusFailed',
|
||||
running: 'mcpMonitor.statusRunning',
|
||||
cancelled: 'mcpMonitor.statusCancelled',
|
||||
pending: 'mcpMonitor.statusPending'
|
||||
pending: 'mcpMonitor.statusPending',
|
||||
result_missing: 'timeline.resultMissing'
|
||||
};
|
||||
const key = keyMap[normalized];
|
||||
if (key) {
|
||||
@@ -3376,7 +3485,8 @@ function getToolExecutionStatusLabel(status) {
|
||||
failed: '失败',
|
||||
running: '运行中',
|
||||
cancelled: '已取消',
|
||||
pending: '等待中'
|
||||
pending: '等待中',
|
||||
result_missing: '结果记录缺失'
|
||||
};
|
||||
return fallback[normalized] || '';
|
||||
}
|
||||
|
||||
+109
-41
@@ -1357,6 +1357,10 @@ function integrateProgressToMCPSection(progressId, assistantMessageId, mcpExecut
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
if (typeof window.bindProcessDetailsLazyHint === 'function') {
|
||||
const tl = detailsContainer.querySelector('.progress-timeline');
|
||||
if (tl) window.bindProcessDetailsLazyHint(tl, assistantMessageId);
|
||||
}
|
||||
}
|
||||
|
||||
const expandLabel = typeof window.t === 'function' ? window.t('chat.expandDetail') : '展开详情';
|
||||
@@ -1492,11 +1496,13 @@ async function loadProcessDetailsPaginated(assistantMessageId, backendMessageId,
|
||||
throw new Error((j && j.error) ? j.error : String(res.status));
|
||||
}
|
||||
const details = (j && Array.isArray(j.processDetails)) ? j.processDetails : [];
|
||||
const toolExecutions = (j && Array.isArray(j.toolExecutions)) ? j.toolExecutions : [];
|
||||
const hasMore = !!(j && j.hasMore);
|
||||
renderProcessDetails(assistantMessageId, details, {
|
||||
append: !isFirst || opts.append,
|
||||
prepend: prepend,
|
||||
markLoaded: autoLoadAll ? !hasMore : true
|
||||
markLoaded: autoLoadAll ? !hasMore : true,
|
||||
toolExecutions: toolExecutions
|
||||
});
|
||||
const responseOffset = j && typeof j.offset === 'number' ? j.offset : offset;
|
||||
const total = j && typeof j.total === 'number' ? j.total : responseOffset + details.length;
|
||||
@@ -1525,6 +1531,81 @@ async function loadProcessDetailsPaginated(assistantMessageId, backendMessageId,
|
||||
|
||||
window.loadProcessDetailsPaginated = loadProcessDetailsPaginated;
|
||||
|
||||
function resolveEventBackendMessageId(eventData) {
|
||||
if (!eventData || typeof eventData !== 'object') return '';
|
||||
const raw = eventData.messageId != null ? eventData.messageId : eventData.assistantMessageId;
|
||||
return raw != null ? String(raw).trim() : '';
|
||||
}
|
||||
|
||||
function triggerLazyProcessDetailsLoad(assistantMessageId, backendMessageId, detailsContainer) {
|
||||
if (!assistantMessageId || !backendMessageId || !detailsContainer) return false;
|
||||
if (detailsContainer.dataset.loading === '1') return false;
|
||||
const collapseT = typeof window.t === 'function' ? window.t('tasks.collapseDetail') : '收起详情';
|
||||
detailsContainer.dataset.loading = '1';
|
||||
const timeline = detailsContainer.querySelector('.progress-timeline');
|
||||
if (timeline) {
|
||||
timeline.innerHTML = '<div class="progress-timeline-empty">' + ((typeof window.t === 'function') ? window.t('common.loading') : '加载中…') + '</div>';
|
||||
}
|
||||
loadProcessDetailsPaginated(assistantMessageId, backendMessageId, { autoLoadAll: false })
|
||||
.catch((e) => {
|
||||
console.error('加载过程详情失败:', e);
|
||||
const tl = detailsContainer.querySelector('.progress-timeline');
|
||||
if (tl) {
|
||||
tl.innerHTML = '<div class="progress-timeline-empty">' + ((typeof window.t === 'function') ? window.t('chat.noProcessDetail') : '暂无过程详情(加载失败)') + '</div>';
|
||||
if (typeof window.bindProcessDetailsLazyHint === 'function') {
|
||||
window.bindProcessDetailsLazyHint(tl, assistantMessageId);
|
||||
}
|
||||
}
|
||||
detailsContainer.dataset.lazyNotLoaded = '1';
|
||||
detailsContainer.dataset.loaded = '0';
|
||||
})
|
||||
.finally(() => {
|
||||
detailsContainer.dataset.loading = '0';
|
||||
if (detailsContainer.dataset.userExpanded === '1') {
|
||||
const tl = detailsContainer.querySelector('.progress-timeline');
|
||||
if (tl) {
|
||||
tl.classList.add('expanded');
|
||||
}
|
||||
if (typeof syncProcessDetailButtonLabels === 'function') {
|
||||
syncProcessDetailButtonLabels(assistantMessageId, true);
|
||||
} else {
|
||||
document.querySelectorAll('#' + assistantMessageId + ' .process-detail-btn').forEach((btn) => {
|
||||
btn.innerHTML = '<span>' + collapseT + '</span>';
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
function maybeReloadLazyProcessDetails(assistantMessageId) {
|
||||
const detailsContainer = document.getElementById('process-details-' + assistantMessageId);
|
||||
if (!detailsContainer) return;
|
||||
const isLazy = detailsContainer.dataset.lazyNotLoaded === '1' && detailsContainer.dataset.loaded !== '1';
|
||||
if (!isLazy) return;
|
||||
const timeline = detailsContainer.querySelector('.progress-timeline');
|
||||
const wantsExpanded = detailsContainer.dataset.userExpanded === '1' ||
|
||||
!!(timeline && timeline.classList.contains('expanded'));
|
||||
if (!wantsExpanded) return;
|
||||
const messageEl = document.getElementById(assistantMessageId);
|
||||
const backendId = messageEl && messageEl.dataset ? String(messageEl.dataset.backendMessageId || '').trim() : '';
|
||||
if (!backendId) return;
|
||||
triggerLazyProcessDetailsLoad(assistantMessageId, backendId, detailsContainer);
|
||||
}
|
||||
|
||||
function scheduleProcessDetailsLoadWhenReady(assistantMessageId, detailsContainer, attempt) {
|
||||
if (!assistantMessageId || !detailsContainer) return;
|
||||
const tries = typeof attempt === 'number' ? attempt : 0;
|
||||
if (tries > 25) return;
|
||||
const messageEl = document.getElementById(assistantMessageId);
|
||||
const backendId = messageEl && messageEl.dataset ? String(messageEl.dataset.backendMessageId || '').trim() : '';
|
||||
if (backendId) {
|
||||
triggerLazyProcessDetailsLoad(assistantMessageId, backendId, detailsContainer);
|
||||
return;
|
||||
}
|
||||
setTimeout(() => scheduleProcessDetailsLoadWhenReady(assistantMessageId, detailsContainer, tries + 1), 200);
|
||||
}
|
||||
|
||||
// 切换过程详情显示
|
||||
function toggleProcessDetails(progressId, assistantMessageId) {
|
||||
const detailsId = 'process-details-' + assistantMessageId;
|
||||
@@ -1539,42 +1620,10 @@ function toggleProcessDetails(progressId, assistantMessageId) {
|
||||
if (maybeLazy) {
|
||||
const messageEl = document.getElementById(assistantMessageId);
|
||||
const backendMessageId = messageEl && messageEl.dataset ? messageEl.dataset.backendMessageId : '';
|
||||
if (backendMessageId && typeof apiFetch === 'function' && typeof renderProcessDetails === 'function') {
|
||||
if (detailsContainer.dataset.loading === '1') {
|
||||
// 正在加载中,避免重复请求
|
||||
} else {
|
||||
detailsContainer.dataset.loading = '1';
|
||||
const timeline = detailsContainer.querySelector('.progress-timeline');
|
||||
if (timeline) {
|
||||
timeline.innerHTML = '<div class="progress-timeline-empty">' + ((typeof window.t === 'function') ? window.t('common.loading') : '加载中…') + '</div>';
|
||||
}
|
||||
loadProcessDetailsPaginated(assistantMessageId, backendMessageId, { autoLoadAll: false })
|
||||
.catch((e) => {
|
||||
console.error('加载过程详情失败:', e);
|
||||
const tl = detailsContainer.querySelector('.progress-timeline');
|
||||
if (tl) {
|
||||
tl.innerHTML = '<div class="progress-timeline-empty">' + ((typeof window.t === 'function') ? window.t('chat.noProcessDetail') : '暂无过程详情(加载失败)') + '</div>';
|
||||
}
|
||||
detailsContainer.dataset.lazyNotLoaded = '1';
|
||||
detailsContainer.dataset.loaded = '0';
|
||||
})
|
||||
.finally(() => {
|
||||
detailsContainer.dataset.loading = '0';
|
||||
if (detailsContainer.dataset.userExpanded === '1') {
|
||||
const tl = detailsContainer.querySelector('.progress-timeline');
|
||||
if (tl) {
|
||||
tl.classList.add('expanded');
|
||||
}
|
||||
if (typeof syncProcessDetailButtonLabels === 'function') {
|
||||
syncProcessDetailButtonLabels(assistantMessageId, true);
|
||||
} else {
|
||||
document.querySelectorAll('#' + assistantMessageId + ' .process-detail-btn').forEach((btn) => {
|
||||
btn.innerHTML = '<span>' + collapseT + '</span>';
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
if (backendMessageId) {
|
||||
triggerLazyProcessDetailsLoad(assistantMessageId, backendMessageId, detailsContainer);
|
||||
} else {
|
||||
scheduleProcessDetailsLoadWhenReady(assistantMessageId, detailsContainer, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1647,6 +1696,7 @@ function applyBackendMessageIdToAssistantDom(domAssistantId, backendMessageId) {
|
||||
if (typeof attachDeleteTurnButton === 'function') {
|
||||
attachDeleteTurnButton(el);
|
||||
}
|
||||
maybeReloadLazyProcessDetails(domAssistantId);
|
||||
}
|
||||
|
||||
/** 将后端用户消息 ID 绑定到最后一条尚未绑定 backendMessageId 的用户气泡 */
|
||||
@@ -2480,7 +2530,7 @@ function handleStreamEvent(event, progressElement, progressId,
|
||||
|
||||
// 复用已有助手消息(若有),避免终态事件重复插入消息
|
||||
{
|
||||
const preferredMessageId = event.data && event.data.messageId ? event.data.messageId : null;
|
||||
const preferredMessageId = resolveEventBackendMessageId(event.data) || null;
|
||||
const { assistantId, assistantElement } = upsertTerminalAssistantMessage(event.message, preferredMessageId);
|
||||
if (assistantId && preferredMessageId) {
|
||||
applyBackendMessageIdToAssistantDom(assistantId, preferredMessageId);
|
||||
@@ -2489,6 +2539,8 @@ function handleStreamEvent(event, progressElement, progressId,
|
||||
const detailsId = 'process-details-' + assistantId;
|
||||
if (!document.getElementById(detailsId)) {
|
||||
integrateProgressToMCPSection(progressId, assistantId, typeof getMcpIds === 'function' ? (getMcpIds() || []) : []);
|
||||
} else if (preferredMessageId) {
|
||||
maybeReloadLazyProcessDetails(assistantId);
|
||||
}
|
||||
setTimeout(() => {
|
||||
collapseAllProgressDetails(assistantId, progressId);
|
||||
@@ -2501,7 +2553,7 @@ function handleStreamEvent(event, progressElement, progressId,
|
||||
// Close any remaining running tool calls for this progress.
|
||||
finalizeOutstandingToolCallsForProgress(progressId, 'failed');
|
||||
break;
|
||||
|
||||
|
||||
case 'response_start': {
|
||||
const responseTaskState = progressTaskState.get(progressId);
|
||||
const responseOriginalConversationId = responseTaskState?.conversationId;
|
||||
@@ -2721,7 +2773,7 @@ function handleStreamEvent(event, progressElement, progressId,
|
||||
|
||||
// 复用已有助手消息(若有),避免终态事件重复插入消息
|
||||
{
|
||||
const preferredMessageId = event.data && event.data.messageId ? event.data.messageId : null;
|
||||
const preferredMessageId = resolveEventBackendMessageId(event.data) || null;
|
||||
const { assistantId, assistantElement } = upsertTerminalAssistantMessage(event.message, preferredMessageId);
|
||||
if (assistantId && preferredMessageId) {
|
||||
applyBackendMessageIdToAssistantDom(assistantId, preferredMessageId);
|
||||
@@ -2730,6 +2782,8 @@ function handleStreamEvent(event, progressElement, progressId,
|
||||
const detailsId = 'process-details-' + assistantId;
|
||||
if (!document.getElementById(detailsId)) {
|
||||
integrateProgressToMCPSection(progressId, assistantId, typeof getMcpIds === 'function' ? (getMcpIds() || []) : []);
|
||||
} else if (preferredMessageId) {
|
||||
maybeReloadLazyProcessDetails(assistantId);
|
||||
}
|
||||
setTimeout(() => {
|
||||
collapseAllProgressDetails(assistantId, progressId);
|
||||
@@ -3961,6 +4015,7 @@ function addTimelineItem(timeline, type, options) {
|
||||
item.dataset.toolCallId = String(d.toolCallId).trim();
|
||||
}
|
||||
const merged = options.mergedResult || d._mergedResult;
|
||||
const terminalStatus = String(options.toolStatus || '').toLowerCase();
|
||||
if (merged) {
|
||||
item.dataset.toolResultMerged = '1';
|
||||
item.dataset.toolSuccess = merged.success !== false ? '1' : '0';
|
||||
@@ -3968,6 +4023,12 @@ function addTimelineItem(timeline, type, options) {
|
||||
if (d._mergedResultDetailId) {
|
||||
item.dataset.toolResultDetailId = String(d._mergedResultDetailId);
|
||||
}
|
||||
} else if (terminalStatus === 'completed' || terminalStatus === 'failed') {
|
||||
item.dataset.toolSuccess = terminalStatus === 'completed' ? '1' : '0';
|
||||
item.classList.add(terminalStatus === 'completed' ? 'tool-call-completed' : 'tool-call-failed');
|
||||
} else if (terminalStatus === 'result_missing') {
|
||||
item.classList.add('tool-call-incomplete');
|
||||
item.title = typeof window.t === 'function' ? window.t('timeline.resultMissing') : '结果记录缺失';
|
||||
}
|
||||
}
|
||||
if (type === 'hitl_interrupt' && options.data && options.data.interruptId != null && String(options.data.interruptId).trim() !== '') {
|
||||
@@ -4041,19 +4102,26 @@ function addTimelineItem(timeline, type, options) {
|
||||
const data = options.data;
|
||||
const args = parseToolCallArgsFromData(data);
|
||||
const merged = options.mergedResult || data._mergedResult;
|
||||
const terminalStatus = String(options.toolStatus || '').toLowerCase();
|
||||
const hasTerminalStatus = terminalStatus === 'completed' || terminalStatus === 'failed';
|
||||
const hasHistoricalStatus = hasTerminalStatus || terminalStatus === 'result_missing';
|
||||
if (merged) {
|
||||
if (merged.success !== false) {
|
||||
item.classList.add('tool-call-completed');
|
||||
} else {
|
||||
item.classList.add('tool-call-failed');
|
||||
}
|
||||
} else if (hasTerminalStatus) {
|
||||
item.classList.add(terminalStatus === 'completed' ? 'tool-call-completed' : 'tool-call-failed');
|
||||
} else if (terminalStatus === 'result_missing') {
|
||||
item.classList.add('tool-call-incomplete');
|
||||
} else if (!options.skipPendingResult) {
|
||||
item.classList.add('tool-call-running');
|
||||
}
|
||||
setToolCallDetailState(item, {
|
||||
args: args,
|
||||
resultData: merged || null,
|
||||
pending: !merged && !options.skipPendingResult,
|
||||
pending: !merged && !hasHistoricalStatus && !options.skipPendingResult,
|
||||
processDetailId: options.processDetailId || '',
|
||||
resultDetailId: data._mergedResultDetailId || '',
|
||||
payloadDeferred: data._payloadDeferred === true || (merged && merged._payloadDeferred === true),
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -81,7 +81,7 @@ function initRouter() {
|
||||
const hashParts = hash.split('?');
|
||||
let pageId = hashParts[0];
|
||||
if (pageId === 'c2') pageId = 'c2-listeners';
|
||||
if (pageId && ['dashboard', 'chat', 'hitl', 'info-collect', 'projects', 'vulnerabilities', 'webshell', 'chat-files', 'mcp-monitor', 'mcp-management', 'knowledge-management', 'knowledge-retrieval-logs', 'roles-management', 'workflows', 'skills-monitor', 'skills-management', 'agents-management', 'settings', 'tasks', 'c2-listeners', 'c2-sessions', 'c2-tasks', 'c2-payloads', 'c2-events', 'c2-profiles'].includes(pageId)) {
|
||||
if (pageId && ['dashboard', 'chat', 'hitl', 'info-collect', 'projects', 'vulnerabilities', 'webshell', 'chat-files', 'mcp-monitor', 'mcp-management', 'knowledge-management', 'knowledge-retrieval-logs', 'roles-management', 'platform-rbac', 'workflows', 'skills-monitor', 'skills-management', 'agents-management', 'settings', 'tasks', 'c2-listeners', 'c2-sessions', 'c2-tasks', 'c2-payloads', 'c2-events', 'c2-profiles'].includes(pageId)) {
|
||||
switchPage(pageId);
|
||||
if (pageId === 'chat') {
|
||||
scheduleChatConversationFromHash(500);
|
||||
@@ -472,6 +472,11 @@ async function initPage(pageId) {
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'platform-rbac':
|
||||
if (typeof initPlatformRbacPage === 'function') {
|
||||
initPlatformRbacPage();
|
||||
}
|
||||
break;
|
||||
case 'workflows':
|
||||
if (typeof refreshWorkflows === 'function') {
|
||||
refreshWorkflows();
|
||||
@@ -538,7 +543,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
let pageId = hashParts[0];
|
||||
|
||||
if (pageId === 'c2') pageId = 'c2-listeners';
|
||||
if (pageId && ['dashboard', 'chat', 'hitl', 'info-collect', 'projects', 'tasks', 'workflows', 'vulnerabilities', 'webshell', 'chat-files', 'mcp-monitor', 'mcp-management', 'knowledge-management', 'knowledge-retrieval-logs', 'roles-management', 'skills-monitor', 'skills-management', 'agents-management', 'settings', 'c2-listeners', 'c2-sessions', 'c2-tasks', 'c2-payloads', 'c2-events', 'c2-profiles'].includes(pageId)) {
|
||||
if (pageId && ['dashboard', 'chat', 'hitl', 'info-collect', 'projects', 'tasks', 'workflows', 'vulnerabilities', 'webshell', 'chat-files', 'mcp-monitor', 'mcp-management', 'knowledge-management', 'knowledge-retrieval-logs', 'roles-management', 'platform-rbac', 'skills-monitor', 'skills-management', 'agents-management', 'settings', 'c2-listeners', 'c2-sessions', 'c2-tasks', 'c2-payloads', 'c2-events', 'c2-profiles'].includes(pageId)) {
|
||||
switchPage(pageId);
|
||||
if (pageId === 'chat') {
|
||||
scheduleChatConversationFromHash(200);
|
||||
|
||||
@@ -509,6 +509,13 @@ function syncC2NavFromConfig(cfg) {
|
||||
|
||||
// 切换设置分类
|
||||
function switchSettingsSection(section) {
|
||||
if (section === 'rbac') {
|
||||
if (typeof switchPage === 'function') {
|
||||
switchPage('platform-rbac');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新导航项状态
|
||||
document.querySelectorAll('.settings-nav-item').forEach(item => {
|
||||
item.classList.remove('active');
|
||||
@@ -3660,3 +3667,6 @@ document.addEventListener('languagechange', function () {
|
||||
console.warn('languagechange MCP refresh failed', e);
|
||||
}
|
||||
});
|
||||
|
||||
window.initSettingsCustomSelects = initSettingsCustomSelects;
|
||||
window.refreshSettingsCustomSelects = refreshSettingsCustomSelects;
|
||||
|
||||
@@ -1048,6 +1048,7 @@ function probeWebshellConnection(conn) {
|
||||
cmd_param: conn.cmdParam || '',
|
||||
encoding: webshellConnEncoding(conn),
|
||||
os: webshellConnOS(conn),
|
||||
connection_id: conn.id || '',
|
||||
command: buildWebshellProbeCommand(probeToken)
|
||||
})
|
||||
})
|
||||
@@ -3854,6 +3855,7 @@ function execWebshellCommand(conn, command) {
|
||||
cmd_param: conn.cmdParam || '',
|
||||
encoding: webshellConnEncoding(conn),
|
||||
os: webshellConnOS(conn),
|
||||
connection_id: conn.id || '',
|
||||
command: command
|
||||
})
|
||||
}).then(function (r) { return r.json(); })
|
||||
|
||||
Reference in New Issue
Block a user