From cca6796176b3a9207a3e6c5fe6f1d86465c14921 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=AC=E6=98=8E?= <83812544+Ed1s0nZ@users.noreply.github.com> Date: Tue, 7 Jul 2026 16:32:15 +0800 Subject: [PATCH] Add files via upload --- web/static/css/c2.css | 150 ++++++ web/static/css/style.css | 920 +++++++++++++++++++++++++++++++++++- web/static/js/c2.js | 170 ++++++- web/static/js/chat-files.js | 156 ++++++ web/static/js/hitl.js | 154 ++++++ web/static/js/projects.js | 192 +++++++- web/static/js/roles.js | 155 ++++++ web/static/js/tasks.js | 153 ++++++ web/static/js/workflows.js | 170 ++++++- web/templates/index.html | 26 +- 10 files changed, 2221 insertions(+), 25 deletions(-) diff --git a/web/static/css/c2.css b/web/static/css/c2.css index 89af3bbc..f95048b1 100644 --- a/web/static/css/c2.css +++ b/web/static/css/c2.css @@ -179,6 +179,156 @@ html[data-theme="dark"] .c2-session-main-empty__icon { padding-right: 14px; } +.c2-modal select.form-control.c2-native-select { + appearance: auto; + -webkit-appearance: menulist-button; + background-image: none; + padding-right: 14px; +} + +.c2-form-native-select { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +.c2-form-select-ui { + position: relative; + width: 100%; + min-width: 0; +} + +.c2-form-select-trigger { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + width: 100%; + min-width: 0; + min-height: 40px; + padding: 10px 14px; + border: 1px solid var(--c2-border); + border-radius: var(--c2-radius-sm); + background: var(--c2-surface); + color: var(--c2-text); + font-size: 14px; + line-height: 1.25; + cursor: pointer; + font-family: inherit; + transition: border-color 0.15s ease, box-shadow 0.15s ease; +} + +.c2-form-select-trigger:hover:not(:disabled) { + border-color: var(--c2-accent); +} + +.c2-form-select-ui.open .c2-form-select-trigger { + border-color: var(--c2-accent); + box-shadow: 0 0 0 2px var(--c2-accent-dim); +} + +.c2-form-select-ui.open { + z-index: 120; +} + +.c2-form-select-ui.is-disabled .c2-form-select-trigger { + opacity: 0.6; + cursor: not-allowed; +} + +.c2-form-select-value { + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + text-align: left; +} + +.c2-form-select-caret { + flex-shrink: 0; + color: var(--c2-text-muted); + transition: transform 0.15s ease; +} + +.c2-form-select-ui.open .c2-form-select-caret { + transform: rotate(180deg); +} + +.c2-form-select-dropdown { + display: none; + position: absolute; + top: calc(100% + 4px); + left: 0; + right: 0; + z-index: 200; + max-height: 280px; + overflow-y: auto; + padding: 4px; + background: var(--c2-surface); + border: 1px solid var(--c2-border); + border-radius: var(--c2-radius-sm); + box-shadow: var(--c2-shadow-lg); +} + +.c2-form-select-ui.open .c2-form-select-dropdown { + display: block; +} + +.c2-form-select-option { + display: flex; + align-items: center; + gap: 8px; + width: 100%; + padding: 8px 10px; + border: none; + border-radius: var(--c2-radius-xs); + background: transparent; + color: var(--c2-text); + font-size: 13px; + font-family: inherit; + cursor: pointer; + text-align: left; + transition: background 0.12s ease, color 0.12s ease; +} + +.c2-form-select-option:hover { + background: var(--c2-surface-alt); +} + +.c2-form-select-option.is-selected { + background: var(--c2-accent-dim); + color: var(--c2-accent); + font-weight: 600; +} + +.c2-form-select-check { + width: 14px; + flex-shrink: 0; + visibility: hidden; + color: var(--c2-accent); + font-size: 12px; + line-height: 1; +} + +.c2-form-select-option.is-selected .c2-form-select-check { + visibility: visible; +} + +.c2-form-select-label { + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + #page-c2-payloads textarea.form-control, .c2-modal textarea.form-control { resize: vertical; diff --git a/web/static/css/style.css b/web/static/css/style.css index ea84834d..1bb7439c 100644 --- a/web/static/css/style.css +++ b/web/static/css/style.css @@ -2009,6 +2009,155 @@ html[data-theme="dark"] .openapi-doc-btn:hover { background: #fff; } +.hitl-filter-native-select { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +.hitl-filters--logs .hitl-filter-select-ui { + position: relative; + min-width: 160px; +} + +.hitl-filters--logs .hitl-filter-select-ui--decision { + min-width: 132px; +} + +.hitl-filters--logs .hitl-filter-select-ui--decidedby { + min-width: 168px; +} + +.hitl-filter-select-trigger { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + width: 100%; + min-width: 0; + padding: 8px 10px; + border: 1px solid var(--border-color, #e2e8f0); + border-radius: 8px; + background: var(--bg-primary, #fff); + color: var(--text-primary, #0f172a); + font-size: 13px; + line-height: 1.25; + cursor: pointer; + font-family: inherit; + transition: border-color 0.15s ease, box-shadow 0.15s ease; +} + +.hitl-filter-select-trigger:hover:not(:disabled) { + border-color: rgba(59, 130, 246, 0.45); +} + +.hitl-filter-select-ui.open .hitl-filter-select-trigger { + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.12); +} + +.hitl-filter-select-ui.open { + z-index: 120; +} + +.hitl-filter-select-value { + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + text-align: left; +} + +.hitl-filter-select-caret { + flex-shrink: 0; + color: var(--text-secondary, #64748b); + transition: transform 0.15s ease; +} + +.hitl-filter-select-ui.open .hitl-filter-select-caret { + transform: rotate(180deg); +} + +.hitl-filter-select-dropdown { + display: none; + position: absolute; + top: calc(100% + 4px); + left: 0; + right: 0; + z-index: 200; + max-height: 280px; + overflow-y: auto; + padding: 4px; + background: var(--bg-primary, #fff); + border: 1px solid var(--border-color, #e2e8f0); + border-radius: 8px; + box-shadow: var(--shadow-lg, 0 10px 15px -3px rgba(0, 0, 0, 0.1)); +} + +.hitl-filter-select-ui.open .hitl-filter-select-dropdown { + display: block; +} + +.hitl-filter-select-option { + display: flex; + align-items: center; + gap: 8px; + width: 100%; + padding: 8px 10px; + border: none; + border-radius: 6px; + background: transparent; + color: var(--text-primary, #0f172a); + font-size: 13px; + font-family: inherit; + cursor: pointer; + text-align: left; + transition: background 0.12s ease, color 0.12s ease; +} + +.hitl-filter-select-option:hover { + background: var(--bg-secondary, #f8fafc); +} + +.hitl-filter-select-option.is-selected { + background: rgba(59, 130, 246, 0.08); + color: #2563eb; + font-weight: 500; +} + +.hitl-filter-select-check { + width: 14px; + flex-shrink: 0; + visibility: hidden; + color: #2563eb; + font-size: 12px; + line-height: 1; +} + +.hitl-filter-select-option.is-selected .hitl-filter-select-check { + visibility: visible; +} + +.hitl-filter-select-label { + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.hitl-filter-select-ui.is-disabled .hitl-filter-select-trigger { + opacity: 0.6; + cursor: not-allowed; +} + .hitl-logs-table-wrap { overflow-x: auto; border: 1px solid #e2e8f0; @@ -14618,6 +14767,147 @@ tr.mcp-stats-tool-row[data-tool-name]:focus-visible { font-size: 0.8125rem; } +.tasks-filter-native-select { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +.batch-queues-filters.tasks-filters.batch-queues-filters--compact .tasks-filter-select-ui { + position: relative; + min-width: 120px; +} + +.tasks-filter-select-trigger { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + width: 100%; + min-width: 0; + padding: 6px 10px; + border: 1px solid var(--border-color); + border-radius: 6px; + background: var(--bg-primary); + color: var(--text-primary); + font-size: 0.8125rem; + line-height: 1.25; + cursor: pointer; + font-family: inherit; + transition: border-color 0.15s ease, box-shadow 0.15s ease; +} + +.tasks-filter-select-trigger:hover:not(:disabled) { + border-color: var(--accent-color); +} + +.tasks-filter-select-ui.open .tasks-filter-select-trigger { + border-color: var(--accent-color); + box-shadow: 0 0 0 2px rgba(0, 102, 255, 0.1); +} + +.tasks-filter-select-ui.open { + z-index: 120; +} + +.tasks-filter-select-value { + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + text-align: left; +} + +.tasks-filter-select-caret { + flex-shrink: 0; + color: var(--text-secondary); + transition: transform 0.15s ease; +} + +.tasks-filter-select-ui.open .tasks-filter-select-caret { + transform: rotate(180deg); +} + +.tasks-filter-select-dropdown { + display: none; + position: absolute; + top: calc(100% + 4px); + left: 0; + right: 0; + z-index: 200; + max-height: 280px; + overflow-y: auto; + padding: 4px; + background: var(--bg-primary); + border: 1px solid var(--border-color); + border-radius: 8px; + box-shadow: var(--shadow-lg); +} + +.tasks-filter-select-ui.open .tasks-filter-select-dropdown { + display: block; +} + +.tasks-filter-select-option { + display: flex; + align-items: center; + gap: 8px; + width: 100%; + padding: 8px 10px; + border: none; + border-radius: 6px; + background: transparent; + color: var(--text-primary); + font-size: 0.8125rem; + font-family: inherit; + cursor: pointer; + text-align: left; + transition: background 0.12s ease, color 0.12s ease; +} + +.tasks-filter-select-option:hover { + background: var(--bg-secondary); +} + +.tasks-filter-select-option.is-selected { + background: rgba(59, 130, 246, 0.08); + color: #2563eb; + font-weight: 500; +} + +.tasks-filter-select-check { + width: 14px; + flex-shrink: 0; + visibility: hidden; + color: #2563eb; + font-size: 12px; + line-height: 1; +} + +.tasks-filter-select-option.is-selected .tasks-filter-select-check { + visibility: visible; +} + +.tasks-filter-select-label { + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.tasks-filter-select-ui.is-disabled .tasks-filter-select-trigger { + opacity: 0.6; + cursor: not-allowed; +} + .batch-queues-filters.tasks-filters.batch-queues-filters--compact input[type="text"] { padding: 6px 10px; font-size: 0.8125rem; @@ -24832,6 +25122,147 @@ html[data-theme="dark"] .vulnerability-card:hover { margin-bottom: 16px; } +.chat-files-filter-native-select { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +.chat-files-filters .chat-files-filter-select-ui { + position: relative; + min-width: 168px; +} + +.chat-files-filter-select-trigger { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + width: 100%; + min-width: 0; + padding: 8px 12px; + border: 1px solid var(--border-color); + border-radius: 6px; + background: var(--bg-primary); + color: var(--text-primary); + font-size: 0.875rem; + line-height: 1.25; + cursor: pointer; + font-family: inherit; + transition: border-color 0.15s ease, box-shadow 0.15s ease; +} + +.chat-files-filter-select-trigger:hover:not(:disabled) { + border-color: var(--accent-color); +} + +.chat-files-filter-select-ui.open .chat-files-filter-select-trigger { + border-color: var(--accent-color); + box-shadow: 0 0 0 2px rgba(0, 102, 255, 0.1); +} + +.chat-files-filter-select-ui.open { + z-index: 120; +} + +.chat-files-filter-select-ui.is-disabled .chat-files-filter-select-trigger { + opacity: 0.6; + cursor: not-allowed; +} + +.chat-files-filter-select-value { + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + text-align: left; +} + +.chat-files-filter-select-caret { + flex-shrink: 0; + color: var(--text-secondary); + transition: transform 0.15s ease; +} + +.chat-files-filter-select-ui.open .chat-files-filter-select-caret { + transform: rotate(180deg); +} + +.chat-files-filter-select-dropdown { + display: none; + position: absolute; + top: calc(100% + 4px); + left: 0; + right: 0; + z-index: 200; + max-height: 280px; + overflow-y: auto; + padding: 4px; + background: var(--bg-primary); + border: 1px solid var(--border-color); + border-radius: 8px; + box-shadow: var(--shadow-lg); +} + +.chat-files-filter-select-ui.open .chat-files-filter-select-dropdown { + display: block; +} + +.chat-files-filter-select-option { + display: flex; + align-items: center; + gap: 8px; + width: 100%; + padding: 8px 10px; + border: none; + border-radius: 6px; + background: transparent; + color: var(--text-primary); + font-size: 0.8125rem; + font-family: inherit; + cursor: pointer; + text-align: left; + transition: background 0.12s ease, color 0.12s ease; +} + +.chat-files-filter-select-option:hover { + background: var(--bg-secondary); +} + +.chat-files-filter-select-option.is-selected { + background: rgba(59, 130, 246, 0.08); + color: #2563eb; + font-weight: 500; +} + +.chat-files-filter-select-check { + width: 14px; + flex-shrink: 0; + visibility: hidden; + color: #2563eb; + font-size: 12px; + line-height: 1; +} + +.chat-files-filter-select-option.is-selected .chat-files-filter-select-check { + visibility: visible; +} + +.chat-files-filter-select-label { + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + .chat-files-table-wrap { overflow-x: auto; background: var(--bg-primary); @@ -26684,6 +27115,175 @@ html[data-theme="dark"] .chat-files-toast.chat-toast--error { border-radius: 8px; transition: border-color 0.15s ease, box-shadow 0.15s ease, background 0.15s ease; } + +.projects-filter-native-select { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +.projects-fact-filter-field .projects-filter-select-ui { + width: 100%; + min-width: 120px; +} + +.projects-filter-select-ui { + position: relative; + width: 100%; + min-width: 0; +} + +.projects-filter-select-ui--compact { + display: inline-flex; + min-width: 64px; +} + +.projects-filter-select-trigger { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + width: 100%; + min-width: 0; + height: 34px; + padding: 0 10px; + border: 1px solid var(--border-color, #e2e8f0); + border-radius: 8px; + background: var(--bg-primary, #f8fafc); + color: var(--text-primary, #0f172a); + font-size: 0.8125rem; + line-height: 1.25; + cursor: pointer; + font-family: inherit; + transition: border-color 0.15s ease, box-shadow 0.15s ease, background 0.15s ease; +} + +.projects-settings-card .projects-filter-select-trigger { + height: 38px; + font-size: 0.875rem; +} + +.projects-filter-select-trigger:hover:not(:disabled) { + border-color: rgba(59, 130, 246, 0.45); +} + +.projects-filter-select-ui.open .projects-filter-select-trigger { + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.12); +} + +.projects-filter-select-ui.open { + z-index: 120; +} + +.projects-filter-select-ui.is-disabled .projects-filter-select-trigger { + opacity: 0.6; + cursor: not-allowed; +} + +.projects-filter-select-value { + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + text-align: left; +} + +.projects-filter-select-caret { + flex-shrink: 0; + color: var(--text-secondary, #94a3b8); + transition: transform 0.15s ease; +} + +.projects-filter-select-ui.open .projects-filter-select-caret { + transform: rotate(180deg); +} + +.projects-filter-select-dropdown { + display: none; + position: absolute; + top: calc(100% + 4px); + left: 0; + right: 0; + z-index: 200; + max-height: 280px; + overflow-y: auto; + padding: 4px; + background: var(--bg-primary, #fff); + border: 1px solid var(--border-color, #e2e8f0); + border-radius: 8px; + box-shadow: var(--shadow-lg, 0 10px 15px -3px rgba(0, 0, 0, 0.1)); +} + +.projects-filter-select-ui.open .projects-filter-select-dropdown { + display: block; +} + +.projects-filter-select-option { + display: flex; + align-items: center; + gap: 8px; + width: 100%; + padding: 8px 10px; + border: none; + border-radius: 6px; + background: transparent; + color: var(--text-primary, #0f172a); + font-size: 0.8125rem; + font-family: inherit; + cursor: pointer; + text-align: left; + transition: background 0.12s ease, color 0.12s ease; +} + +.projects-filter-select-option:hover { + background: var(--bg-secondary, #f1f5f9); +} + +.projects-filter-select-option.is-selected { + background: rgba(59, 130, 246, 0.08); + color: #2563eb; + font-weight: 500; +} + +.projects-filter-select-check { + width: 14px; + flex-shrink: 0; + visibility: hidden; + color: #2563eb; + font-size: 12px; + line-height: 1; +} + +.projects-filter-select-option.is-selected .projects-filter-select-check { + visibility: visible; +} + +.projects-filter-select-label { + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.sidebar-list-pagination .pagination-page-size .projects-filter-select-ui--compact { + vertical-align: middle; +} + +.sidebar-list-pagination .pagination-page-size .projects-filter-select-trigger { + height: 28px; + padding: 0 8px; + font-size: 0.75rem; + border-radius: 6px; +} .projects-fact-filter-field--search input { padding-left: 34px; background: #fff; @@ -28556,7 +29156,13 @@ html[data-theme="dark"] .role-selection-item-main:hover, html[data-theme="dark"] .role-select-item:hover, html[data-theme="dark"] .custom-select-option:hover, html[data-theme="dark"] .audit-custom-select-option:hover, -html[data-theme="dark"] .vuln-filter-select-option:hover { +html[data-theme="dark"] .vuln-filter-select-option:hover, +html[data-theme="dark"] .hitl-filter-select-option:hover, +html[data-theme="dark"] .tasks-filter-select-option:hover, +html[data-theme="dark"] .workflow-form-select-option:hover, +html[data-theme="dark"] .projects-filter-select-option:hover, +html[data-theme="dark"] .chat-files-filter-select-option:hover, +html[data-theme="dark"] #role-modal .role-form-select-option:hover { background: rgba(96, 165, 250, 0.12); } @@ -28565,7 +29171,13 @@ html[data-theme="dark"] .role-selection-item-main.selected, html[data-theme="dark"] .role-select-item.selected, html[data-theme="dark"] .custom-select-option.selected, html[data-theme="dark"] .audit-custom-select-option.is-selected, -html[data-theme="dark"] .vuln-filter-select-option.is-selected { +html[data-theme="dark"] .vuln-filter-select-option.is-selected, +html[data-theme="dark"] .hitl-filter-select-option.is-selected, +html[data-theme="dark"] .tasks-filter-select-option.is-selected, +html[data-theme="dark"] .workflow-form-select-option.is-selected, +html[data-theme="dark"] .projects-filter-select-option.is-selected, +html[data-theme="dark"] .chat-files-filter-select-option.is-selected, +html[data-theme="dark"] #role-modal .role-form-select-option.is-selected { background: rgba(96, 165, 250, 0.18); color: var(--accent-hover); } @@ -28579,6 +29191,13 @@ html[data-theme="dark"] .model-pick-input, html[data-theme="dark"] .custom-select-trigger, html[data-theme="dark"] .audit-custom-select-trigger, html[data-theme="dark"] .vuln-filter-select-trigger, +html[data-theme="dark"] .hitl-filter-select-trigger, +html[data-theme="dark"] .tasks-filter-select-trigger, +html[data-theme="dark"] .workflow-form-select-trigger, +html[data-theme="dark"] .workflow-tool-select-trigger, +html[data-theme="dark"] .projects-filter-select-trigger, +html[data-theme="dark"] .chat-files-filter-select-trigger, +html[data-theme="dark"] .role-form-select-trigger, html[data-theme="dark"] .role-selector-btn, html[data-theme="dark"] .conversation-project-filter-trigger { background: #0f172a; @@ -28599,7 +29218,13 @@ html[data-theme="dark"] .form-control:focus, html[data-theme="dark"] .model-pick-input:focus, html[data-theme="dark"] .custom-select.open .custom-select-trigger, html[data-theme="dark"] .audit-custom-select.open .audit-custom-select-trigger, -html[data-theme="dark"] .vuln-filter-select.open .vuln-filter-select-trigger { +html[data-theme="dark"] .vuln-filter-select.open .vuln-filter-select-trigger, +html[data-theme="dark"] .hitl-filter-select-ui.open .hitl-filter-select-trigger, +html[data-theme="dark"] .tasks-filter-select-ui.open .tasks-filter-select-trigger, +html[data-theme="dark"] .workflow-form-select-ui.open .workflow-form-select-trigger, +html[data-theme="dark"] .projects-filter-select-ui.open .projects-filter-select-trigger, +html[data-theme="dark"] .chat-files-filter-select-ui.open .chat-files-filter-select-trigger, +html[data-theme="dark"] #role-modal .role-form-select-ui.open .role-form-select-trigger { border-color: var(--accent-color); box-shadow: 0 0 0 3px rgba(96, 165, 250, 0.16); } @@ -30192,6 +30817,149 @@ html[data-theme="dark"] #agent-md-modal .modal-content, html[data-theme="dark"] #agent-md-modal .modal-header, html[data-theme="dark"] #agent-md-modal .modal-body, html[data-theme="dark"] #agent-md-modal .modal-footer, +.role-form-native-select { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +#role-modal .role-form-select-ui { + position: relative; + width: 100%; + min-width: 0; +} + +#role-modal .role-form-select-trigger { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + width: 100%; + min-width: 0; + min-height: 40px; + padding: 10px 12px; + border: 1px solid var(--border-color); + border-radius: 8px; + background: var(--bg-primary); + color: var(--text-primary); + font-size: 0.875rem; + line-height: 1.25; + cursor: pointer; + font-family: inherit; + transition: border-color 0.15s ease, box-shadow 0.15s ease; +} + +#role-modal .role-form-select-trigger:hover:not(:disabled) { + border-color: rgba(59, 130, 246, 0.45); +} + +#role-modal .role-form-select-ui.open .role-form-select-trigger { + border-color: var(--accent-color); + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.12); +} + +#role-modal .role-form-select-ui.open { + z-index: 120; +} + +#role-modal .role-form-select-ui.is-disabled .role-form-select-trigger { + opacity: 0.6; + cursor: not-allowed; +} + +#role-modal .role-form-select-value { + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + text-align: left; +} + +#role-modal .role-form-select-caret { + flex-shrink: 0; + color: var(--text-secondary); + transition: transform 0.15s ease; +} + +#role-modal .role-form-select-ui.open .role-form-select-caret { + transform: rotate(180deg); +} + +#role-modal .role-form-select-dropdown { + display: none; + position: absolute; + top: calc(100% + 4px); + left: 0; + right: 0; + z-index: 200; + max-height: 280px; + overflow-y: auto; + padding: 4px; + background: var(--bg-primary); + border: 1px solid var(--border-color); + border-radius: 8px; + box-shadow: var(--shadow-lg); +} + +#role-modal .role-form-select-ui.open .role-form-select-dropdown { + display: block; +} + +#role-modal .role-form-select-option { + display: flex; + align-items: center; + gap: 8px; + width: 100%; + padding: 8px 10px; + border: none; + border-radius: 6px; + background: transparent; + color: var(--text-primary); + font-size: 0.8125rem; + font-family: inherit; + cursor: pointer; + text-align: left; + transition: background 0.12s ease, color 0.12s ease; +} + +#role-modal .role-form-select-option:hover { + background: var(--bg-secondary); +} + +#role-modal .role-form-select-option.is-selected { + background: rgba(59, 130, 246, 0.08); + color: #2563eb; + font-weight: 500; +} + +#role-modal .role-form-select-check { + width: 14px; + flex-shrink: 0; + visibility: hidden; + color: #2563eb; + font-size: 12px; + line-height: 1; +} + +#role-modal .role-form-select-option.is-selected .role-form-select-check { + visibility: visible; +} + +#role-modal .role-form-select-label { + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + html[data-theme="dark"] #role-modal .modal-content, html[data-theme="dark"] #role-modal .modal-header, html[data-theme="dark"] #role-modal .modal-body, @@ -31787,6 +32555,152 @@ html[data-theme="dark"] .form-group select { pointer-events: none; } +.workflow-form-native-select { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +.workflow-form-select-ui { + position: relative; + width: 100%; + min-width: 0; +} + +.workflow-binding-row .workflow-form-select-ui { + flex: 1; + min-width: 0; +} + +.workflow-form-select-trigger { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + width: 100%; + min-height: 38px; + padding: 8px 10px; + border: 1px solid var(--border-color); + border-radius: 7px; + background: var(--bg-primary); + color: var(--text-primary); + font-size: 0.875rem; + font-family: inherit; + cursor: pointer; + text-align: left; + transition: border-color 0.2s ease, box-shadow 0.2s ease; +} + +.workflow-form-select-trigger:hover:not(:disabled) { + border-color: rgba(59, 130, 246, 0.45); +} + +.workflow-form-select-ui.open .workflow-form-select-trigger { + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.12); +} + +.workflow-form-select-ui.open { + z-index: 120; +} + +.workflow-form-select-ui.is-disabled .workflow-form-select-trigger { + opacity: 0.6; + cursor: not-allowed; +} + +.workflow-form-select-value { + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.workflow-form-select-caret { + flex-shrink: 0; + color: var(--text-secondary); + transition: transform 0.15s ease; +} + +.workflow-form-select-ui.open .workflow-form-select-caret { + transform: rotate(180deg); +} + +.workflow-form-select-dropdown { + display: none; + position: absolute; + top: calc(100% + 4px); + left: 0; + right: 0; + z-index: 200; + max-height: 280px; + overflow-y: auto; + padding: 4px; + background: var(--bg-primary); + border: 1px solid var(--border-color); + border-radius: 8px; + box-shadow: var(--shadow-lg); +} + +.workflow-form-select-ui.open .workflow-form-select-dropdown { + display: block; +} + +.workflow-form-select-option { + display: flex; + align-items: center; + gap: 8px; + width: 100%; + padding: 8px 10px; + border: none; + border-radius: 6px; + background: transparent; + color: var(--text-primary); + font-size: 0.8125rem; + font-family: inherit; + cursor: pointer; + text-align: left; + transition: background 0.12s ease, color 0.12s ease; +} + +.workflow-form-select-option:hover { + background: var(--bg-secondary); +} + +.workflow-form-select-option.is-selected { + background: rgba(59, 130, 246, 0.08); + color: #2563eb; + font-weight: 500; +} + +.workflow-form-select-check { + width: 14px; + flex-shrink: 0; + visibility: hidden; + color: #2563eb; + font-size: 12px; + line-height: 1; +} + +.workflow-form-select-option.is-selected .workflow-form-select-check { + visibility: visible; +} + +.workflow-form-select-label { + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + .workflow-tool-select { position: relative; width: 100%; diff --git a/web/static/js/c2.js b/web/static/js/c2.js index 7e36b0a1..3b4a85fe 100644 --- a/web/static/js/c2.js +++ b/web/static/js/c2.js @@ -71,6 +71,162 @@ return key; } + const c2FormSelectMap = {}; + let c2FormSelectDocBound = false; + const C2_FORM_SELECT_CARET = ''; + const C2_FORM_SELECT_HANDLERS = { + 'c2-listener-type': function () { + C2.syncListenerProfileRowForType(); + } + }; + + function closeAllC2FormSelects() { + Object.keys(c2FormSelectMap).forEach(function (id) { + const reg = c2FormSelectMap[id]; + if (!reg || !reg.wrapper) return; + reg.wrapper.classList.remove('open'); + if (reg.trigger) reg.trigger.setAttribute('aria-expanded', 'false'); + }); + } + + function syncC2FormSelect(selectId) { + const reg = c2FormSelectMap[selectId]; + if (!reg) return; + const select = reg.select; + const dropdown = reg.dropdown; + const trigger = reg.trigger; + const valueSpan = trigger.querySelector('.c2-form-select-value'); + + dropdown.innerHTML = ''; + Array.prototype.forEach.call(select.options, function (opt) { + const item = document.createElement('button'); + item.type = 'button'; + item.className = 'c2-form-select-option'; + item.setAttribute('role', 'option'); + item.setAttribute('data-value', opt.value); + if (opt.value === select.value) { + item.classList.add('is-selected'); + item.setAttribute('aria-selected', 'true'); + } else { + item.setAttribute('aria-selected', 'false'); + } + const check = document.createElement('span'); + check.className = 'c2-form-select-check'; + check.setAttribute('aria-hidden', 'true'); + check.textContent = '✓'; + const label = document.createElement('span'); + label.className = 'c2-form-select-label'; + label.textContent = opt.textContent; + item.appendChild(check); + item.appendChild(label); + dropdown.appendChild(item); + }); + + const selectedOpt = select.options[select.selectedIndex]; + if (valueSpan) { + valueSpan.textContent = selectedOpt ? selectedOpt.textContent : ''; + } + trigger.disabled = !!select.disabled; + reg.wrapper.classList.toggle('is-disabled', !!select.disabled); + } + + function enhanceC2FormSelect(select) { + if (!select || !select.id) return; + const existing = c2FormSelectMap[select.id]; + if (existing && existing.select !== select) { + delete c2FormSelectMap[select.id]; + } + if (select.dataset.c2FormCustom === '1') { + syncC2FormSelect(select.id); + return; + } + select.dataset.c2FormCustom = '1'; + select.classList.add('c2-form-native-select'); + select.tabIndex = -1; + select.setAttribute('aria-hidden', 'true'); + + const wrapper = document.createElement('div'); + wrapper.className = 'c2-form-select-ui'; + + const trigger = document.createElement('button'); + trigger.type = 'button'; + trigger.className = 'c2-form-select-trigger'; + trigger.setAttribute('aria-haspopup', 'listbox'); + trigger.setAttribute('aria-expanded', 'false'); + const valueSpan = document.createElement('span'); + valueSpan.className = 'c2-form-select-value'; + trigger.appendChild(valueSpan); + trigger.insertAdjacentHTML('beforeend', C2_FORM_SELECT_CARET); + + const dropdown = document.createElement('div'); + dropdown.className = 'c2-form-select-dropdown'; + dropdown.setAttribute('role', 'listbox'); + + const parent = select.parentNode; + parent.insertBefore(wrapper, select); + wrapper.appendChild(trigger); + wrapper.appendChild(dropdown); + wrapper.appendChild(select); + + c2FormSelectMap[select.id] = { wrapper: wrapper, trigger: trigger, dropdown: dropdown, select: select }; + + trigger.addEventListener('click', function (e) { + e.stopPropagation(); + if (select.disabled) return; + const open = wrapper.classList.contains('open'); + closeAllC2FormSelects(); + if (!open) { + wrapper.classList.add('open'); + trigger.setAttribute('aria-expanded', 'true'); + } + }); + + dropdown.addEventListener('click', function (e) { + const opt = e.target.closest('.c2-form-select-option'); + if (!opt) return; + e.stopPropagation(); + const val = opt.getAttribute('data-value'); + if (val === null) return; + if (select.value !== val) { + select.value = val; + select.dispatchEvent(new Event('change', { bubbles: true })); + } + wrapper.classList.remove('open'); + trigger.setAttribute('aria-expanded', 'false'); + syncC2FormSelect(select.id); + }); + + select.addEventListener('change', function () { + syncC2FormSelect(select.id); + }); + + if (!select.dataset.c2FormFilterBound) { + select.dataset.c2FormFilterBound = '1'; + const handler = C2_FORM_SELECT_HANDLERS[select.id]; + if (typeof handler === 'function') { + select.addEventListener('change', handler); + } + } + + syncC2FormSelect(select.id); + } + + C2.refreshFormSelects = function (root) { + const container = root || document.getElementById('c2-modal-content'); + if (!container) return; + Object.keys(c2FormSelectMap).forEach(function (id) { + if (!document.getElementById(id)) delete c2FormSelectMap[id]; + }); + container.querySelectorAll('select.c2-form-select-native').forEach(enhanceC2FormSelect); + if (!c2FormSelectDocBound) { + c2FormSelectDocBound = true; + document.addEventListener('click', closeAllC2FormSelects); + document.addEventListener('keydown', function (e) { + if (e.key === 'Escape') closeAllC2FormSelects(); + }); + } + }; + function listenerTypeLabel(type) { if (!type) return ''; const k = 'c2.listeners.typeLabels.' + String(type).toLowerCase(); @@ -609,7 +765,7 @@
- @@ -631,7 +787,7 @@
${emptyProfHintCreate} - +
${escapeHtml(c2t('c2.listeners.malleableProfileHint'))}
@@ -657,6 +813,7 @@
`; C2.syncListenerProfileRowForType(); + C2.refreshFormSelects(content); }).catch(() => { showToast(c2t('c2.listeners.toastProfilesLoadFailed'), 'error'); C2.closeModal(); @@ -681,6 +838,8 @@ if (legacyRow) { legacyRow.style.display = t === 'tcp_reverse' ? '' : 'none'; } + syncC2FormSelect('c2-listener-type'); + syncC2FormSelect('c2-listener-profile-id'); }; C2.createListener = function() { @@ -801,7 +960,7 @@
${httpHint}${emptyProfHint} - +
${escapeHtml(c2t('c2.listeners.malleableProfileHint'))}
@@ -827,6 +986,7 @@
`; + C2.refreshFormSelects(content); }).catch(() => { showToast(c2t('c2.listeners.toastProfilesLoadFailed'), 'error'); C2.closeModal(); @@ -3580,6 +3740,7 @@ }; C2.closeModal = function() { + closeAllC2FormSelects(); const modal = document.getElementById('c2-modal'); if (modal) { const modalBox = modal.querySelector('.c2-modal'); @@ -3619,6 +3780,9 @@ if (!window.currentPageId || !String(window.currentPageId).startsWith('c2')) return; if (typeof applyTranslations === 'function') applyTranslations(document); C2.init(); + if (isAppModalOpen('c2-modal')) { + C2.refreshFormSelects(); + } if (C2.selectedSessionId && (window.currentPageId === 'c2-sessions')) { C2.renderSessions(); C2.renderSessionDetail(C2.selectedSessionId); diff --git a/web/static/js/chat-files.js b/web/static/js/chat-files.js index cd79fa4c..edb0eff6 100644 --- a/web/static/js/chat-files.js +++ b/web/static/js/chat-files.js @@ -17,6 +17,156 @@ let chatFilesPendingUploadDir = ''; /** 文件管理页面向服务器上传进行中,避免重复选择并禁用顶栏按钮 */ let chatFilesXHRUploadBusy = false; +const CHAT_FILES_FILTER_SELECT_IDS = ['chat-files-group-by']; +const chatFilesFilterSelectMap = {}; +let chatFilesFilterSelectDocBound = false; +const CHAT_FILES_FILTER_SELECT_CARET = ''; + +function closeAllChatFilesFilterSelects() { + Object.keys(chatFilesFilterSelectMap).forEach(function (id) { + const reg = chatFilesFilterSelectMap[id]; + if (!reg || !reg.wrapper) return; + reg.wrapper.classList.remove('open'); + if (reg.trigger) reg.trigger.setAttribute('aria-expanded', 'false'); + }); +} + +function syncChatFilesFilterSelect(selectId) { + const reg = chatFilesFilterSelectMap[selectId]; + if (!reg) return; + const select = reg.select; + const dropdown = reg.dropdown; + const trigger = reg.trigger; + const valueSpan = trigger.querySelector('.chat-files-filter-select-value'); + + dropdown.innerHTML = ''; + Array.prototype.forEach.call(select.options, function (opt) { + const item = document.createElement('button'); + item.type = 'button'; + item.className = 'chat-files-filter-select-option'; + item.setAttribute('role', 'option'); + item.setAttribute('data-value', opt.value); + if (opt.value === select.value) { + item.classList.add('is-selected'); + item.setAttribute('aria-selected', 'true'); + } else { + item.setAttribute('aria-selected', 'false'); + } + const check = document.createElement('span'); + check.className = 'chat-files-filter-select-check'; + check.setAttribute('aria-hidden', 'true'); + check.textContent = '✓'; + const label = document.createElement('span'); + label.className = 'chat-files-filter-select-label'; + label.textContent = opt.textContent; + item.appendChild(check); + item.appendChild(label); + dropdown.appendChild(item); + }); + + const selectedOpt = select.options[select.selectedIndex]; + if (valueSpan) { + valueSpan.textContent = selectedOpt ? selectedOpt.textContent : ''; + } + trigger.disabled = !!select.disabled; + reg.wrapper.classList.toggle('is-disabled', !!select.disabled); +} + +function syncAllChatFilesFilterSelects() { + CHAT_FILES_FILTER_SELECT_IDS.forEach(syncChatFilesFilterSelect); +} + +function enhanceChatFilesFilterSelect(selectId) { + const select = document.getElementById(selectId); + if (!select) return; + const existing = chatFilesFilterSelectMap[selectId]; + if (existing && existing.select !== select) { + delete chatFilesFilterSelectMap[selectId]; + } + if (select.dataset.chatFilesCustomSelect === '1') { + syncChatFilesFilterSelect(selectId); + return; + } + select.dataset.chatFilesCustomSelect = '1'; + select.classList.add('chat-files-filter-native-select'); + select.tabIndex = -1; + select.setAttribute('aria-hidden', 'true'); + + const wrapper = document.createElement('div'); + wrapper.className = 'chat-files-filter-select-ui'; + + const trigger = document.createElement('button'); + trigger.type = 'button'; + trigger.className = 'chat-files-filter-select-trigger'; + trigger.setAttribute('aria-haspopup', 'listbox'); + trigger.setAttribute('aria-expanded', 'false'); + const valueSpan = document.createElement('span'); + valueSpan.className = 'chat-files-filter-select-value'; + trigger.appendChild(valueSpan); + trigger.insertAdjacentHTML('beforeend', CHAT_FILES_FILTER_SELECT_CARET); + + const dropdown = document.createElement('div'); + dropdown.className = 'chat-files-filter-select-dropdown'; + dropdown.setAttribute('role', 'listbox'); + + const parent = select.parentNode; + parent.insertBefore(wrapper, select); + wrapper.appendChild(trigger); + wrapper.appendChild(dropdown); + wrapper.appendChild(select); + + chatFilesFilterSelectMap[selectId] = { wrapper: wrapper, trigger: trigger, dropdown: dropdown, select: select }; + + trigger.addEventListener('click', function (e) { + e.stopPropagation(); + if (select.disabled) return; + const open = wrapper.classList.contains('open'); + closeAllChatFilesFilterSelects(); + if (!open) { + wrapper.classList.add('open'); + trigger.setAttribute('aria-expanded', 'true'); + } + }); + + dropdown.addEventListener('click', function (e) { + const opt = e.target.closest('.chat-files-filter-select-option'); + if (!opt) return; + e.stopPropagation(); + const val = opt.getAttribute('data-value'); + if (val === null) return; + if (select.value !== val) { + select.value = val; + select.dispatchEvent(new Event('change', { bubbles: true })); + } + wrapper.classList.remove('open'); + trigger.setAttribute('aria-expanded', 'false'); + syncChatFilesFilterSelect(selectId); + }); + + select.addEventListener('change', function () { + syncChatFilesFilterSelect(selectId); + }); + + if (!select.dataset.chatFilesFilterBound) { + select.dataset.chatFilesFilterBound = '1'; + select.addEventListener('change', chatFilesGroupByChange); + } + + syncChatFilesFilterSelect(selectId); +} + +function initChatFilesFilterSelects() { + if (!chatFilesFilterSelectDocBound) { + document.addEventListener('click', closeAllChatFilesFilterSelects); + document.addEventListener('keydown', function (e) { + if (e.key === 'Escape') closeAllChatFilesFilterSelects(); + }); + chatFilesFilterSelectDocBound = true; + } + CHAT_FILES_FILTER_SELECT_IDS.forEach(enhanceChatFilesFilterSelect); + syncAllChatFilesFilterSelects(); +} + function chatFilesLoadBrowsePathFromStorage() { try { const raw = localStorage.getItem(CHAT_FILES_BROWSE_PATH_KEY); @@ -84,6 +234,7 @@ function initChatFilesPage() { /* ignore */ } } + initChatFilesFilterSelects(); setupChatFilesDragDrop(); loadChatFilesPage(); } @@ -1349,7 +1500,12 @@ function setupChatFilesDragDrop() { document.addEventListener('languagechange', function () { if (typeof window.currentPage !== 'function') return; if (window.currentPage() !== 'chat-files') return; + syncAllChatFilesFilterSelects(); if (typeof renderChatFilesTable === 'function') { renderChatFilesTable(); } }); + +document.addEventListener('DOMContentLoaded', function () { + initChatFilesFilterSelects(); +}); diff --git a/web/static/js/hitl.js b/web/static/js/hitl.js index c61b3d4b..566a99da 100644 --- a/web/static/js/hitl.js +++ b/web/static/js/hitl.js @@ -1145,6 +1145,158 @@ function hitlFormatTime(v) { } } +const HITL_LOG_FILTER_SELECT_IDS = ['hitl-logs-decision-filter', 'hitl-logs-decidedby-filter']; +const hitlLogFilterSelectMap = {}; +let hitlLogFilterSelectDocBound = false; + +const HITL_FILTER_SELECT_CARET = ''; + +function closeAllHitlLogFilterSelects() { + Object.keys(hitlLogFilterSelectMap).forEach(function (id) { + const reg = hitlLogFilterSelectMap[id]; + if (!reg || !reg.wrapper) return; + reg.wrapper.classList.remove('open'); + if (reg.trigger) reg.trigger.setAttribute('aria-expanded', 'false'); + }); +} + +function syncHitlLogFilterSelect(selectId) { + const reg = hitlLogFilterSelectMap[selectId]; + if (!reg) return; + const select = reg.select; + const dropdown = reg.dropdown; + const trigger = reg.trigger; + const valueSpan = trigger.querySelector('.hitl-filter-select-value'); + + dropdown.innerHTML = ''; + Array.prototype.forEach.call(select.options, function (opt) { + const item = document.createElement('button'); + item.type = 'button'; + item.className = 'hitl-filter-select-option'; + item.setAttribute('role', 'option'); + item.setAttribute('data-value', opt.value); + if (opt.value === select.value) { + item.classList.add('is-selected'); + item.setAttribute('aria-selected', 'true'); + } else { + item.setAttribute('aria-selected', 'false'); + } + const check = document.createElement('span'); + check.className = 'hitl-filter-select-check'; + check.setAttribute('aria-hidden', 'true'); + check.textContent = '✓'; + const label = document.createElement('span'); + label.className = 'hitl-filter-select-label'; + label.textContent = opt.textContent; + item.appendChild(check); + item.appendChild(label); + dropdown.appendChild(item); + }); + + const selectedOpt = select.options[select.selectedIndex]; + if (valueSpan) { + valueSpan.textContent = selectedOpt ? selectedOpt.textContent : ''; + } + trigger.disabled = !!select.disabled; + reg.wrapper.classList.toggle('is-disabled', !!select.disabled); +} + +function syncAllHitlLogFilterSelects() { + HITL_LOG_FILTER_SELECT_IDS.forEach(syncHitlLogFilterSelect); +} + +function enhanceHitlLogFilterSelect(selectId) { + const select = document.getElementById(selectId); + if (!select) return; + if (select.dataset.hitlCustomSelect === '1') { + syncHitlLogFilterSelect(selectId); + return; + } + select.dataset.hitlCustomSelect = '1'; + select.classList.add('hitl-filter-native-select'); + select.tabIndex = -1; + select.setAttribute('aria-hidden', 'true'); + + const wrapper = document.createElement('div'); + wrapper.className = 'hitl-filter-select-ui'; + if (selectId === 'hitl-logs-decision-filter') { + wrapper.classList.add('hitl-filter-select-ui--decision'); + } else if (selectId === 'hitl-logs-decidedby-filter') { + wrapper.classList.add('hitl-filter-select-ui--decidedby'); + } + + const trigger = document.createElement('button'); + trigger.type = 'button'; + trigger.className = 'hitl-filter-select-trigger'; + trigger.setAttribute('aria-haspopup', 'listbox'); + trigger.setAttribute('aria-expanded', 'false'); + const valueSpan = document.createElement('span'); + valueSpan.className = 'hitl-filter-select-value'; + trigger.appendChild(valueSpan); + trigger.insertAdjacentHTML('beforeend', HITL_FILTER_SELECT_CARET); + + const dropdown = document.createElement('div'); + dropdown.className = 'hitl-filter-select-dropdown'; + dropdown.setAttribute('role', 'listbox'); + + const parent = select.parentNode; + parent.insertBefore(wrapper, select); + wrapper.appendChild(trigger); + wrapper.appendChild(dropdown); + wrapper.appendChild(select); + + hitlLogFilterSelectMap[selectId] = { wrapper: wrapper, trigger: trigger, dropdown: dropdown, select: select }; + + trigger.addEventListener('click', function (e) { + e.stopPropagation(); + if (select.disabled) return; + const open = wrapper.classList.contains('open'); + closeAllHitlLogFilterSelects(); + if (!open) { + wrapper.classList.add('open'); + trigger.setAttribute('aria-expanded', 'true'); + } + }); + + dropdown.addEventListener('click', function (e) { + const opt = e.target.closest('.hitl-filter-select-option'); + if (!opt) return; + e.stopPropagation(); + const val = opt.getAttribute('data-value'); + if (val === null) return; + if (select.value !== val) { + select.value = val; + select.dispatchEvent(new Event('change', { bubbles: true })); + } + wrapper.classList.remove('open'); + trigger.setAttribute('aria-expanded', 'false'); + syncHitlLogFilterSelect(selectId); + }); + + select.addEventListener('change', function () { + syncHitlLogFilterSelect(selectId); + }); +} + +function initHitlLogFilterSelects() { + if (!hitlLogFilterSelectDocBound) { + document.addEventListener('click', closeAllHitlLogFilterSelects); + document.addEventListener('keydown', function (e) { + if (e.key === 'Escape') closeAllHitlLogFilterSelects(); + }); + hitlLogFilterSelectDocBound = true; + } + HITL_LOG_FILTER_SELECT_IDS.forEach(function (id) { + enhanceHitlLogFilterSelect(id); + const select = document.getElementById(id); + if (select && !select.dataset.hitlFilterBound) { + select.dataset.hitlFilterBound = '1'; + select.addEventListener('change', filterHitlLogs); + } + }); + syncAllHitlLogFilterSelects(); +} + function hitlLogsHasActiveFilters() { const qEl = document.getElementById('hitl-logs-search'); const decEl = document.getElementById('hitl-logs-decision-filter'); @@ -1432,6 +1584,7 @@ function refreshHitlPendingI18n() { function refreshHitlI18n() { refreshHitlLogsI18n(); refreshHitlPendingI18n(); + syncAllHitlLogFilterSelects(); renderHitlLogsPagination(); renderHitlPendingPagination(); } @@ -1619,6 +1772,7 @@ window.addEventListener('pageshow', function () { document.addEventListener('DOMContentLoaded', function () { initHitlPageSizeFromStorage(HITL_LOGS_PAGE_SIZE_KEY, 20, function (n) { hitlLogsPageSize = n; }); initHitlPageSizeFromStorage(HITL_PENDING_PAGE_SIZE_KEY, 20, function (n) { hitlPendingPageSize = n; }); + initHitlLogFilterSelects(); if (typeof window.bindHitlReviewerToggleListeners === 'function') { window.bindHitlReviewerToggleListeners(); } diff --git a/web/static/js/projects.js b/web/static/js/projects.js index 2b80df76..0c09826d 100644 --- a/web/static/js/projects.js +++ b/web/static/js/projects.js @@ -26,6 +26,189 @@ function tpFmt(key, fallback, opts) { return text; } +function tpFmt(key, fallback, opts) { + const text = tp(key, opts); + if (!text || text === key) return fallback; + return text; +} + +const PROJECTS_FILTER_SELECT_HANDLERS = { + 'project-facts-filter-category': function () { loadProjectFacts(); }, + 'project-facts-filter-confidence': function () { loadProjectFacts(); }, + 'project-graph-view': function () { loadProjectFactGraph(); }, + 'project-vulns-filter-severity': function () { loadProjectVulnerabilities(); }, + 'project-vulns-filter-status': function () { loadProjectVulnerabilities(); }, + 'projects-page-size-pagination': function () { changeProjectsPageSize(); } +}; +const projectsFilterSelectMap = {}; +let projectsFilterSelectDocBound = false; +const PROJECTS_FILTER_SELECT_CARET = ''; + +function closeAllProjectsFilterSelects() { + Object.keys(projectsFilterSelectMap).forEach(function (id) { + const reg = projectsFilterSelectMap[id]; + if (!reg || !reg.wrapper) return; + reg.wrapper.classList.remove('open'); + if (reg.trigger) reg.trigger.setAttribute('aria-expanded', 'false'); + }); +} + +function pruneProjectsFilterSelectMap(root) { + Object.keys(projectsFilterSelectMap).forEach(function (id) { + const select = document.getElementById(id); + if (!select || (root && !root.contains(select))) { + delete projectsFilterSelectMap[id]; + } + }); +} + +function syncProjectsFilterSelect(select) { + const reg = projectsFilterSelectMap[select.id]; + if (!reg) return; + const dropdown = reg.dropdown; + const trigger = reg.trigger; + const valueSpan = trigger.querySelector('.projects-filter-select-value'); + + dropdown.innerHTML = ''; + Array.prototype.forEach.call(select.options, function (opt) { + const item = document.createElement('button'); + item.type = 'button'; + item.className = 'projects-filter-select-option'; + item.setAttribute('role', 'option'); + item.setAttribute('data-value', opt.value); + if (opt.value === select.value) { + item.classList.add('is-selected'); + item.setAttribute('aria-selected', 'true'); + } else { + item.setAttribute('aria-selected', 'false'); + } + const check = document.createElement('span'); + check.className = 'projects-filter-select-check'; + check.setAttribute('aria-hidden', 'true'); + check.textContent = '✓'; + const label = document.createElement('span'); + label.className = 'projects-filter-select-label'; + label.textContent = opt.textContent; + item.appendChild(check); + item.appendChild(label); + dropdown.appendChild(item); + }); + + const selectedOpt = select.options[select.selectedIndex]; + if (valueSpan) { + valueSpan.textContent = selectedOpt ? selectedOpt.textContent : ''; + } + trigger.disabled = !!select.disabled; + reg.wrapper.classList.toggle('is-disabled', !!select.disabled); +} + +function syncAllProjectsFilterSelects() { + Object.keys(projectsFilterSelectMap).forEach(function (id) { + const select = document.getElementById(id); + if (select) syncProjectsFilterSelect(select); + }); +} + +function enhanceProjectsFilterSelect(select) { + if (!select || !select.id) return; + const existing = projectsFilterSelectMap[select.id]; + if (existing && existing.select !== select) { + delete projectsFilterSelectMap[select.id]; + } + if (select.dataset.projectsCustomSelect === '1') { + syncProjectsFilterSelect(select); + return; + } + select.dataset.projectsCustomSelect = '1'; + select.classList.add('projects-filter-native-select'); + select.tabIndex = -1; + select.setAttribute('aria-hidden', 'true'); + + const wrapper = document.createElement('div'); + wrapper.className = 'projects-filter-select-ui'; + if (select.id === 'projects-page-size-pagination') { + wrapper.classList.add('projects-filter-select-ui--compact'); + } + + const trigger = document.createElement('button'); + trigger.type = 'button'; + trigger.className = 'projects-filter-select-trigger'; + trigger.setAttribute('aria-haspopup', 'listbox'); + trigger.setAttribute('aria-expanded', 'false'); + const valueSpan = document.createElement('span'); + valueSpan.className = 'projects-filter-select-value'; + trigger.appendChild(valueSpan); + trigger.insertAdjacentHTML('beforeend', PROJECTS_FILTER_SELECT_CARET); + + const dropdown = document.createElement('div'); + dropdown.className = 'projects-filter-select-dropdown'; + dropdown.setAttribute('role', 'listbox'); + + const parent = select.parentNode; + parent.insertBefore(wrapper, select); + wrapper.appendChild(trigger); + wrapper.appendChild(dropdown); + wrapper.appendChild(select); + + projectsFilterSelectMap[select.id] = { wrapper: wrapper, trigger: trigger, dropdown: dropdown, select: select }; + + trigger.addEventListener('click', function (e) { + e.stopPropagation(); + if (select.disabled) return; + const open = wrapper.classList.contains('open'); + closeAllProjectsFilterSelects(); + if (!open) { + wrapper.classList.add('open'); + trigger.setAttribute('aria-expanded', 'true'); + } + }); + + dropdown.addEventListener('click', function (e) { + const opt = e.target.closest('.projects-filter-select-option'); + if (!opt) return; + e.stopPropagation(); + const val = opt.getAttribute('data-value'); + if (val === null) return; + if (select.value !== val) { + select.value = val; + select.dispatchEvent(new Event('change', { bubbles: true })); + } + wrapper.classList.remove('open'); + trigger.setAttribute('aria-expanded', 'false'); + syncProjectsFilterSelect(select); + }); + + select.addEventListener('change', function () { + syncProjectsFilterSelect(select); + }); + + if (!select.dataset.projectsFilterBound) { + select.dataset.projectsFilterBound = '1'; + const handler = PROJECTS_FILTER_SELECT_HANDLERS[select.id]; + if (typeof handler === 'function') { + select.addEventListener('change', handler); + } + } + + syncProjectsFilterSelect(select); +} + +function refreshProjectsFilterSelects() { + const page = document.getElementById('page-projects'); + if (!page) return; + pruneProjectsFilterSelectMap(page); + page.querySelectorAll('select.projects-filter-select-native, #projects-page-size-pagination').forEach(function (select) { + enhanceProjectsFilterSelect(select); + }); + if (!projectsFilterSelectDocBound) { + projectsFilterSelectDocBound = true; + document.addEventListener('click', closeAllProjectsFilterSelects); + document.addEventListener('keydown', function (e) { + if (e.key === 'Escape') closeAllProjectsFilterSelects(); + }); + } +} + /** 与后端 internal/project/fact_template.go 对齐 */ const FACT_ATTACK_CHAIN_BODY_TEMPLATE = `## 结论(可验证,一句话) <勿仅写「存在漏洞」;写明类型 + 位置 + 触发条件> @@ -404,6 +587,7 @@ async function initProjectsPage() { const page = document.getElementById('page-projects'); if (!page || page.style.display === 'none') return; initProjectsModalEscape(); + refreshProjectsFilterSelects(); if (typeof syncAppModalBodyLock === 'function') { syncAppModalBodyLock(); } @@ -638,13 +822,14 @@ function renderProjectsPagination() {
`; + refreshProjectsFilterSelects(); } function renderProjectsSidebar() { @@ -780,6 +965,7 @@ async function selectProject(id) { if (vulnSearchEl) vulnSearchEl.value = ''; if (vulnSevEl) vulnSevEl.value = ''; if (vulnStatusEl) vulnStatusEl.value = ''; + syncAllProjectsFilterSelects(); renderProjectsSidebar(); updateProjectsDetailVisibility(); try { @@ -792,6 +978,7 @@ async function selectProject(id) { document.getElementById('project-edit-scope').value = p.scope_json || ''; const statusEl = document.getElementById('project-edit-status'); if (statusEl) statusEl.value = p.status || 'active'; + syncAllProjectsFilterSelects(); const pinEl = document.getElementById('project-edit-pinned'); if (pinEl) pinEl.checked = !!p.pinned; updateProjectStatusPill(p.status || 'active'); @@ -2427,6 +2614,7 @@ function initChatProjectSelector() { document.addEventListener('languagechange', () => { renderProjectsSidebar(); renderProjectsPagination(); + syncAllProjectsFilterSelects(); updateChatProjectButtonLabel(); const panel = document.getElementById('chat-project-panel'); if (panel && panel.style.display === 'flex') loadChatProjectPanelList(); @@ -2455,10 +2643,12 @@ if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { initChatProjectSelector(); initProjectListActionMenu(); + refreshProjectsFilterSelects(); }); } else { initChatProjectSelector(); initProjectListActionMenu(); + refreshProjectsFilterSelects(); } window.initProjectsPage = initProjectsPage; diff --git a/web/static/js/roles.js b/web/static/js/roles.js index 95d586a3..ccfa075c 100644 --- a/web/static/js/roles.js +++ b/web/static/js/roles.js @@ -17,6 +17,155 @@ function _t(key, opts) { return key; } +const ROLE_MODAL_SELECT_IDS = ['role-workflow-id', 'role-workflow-policy']; +const roleModalSelectMap = {}; +let roleModalSelectDocBound = false; +const ROLE_FORM_SELECT_CARET = ''; + +function closeAllRoleModalSelects() { + Object.keys(roleModalSelectMap).forEach(function (id) { + const reg = roleModalSelectMap[id]; + if (!reg || !reg.wrapper) return; + reg.wrapper.classList.remove('open'); + if (reg.trigger) reg.trigger.setAttribute('aria-expanded', 'false'); + }); +} + +function syncRoleModalSelect(selectId) { + const reg = roleModalSelectMap[selectId]; + if (!reg) return; + const select = reg.select; + const dropdown = reg.dropdown; + const trigger = reg.trigger; + const valueSpan = trigger.querySelector('.role-form-select-value'); + + dropdown.innerHTML = ''; + Array.prototype.forEach.call(select.options, function (opt) { + const item = document.createElement('button'); + item.type = 'button'; + item.className = 'role-form-select-option'; + item.setAttribute('role', 'option'); + item.setAttribute('data-value', opt.value); + if (opt.value === select.value) { + item.classList.add('is-selected'); + item.setAttribute('aria-selected', 'true'); + } else { + item.setAttribute('aria-selected', 'false'); + } + const check = document.createElement('span'); + check.className = 'role-form-select-check'; + check.setAttribute('aria-hidden', 'true'); + check.textContent = '✓'; + const label = document.createElement('span'); + label.className = 'role-form-select-label'; + label.textContent = opt.textContent; + item.appendChild(check); + item.appendChild(label); + dropdown.appendChild(item); + }); + + const selectedOpt = select.options[select.selectedIndex]; + if (valueSpan) { + valueSpan.textContent = selectedOpt ? selectedOpt.textContent : ''; + } + trigger.disabled = !!select.disabled; + reg.wrapper.classList.toggle('is-disabled', !!select.disabled); +} + +function syncAllRoleModalSelects() { + ROLE_MODAL_SELECT_IDS.forEach(syncRoleModalSelect); +} + +function enhanceRoleModalSelect(selectId) { + const select = document.getElementById(selectId); + if (!select) return; + const existing = roleModalSelectMap[selectId]; + if (existing && existing.select !== select) { + delete roleModalSelectMap[selectId]; + } + if (select.dataset.roleFormCustom === '1') { + syncRoleModalSelect(selectId); + return; + } + select.dataset.roleFormCustom = '1'; + select.classList.add('role-form-native-select'); + select.tabIndex = -1; + select.setAttribute('aria-hidden', 'true'); + + const wrapper = document.createElement('div'); + wrapper.className = 'role-form-select-ui'; + + const trigger = document.createElement('button'); + trigger.type = 'button'; + trigger.className = 'role-form-select-trigger'; + trigger.setAttribute('aria-haspopup', 'listbox'); + trigger.setAttribute('aria-expanded', 'false'); + const valueSpan = document.createElement('span'); + valueSpan.className = 'role-form-select-value'; + trigger.appendChild(valueSpan); + trigger.insertAdjacentHTML('beforeend', ROLE_FORM_SELECT_CARET); + + const dropdown = document.createElement('div'); + dropdown.className = 'role-form-select-dropdown'; + dropdown.setAttribute('role', 'listbox'); + + const parent = select.parentNode; + parent.insertBefore(wrapper, select); + wrapper.appendChild(trigger); + wrapper.appendChild(dropdown); + wrapper.appendChild(select); + + roleModalSelectMap[selectId] = { wrapper: wrapper, trigger: trigger, dropdown: dropdown, select: select }; + + trigger.addEventListener('click', function (e) { + e.stopPropagation(); + if (select.disabled) return; + const open = wrapper.classList.contains('open'); + closeAllRoleModalSelects(); + if (!open) { + wrapper.classList.add('open'); + trigger.setAttribute('aria-expanded', 'true'); + } + }); + + dropdown.addEventListener('click', function (e) { + const opt = e.target.closest('.role-form-select-option'); + if (!opt) return; + e.stopPropagation(); + const val = opt.getAttribute('data-value'); + if (val === null) return; + if (select.value !== val) { + select.value = val; + select.dispatchEvent(new Event('change', { bubbles: true })); + } + wrapper.classList.remove('open'); + trigger.setAttribute('aria-expanded', 'false'); + syncRoleModalSelect(selectId); + }); + + select.addEventListener('change', function () { + syncRoleModalSelect(selectId); + }); + + syncRoleModalSelect(selectId); +} + +function refreshRoleModalSelects() { + const modal = document.getElementById('role-modal'); + if (!modal) return; + Object.keys(roleModalSelectMap).forEach(function (id) { + if (!document.getElementById(id)) delete roleModalSelectMap[id]; + }); + ROLE_MODAL_SELECT_IDS.forEach(enhanceRoleModalSelect); + if (!roleModalSelectDocBound) { + roleModalSelectDocBound = true; + document.addEventListener('click', closeAllRoleModalSelects); + document.addEventListener('keydown', function (e) { + if (e.key === 'Escape') closeAllRoleModalSelects(); + }); + } +} + /** 角色配置中的描述:trim,并把误存为 i18n key 的字面量视为空 */ function rolePlainDescription(role) { const raw = typeof role.description === 'string' ? role.description.trim() : ''; @@ -1201,6 +1350,7 @@ async function showAddRoleModal() { // 确保统计信息正确更新(显示0/108) updateRoleToolsStats(); + refreshRoleModalSelects(); openAppModal('role-modal'); } @@ -1370,11 +1520,13 @@ async function editRole(roleName) { } } + refreshRoleModalSelects(); openAppModal('role-modal'); } // 关闭角色模态框 function closeRoleModal() { + closeAllRoleModalSelects(); closeAppModal('role-modal'); } @@ -1720,12 +1872,14 @@ document.addEventListener('click', (e) => { document.addEventListener('DOMContentLoaded', () => { loadRoles(); updateRoleSelectorDisplay(); + refreshRoleModalSelects(); }); // 语言切换后刷新角色选择器与「选择角色」列表文案 document.addEventListener('languagechange', () => { updateRoleSelectorDisplay(); renderRoleSelectionSidebar(); + syncAllRoleModalSelects(); }); // 获取当前选中的角色(供chat.js使用) @@ -1740,6 +1894,7 @@ if (typeof window !== 'undefined') { window.closeRoleSelectionPanel = closeRoleSelectionPanel; window.closeRoleSelectModal = closeRoleSelectModal; window.filterRoleToolsByStatus = filterRoleToolsByStatus; + window.refreshRoleModalSelects = refreshRoleModalSelects; window.currentSelectedRole = getCurrentRole(); // 监听角色变化,更新全局变量 diff --git a/web/static/js/tasks.js b/web/static/js/tasks.js index 5baaad32..397023d7 100644 --- a/web/static/js/tasks.js +++ b/web/static/js/tasks.js @@ -764,6 +764,7 @@ function toggleTasksAutoRefresh(enabled) { // 初始化任务管理页面 function initTasksPage() { + initBatchQueuesFilterSelects(); // 恢复自动刷新设置 const autoRefreshCheckbox = document.getElementById('tasks-auto-refresh'); if (autoRefreshCheckbox) { @@ -1151,6 +1152,153 @@ async function loadBatchQueues(page) { } } +const BATCH_QUEUES_FILTER_SELECT_IDS = ['batch-queues-status-filter']; +const batchQueuesFilterSelectMap = {}; +let batchQueuesFilterSelectDocBound = false; + +const TASKS_FILTER_SELECT_CARET = ''; + +function closeAllBatchQueuesFilterSelects() { + Object.keys(batchQueuesFilterSelectMap).forEach(function (id) { + const reg = batchQueuesFilterSelectMap[id]; + if (!reg || !reg.wrapper) return; + reg.wrapper.classList.remove('open'); + if (reg.trigger) reg.trigger.setAttribute('aria-expanded', 'false'); + }); +} + +function syncBatchQueuesFilterSelect(selectId) { + const reg = batchQueuesFilterSelectMap[selectId]; + if (!reg) return; + const select = reg.select; + const dropdown = reg.dropdown; + const trigger = reg.trigger; + const valueSpan = trigger.querySelector('.tasks-filter-select-value'); + + dropdown.innerHTML = ''; + Array.prototype.forEach.call(select.options, function (opt) { + const item = document.createElement('button'); + item.type = 'button'; + item.className = 'tasks-filter-select-option'; + item.setAttribute('role', 'option'); + item.setAttribute('data-value', opt.value); + if (opt.value === select.value) { + item.classList.add('is-selected'); + item.setAttribute('aria-selected', 'true'); + } else { + item.setAttribute('aria-selected', 'false'); + } + const check = document.createElement('span'); + check.className = 'tasks-filter-select-check'; + check.setAttribute('aria-hidden', 'true'); + check.textContent = '✓'; + const label = document.createElement('span'); + label.className = 'tasks-filter-select-label'; + label.textContent = opt.textContent; + item.appendChild(check); + item.appendChild(label); + dropdown.appendChild(item); + }); + + const selectedOpt = select.options[select.selectedIndex]; + if (valueSpan) { + valueSpan.textContent = selectedOpt ? selectedOpt.textContent : ''; + } + trigger.disabled = !!select.disabled; + reg.wrapper.classList.toggle('is-disabled', !!select.disabled); +} + +function syncAllBatchQueuesFilterSelects() { + BATCH_QUEUES_FILTER_SELECT_IDS.forEach(syncBatchQueuesFilterSelect); +} + +function enhanceBatchQueuesFilterSelect(selectId) { + const select = document.getElementById(selectId); + if (!select) return; + if (select.dataset.tasksCustomSelect === '1') { + syncBatchQueuesFilterSelect(selectId); + return; + } + select.dataset.tasksCustomSelect = '1'; + select.classList.add('tasks-filter-native-select'); + select.tabIndex = -1; + select.setAttribute('aria-hidden', 'true'); + + const wrapper = document.createElement('div'); + wrapper.className = 'tasks-filter-select-ui'; + + const trigger = document.createElement('button'); + trigger.type = 'button'; + trigger.className = 'tasks-filter-select-trigger'; + trigger.setAttribute('aria-haspopup', 'listbox'); + trigger.setAttribute('aria-expanded', 'false'); + const valueSpan = document.createElement('span'); + valueSpan.className = 'tasks-filter-select-value'; + trigger.appendChild(valueSpan); + trigger.insertAdjacentHTML('beforeend', TASKS_FILTER_SELECT_CARET); + + const dropdown = document.createElement('div'); + dropdown.className = 'tasks-filter-select-dropdown'; + dropdown.setAttribute('role', 'listbox'); + + const parent = select.parentNode; + parent.insertBefore(wrapper, select); + wrapper.appendChild(trigger); + wrapper.appendChild(dropdown); + wrapper.appendChild(select); + + batchQueuesFilterSelectMap[selectId] = { wrapper: wrapper, trigger: trigger, dropdown: dropdown, select: select }; + + trigger.addEventListener('click', function (e) { + e.stopPropagation(); + if (select.disabled) return; + const open = wrapper.classList.contains('open'); + closeAllBatchQueuesFilterSelects(); + if (!open) { + wrapper.classList.add('open'); + trigger.setAttribute('aria-expanded', 'true'); + } + }); + + dropdown.addEventListener('click', function (e) { + const opt = e.target.closest('.tasks-filter-select-option'); + if (!opt) return; + e.stopPropagation(); + const val = opt.getAttribute('data-value'); + if (val === null) return; + if (select.value !== val) { + select.value = val; + select.dispatchEvent(new Event('change', { bubbles: true })); + } + wrapper.classList.remove('open'); + trigger.setAttribute('aria-expanded', 'false'); + syncBatchQueuesFilterSelect(selectId); + }); + + select.addEventListener('change', function () { + syncBatchQueuesFilterSelect(selectId); + }); +} + +function initBatchQueuesFilterSelects() { + if (!batchQueuesFilterSelectDocBound) { + document.addEventListener('click', closeAllBatchQueuesFilterSelects); + document.addEventListener('keydown', function (e) { + if (e.key === 'Escape') closeAllBatchQueuesFilterSelects(); + }); + batchQueuesFilterSelectDocBound = true; + } + BATCH_QUEUES_FILTER_SELECT_IDS.forEach(function (id) { + enhanceBatchQueuesFilterSelect(id); + const select = document.getElementById(id); + if (select && !select.dataset.tasksFilterBound) { + select.dataset.tasksFilterBound = '1'; + select.addEventListener('change', filterBatchQueues); + } + }); + syncAllBatchQueuesFilterSelects(); +} + // 筛选批量任务队列 function filterBatchQueues() { const statusFilter = document.getElementById('batch-queues-status-filter'); @@ -2526,6 +2674,7 @@ window.saveInlineSchedule = saveInlineSchedule; // 语言切换后,列表/分页/详情弹窗由 JS 渲染的文案需用当前语言重绘(applyTranslations 不会处理 innerHTML 内容) document.addEventListener('languagechange', function () { try { + syncAllBatchQueuesFilterSelects(); const tasksPage = document.getElementById('page-tasks'); if (!tasksPage || !tasksPage.classList.contains('active')) { return; @@ -2545,3 +2694,7 @@ document.addEventListener('languagechange', function () { console.warn('languagechange tasks refresh failed', e); } }); + +document.addEventListener('DOMContentLoaded', function () { + initBatchQueuesFilterSelects(); +}); diff --git a/web/static/js/workflows.js b/web/static/js/workflows.js index 818dfa85..b4ebe965 100644 --- a/web/static/js/workflows.js +++ b/web/static/js/workflows.js @@ -90,7 +90,7 @@
- +
${hint ? '

' + hint + '

' : ''} @@ -534,6 +534,7 @@ if (select.disabled) return; const open = wrapper.classList.contains('open'); closeWorkflowToolSelect(); + closeAllWorkflowFormSelects(); if (!open) { wrapper.classList.add('open'); trigger.setAttribute('aria-expanded', 'true'); @@ -573,6 +574,160 @@ syncWorkflowToolSelect(); } + const workflowFormSelectMap = {}; + let workflowFormSelectDocBound = false; + const WORKFLOW_FORM_SELECT_CARET = ''; + + function closeAllWorkflowFormSelects() { + Object.keys(workflowFormSelectMap).forEach(function (id) { + const reg = workflowFormSelectMap[id]; + if (!reg || !reg.wrapper) return; + reg.wrapper.classList.remove('open'); + if (reg.trigger) reg.trigger.setAttribute('aria-expanded', 'false'); + }); + } + + function pruneWorkflowFormSelectMap(root) { + Object.keys(workflowFormSelectMap).forEach(function (id) { + const select = document.getElementById(id); + if (!select || (root && !root.contains(select))) { + delete workflowFormSelectMap[id]; + } + }); + } + + function syncWorkflowFormSelect(select) { + const reg = workflowFormSelectMap[select.id]; + if (!reg) return; + const dropdown = reg.dropdown; + const trigger = reg.trigger; + const valueSpan = trigger.querySelector('.workflow-form-select-value'); + + dropdown.innerHTML = ''; + Array.prototype.forEach.call(select.options, function (opt) { + const item = document.createElement('button'); + item.type = 'button'; + item.className = 'workflow-form-select-option'; + item.setAttribute('role', 'option'); + item.setAttribute('data-value', opt.value); + if (opt.value === select.value) { + item.classList.add('is-selected'); + item.setAttribute('aria-selected', 'true'); + } else { + item.setAttribute('aria-selected', 'false'); + } + const check = document.createElement('span'); + check.className = 'workflow-form-select-check'; + check.setAttribute('aria-hidden', 'true'); + check.textContent = '✓'; + const label = document.createElement('span'); + label.className = 'workflow-form-select-label'; + label.textContent = opt.textContent; + item.appendChild(check); + item.appendChild(label); + dropdown.appendChild(item); + }); + + const selectedOpt = select.options[select.selectedIndex]; + if (valueSpan) { + valueSpan.textContent = selectedOpt ? selectedOpt.textContent : ''; + } + trigger.disabled = !!select.disabled; + reg.wrapper.classList.toggle('is-disabled', !!select.disabled); + } + + function enhanceWorkflowFormSelect(select) { + if (!select || !select.id) return; + if (select.id === WORKFLOW_TOOL_SELECT_ID) return; + const existing = workflowFormSelectMap[select.id]; + if (existing && existing.select !== select) { + delete workflowFormSelectMap[select.id]; + } + if (select.dataset.workflowFormCustom === '1') { + syncWorkflowFormSelect(select); + return; + } + select.dataset.workflowFormCustom = '1'; + select.classList.add('workflow-form-native-select'); + select.tabIndex = -1; + select.setAttribute('aria-hidden', 'true'); + + const wrapper = document.createElement('div'); + wrapper.className = 'workflow-form-select-ui'; + + const trigger = document.createElement('button'); + trigger.type = 'button'; + trigger.className = 'workflow-form-select-trigger'; + trigger.setAttribute('aria-haspopup', 'listbox'); + trigger.setAttribute('aria-expanded', 'false'); + const valueSpan = document.createElement('span'); + valueSpan.className = 'workflow-form-select-value'; + trigger.appendChild(valueSpan); + trigger.insertAdjacentHTML('beforeend', WORKFLOW_FORM_SELECT_CARET); + + const dropdown = document.createElement('div'); + dropdown.className = 'workflow-form-select-dropdown'; + dropdown.setAttribute('role', 'listbox'); + + const parent = select.parentNode; + parent.insertBefore(wrapper, select); + wrapper.appendChild(trigger); + wrapper.appendChild(dropdown); + wrapper.appendChild(select); + + workflowFormSelectMap[select.id] = { wrapper: wrapper, trigger: trigger, dropdown: dropdown, select: select }; + + trigger.addEventListener('click', function (e) { + e.stopPropagation(); + if (select.disabled) return; + const open = wrapper.classList.contains('open'); + closeAllWorkflowFormSelects(); + closeWorkflowToolSelect(); + if (!open) { + wrapper.classList.add('open'); + trigger.setAttribute('aria-expanded', 'true'); + } + }); + + dropdown.addEventListener('click', function (e) { + const opt = e.target.closest('.workflow-form-select-option'); + if (!opt) return; + e.stopPropagation(); + const val = opt.getAttribute('data-value'); + if (val === null) return; + if (select.value !== val) { + select.value = val; + select.dispatchEvent(new Event('change', { bubbles: true })); + } + wrapper.classList.remove('open'); + trigger.setAttribute('aria-expanded', 'false'); + syncWorkflowFormSelect(select); + }); + + select.addEventListener('change', function () { + syncWorkflowFormSelect(select); + }); + + syncWorkflowFormSelect(select); + } + + function refreshWorkflowPropertySelects() { + const form = document.getElementById('workflow-property-form'); + if (!form || form.hidden) return; + pruneWorkflowFormSelectMap(form); + form.querySelectorAll('select').forEach(function (select) { + if (select.id === WORKFLOW_TOOL_SELECT_ID) return; + enhanceWorkflowFormSelect(select); + }); + if (!workflowFormSelectDocBound) { + workflowFormSelectDocBound = true; + document.addEventListener('click', closeAllWorkflowFormSelects); + document.addEventListener('keydown', function (e) { + if (e.key === 'Escape') closeAllWorkflowFormSelects(); + }); + } + } + function readWorkflowMetaFromForm() { const idEl = document.getElementById('workflow-id'); const nameEl = document.getElementById('workflow-name'); @@ -794,7 +949,7 @@ return `
- ${JOIN_STRATEGIES.map(strategy => ``).join('')}

${esc(_t('workflows.config.joinStrategyHint') || '多个上游进入同一节点时如何生成 previous。')}

@@ -838,7 +993,7 @@ ${sourceType === 'condition' ? `
- @@ -847,6 +1002,7 @@

${esc(_t('workflows.config.edgeBranchHint'))}

` : ''} `; + refreshWorkflowPropertySelects(); return; } const type = ele.data('type') || 'tool'; @@ -879,7 +1035,7 @@ ${joinStrategyHtml(cfg)}
- ${AGENT_MODES.map(mode => ``).join('')}
@@ -904,7 +1060,7 @@

${_t('workflows.config.hitlInteractiveHint')}

- @@ -925,6 +1081,7 @@ default: wrap.innerHTML = ''; } + refreshWorkflowPropertySelects(); } function renderCustomFields(config) { @@ -1688,6 +1845,9 @@ `` )).join(''); select.value = current || ''; + if (typeof window.refreshRoleModalSelects === 'function') { + window.refreshRoleModalSelects(); + } }; function refreshCanvasLabels() { diff --git a/web/templates/index.html b/web/templates/index.html index e643ea42..e8a859cb 100644 --- a/web/templates/index.html +++ b/web/templates/index.html @@ -1271,7 +1271,7 @@