Add files via upload

This commit is contained in:
公明
2026-07-07 16:32:15 +08:00
committed by GitHub
parent 741f131f80
commit cca6796176
10 changed files with 2221 additions and 25 deletions
+150
View File
@@ -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;
+917 -3
View File
@@ -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%;
+167 -3
View File
@@ -71,6 +71,162 @@
return key;
}
const c2FormSelectMap = {};
let c2FormSelectDocBound = false;
const C2_FORM_SELECT_CARET = '<svg class="c2-form-select-caret" width="14" height="14" viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M6 9l6 6 6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>';
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 @@
</div>
<div class="c2-form-group">
<label>${escapeHtml(c2t('c2.listeners.type'))}</label>
<select id="c2-listener-type" class="form-control c2-native-select" onchange="C2.syncListenerProfileRowForType()">
<select id="c2-listener-type" class="form-control c2-form-select-native">
<option value="http_beacon">HTTP Beacon</option>
<option value="https_beacon">HTTPS Beacon</option>
<option value="tcp_reverse">TCP Reverse</option>
@@ -631,7 +787,7 @@
<div class="c2-form-group" id="c2-listener-profile-group">
<label>${escapeHtml(c2t('c2.listeners.malleableProfile'))}</label>
${emptyProfHintCreate}
<select id="c2-listener-profile-id" class="form-control c2-native-select">${profileOpts}</select>
<select id="c2-listener-profile-id" class="form-control c2-form-select-native">${profileOpts}</select>
<div class="form-hint">${escapeHtml(c2t('c2.listeners.malleableProfileHint'))}</div>
</div>
<div class="c2-form-group">
@@ -657,6 +813,7 @@
</div>
`;
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 @@
<div class="c2-form-group" id="c2-listener-profile-group">
<label>${escapeHtml(c2t('c2.listeners.malleableProfile'))}</label>
${httpHint}${emptyProfHint}
<select id="c2-listener-profile-id" class="form-control c2-native-select">${profileOpts}</select>
<select id="c2-listener-profile-id" class="form-control c2-form-select-native">${profileOpts}</select>
<div class="form-hint">${escapeHtml(c2t('c2.listeners.malleableProfileHint'))}</div>
</div>
<div class="c2-form-group">
@@ -827,6 +986,7 @@
<button class="btn-primary" onclick="C2.saveListener('${l.id}')">${escapeHtml(c2t('common.save'))}</button>
</div>
`;
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);
+156
View File
@@ -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 = '<svg class="chat-files-filter-select-caret" width="14" height="14" viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M6 9l6 6 6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>';
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();
});
+154
View File
@@ -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 = '<svg class="hitl-filter-select-caret" width="14" height="14" viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M6 9l6 6 6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>';
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();
}
+191 -1
View File
@@ -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 = '<svg class="projects-filter-select-caret" width="14" height="14" viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M6 9l6 6 6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>';
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() {
</div>
<label class="pagination-page-size">
${escapeHtml(tp('projects.paginationPerPage'))}
<select id="projects-page-size-pagination" onchange="changeProjectsPageSize()">
<select id="projects-page-size-pagination" class="projects-filter-select-native">
<option value="20" ${pageSize === 20 ? 'selected' : ''}>20</option>
<option value="50" ${pageSize === 50 ? 'selected' : ''}>50</option>
<option value="100" ${pageSize === 100 ? 'selected' : ''}>100</option>
</select>
</label>
</div>`;
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;
+155
View File
@@ -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 = '<svg class="role-form-select-caret" width="14" height="14" viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M6 9l6 6 6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>';
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();
// 监听角色变化,更新全局变量
+153
View File
@@ -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 = '<svg class="tasks-filter-select-caret" width="14" height="14" viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M6 9l6 6 6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>';
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();
});
+165 -5
View File
@@ -90,7 +90,7 @@
<div class="form-group">
<label>${esc(label)}</label>
<div class="workflow-binding-row" style="display:flex;gap:8px;">
<select id="${prefix}-from" onchange="updateWorkflowTypedConfig()" style="flex:1;">${options}</select>
<select id="${prefix}-from" class="workflow-form-select-native" onchange="updateWorkflowTypedConfig()" style="flex:1;">${options}</select>
<input type="text" id="${prefix}-field" value="${esc(field)}" placeholder="output" oninput="updateWorkflowTypedConfig()" style="flex:1;">
</div>
${hint ? '<p class="workflow-config-hint">' + hint + '</p>' : ''}
@@ -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 = '<svg class="workflow-form-select-caret" width="14" height="14" viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M6 9l6 6 6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>';
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 `
<div class="form-group">
<label for="workflow-join-strategy">${esc(_t('workflows.config.joinStrategy') || '汇聚策略')}</label>
<select id="workflow-join-strategy" onchange="updateWorkflowTypedConfig()">
<select id="workflow-join-strategy" class="workflow-form-select-native" onchange="updateWorkflowTypedConfig()">
${JOIN_STRATEGIES.map(strategy => `<option value="${strategy}" ${strategy === selected ? 'selected' : ''}>${strategy}</option>`).join('')}
</select>
<p class="workflow-config-hint">${esc(_t('workflows.config.joinStrategyHint') || '多个上游进入同一节点时如何生成 previous。')}</p>
@@ -838,7 +993,7 @@
${sourceType === 'condition' ? `
<div class="form-group">
<label for="workflow-edge-branch">${esc(_t('workflows.config.edgeBranch') || '条件分支')}</label>
<select id="workflow-edge-branch" onchange="updateWorkflowTypedConfig()">
<select id="workflow-edge-branch" class="workflow-form-select-native" onchange="updateWorkflowTypedConfig()">
<option value="">${esc(_t('workflows.config.selectBranch') || '请选择')}</option>
<option value="true" ${cfg.branch === 'true' ? 'selected' : ''}>true / 是</option>
<option value="false" ${cfg.branch === 'false' ? 'selected' : ''}>false / 否</option>
@@ -847,6 +1002,7 @@
<p class="workflow-config-hint">${esc(_t('workflows.config.edgeBranchHint'))}</p>
` : ''}
`;
refreshWorkflowPropertySelects();
return;
}
const type = ele.data('type') || 'tool';
@@ -879,7 +1035,7 @@
${joinStrategyHtml(cfg)}
<div class="form-group">
<label for="workflow-agent-mode">${esc(_t('workflows.config.agentMode'))}</label>
<select id="workflow-agent-mode" onchange="updateWorkflowTypedConfig()">
<select id="workflow-agent-mode" class="workflow-form-select-native" onchange="updateWorkflowTypedConfig()">
${AGENT_MODES.map(mode => `<option value="${mode}" ${mode === cfg.agent_mode ? 'selected' : ''}>${mode}</option>`).join('')}
</select>
</div>
@@ -904,7 +1060,7 @@
<p class="workflow-config-hint">${_t('workflows.config.hitlInteractiveHint')}</p>
<div class="form-group">
<label for="workflow-hitl-reviewer">${esc(_t('workflows.config.hitlReviewer'))}</label>
<select id="workflow-hitl-reviewer" onchange="updateWorkflowTypedConfig()">
<select id="workflow-hitl-reviewer" class="workflow-form-select-native" onchange="updateWorkflowTypedConfig()">
<option value="human" ${cfg.reviewer === 'human' ? 'selected' : ''}>human</option>
<option value="audit_agent" ${cfg.reviewer === 'audit_agent' ? 'selected' : ''}>audit_agent</option>
</select>
@@ -925,6 +1081,7 @@
default:
wrap.innerHTML = '';
}
refreshWorkflowPropertySelects();
}
function renderCustomFields(config) {
@@ -1688,6 +1845,9 @@
`<option value="${esc(wf.id)}">${esc(wf.name || wf.id)}${wf.enabled ? '' : esc(_t('roleModal.workflowDisabledSuffix'))}</option>`
)).join('');
select.value = current || '';
if (typeof window.refreshRoleModalSelects === 'function') {
window.refreshRoleModalSelects();
}
};
function refreshCanvasLabels() {
+13 -13
View File
@@ -1271,7 +1271,7 @@
</label>
<label>
<span data-i18n="hitl.filterDecision">决策</span>
<select id="hitl-logs-decision-filter" class="hitl-filter-select" onchange="filterHitlLogs()">
<select id="hitl-logs-decision-filter" class="hitl-filter-select">
<option value="all" data-i18n="hitl.filterAll">全部</option>
<option value="approve" data-i18n="hitl.decisionApprove">通过</option>
<option value="reject" data-i18n="hitl.decisionReject">拒绝</option>
@@ -1279,7 +1279,7 @@
</label>
<label>
<span data-i18n="hitl.filterDecidedBy">审批方</span>
<select id="hitl-logs-decidedby-filter" class="hitl-filter-select" onchange="filterHitlLogs()">
<select id="hitl-logs-decidedby-filter" class="hitl-filter-select">
<option value="all" data-i18n="hitl.filterAll">全部</option>
<option value="human" data-i18n="hitl.reviewerHuman">人工</option>
<option value="audit_agent" data-i18n="hitl.reviewerAgent">审计 Agent</option>
@@ -1795,7 +1795,7 @@
</label>
<label class="projects-fact-filter-field">
<span class="projects-fact-filter-label" data-i18n="projects.category">分类</span>
<select id="project-facts-filter-category" onchange="loadProjectFacts()">
<select id="project-facts-filter-category" class="projects-filter-select-native">
<option value="" data-i18n="projects.all">全部</option>
<option value="target">target</option>
<option value="auth">auth</option>
@@ -1810,7 +1810,7 @@
</label>
<label class="projects-fact-filter-field">
<span class="projects-fact-filter-label" data-i18n="projects.confidence">置信度</span>
<select id="project-facts-filter-confidence" onchange="loadProjectFacts()">
<select id="project-facts-filter-confidence" class="projects-filter-select-native">
<option value="" data-i18n="projects.all">全部</option>
<option value="confirmed" data-i18n="projects.confidenceConfirmed">已确认</option>
<option value="tentative" data-i18n="projects.confidenceTentative">待确认</option>
@@ -1848,7 +1848,7 @@
<div class="projects-fact-toolbar-filters projects-graph-toolbar-row">
<label class="projects-fact-filter-field">
<span class="projects-fact-filter-label" data-i18n="projects.graphView">视图</span>
<select id="project-graph-view" onchange="loadProjectFactGraph()">
<select id="project-graph-view" class="projects-filter-select-native">
<option value="path" data-i18n="projects.graphViewPath">攻击路径</option>
<option value="full" data-i18n="projects.graphViewFull">完整关系</option>
</select>
@@ -1961,7 +1961,7 @@
</label>
<label class="projects-fact-filter-field">
<span class="projects-fact-filter-label" data-i18n="projects.severity">严重度</span>
<select id="project-vulns-filter-severity" onchange="loadProjectVulnerabilities()">
<select id="project-vulns-filter-severity" class="projects-filter-select-native">
<option value="" data-i18n="projects.all">全部</option>
<option value="critical">critical</option>
<option value="high">high</option>
@@ -1972,7 +1972,7 @@
</label>
<label class="projects-fact-filter-field">
<span class="projects-fact-filter-label" data-i18n="projects.status">状态</span>
<select id="project-vulns-filter-status" onchange="loadProjectVulnerabilities()">
<select id="project-vulns-filter-status" class="projects-filter-select-native">
<option value="" data-i18n="projects.all">全部</option>
<option value="open" data-i18n="vulnerabilityPage.statusOpen">待处理</option>
<option value="confirmed" data-i18n="vulnerabilityPage.statusConfirmed">已确认</option>
@@ -2020,7 +2020,7 @@
<div class="projects-form-field">
<label for="project-edit-status" data-i18n="projects.status">状态</label>
<div class="projects-status-select-wrap">
<select id="project-edit-status" class="form-input projects-status-select">
<select id="project-edit-status" class="form-input projects-status-select projects-filter-select-native">
<option value="active" data-i18n="projects.statusActive">进行中</option>
<option value="archived" data-i18n="projects.statusArchived">已归档</option>
</select>
@@ -2317,7 +2317,7 @@
</label>
<label>
<span data-i18n="chatFilesPage.groupBy">分组</span>
<select id="chat-files-group-by" class="form-control" onchange="chatFilesGroupByChange()">
<select id="chat-files-group-by" class="form-control chat-files-filter-select-native">
<option value="none" data-i18n="chatFilesPage.groupNone">不分组</option>
<option value="date" data-i18n="chatFilesPage.groupByDate">按日期</option>
<option value="conversation" data-i18n="chatFilesPage.groupByConversation">按会话</option>
@@ -2356,7 +2356,7 @@
<div class="batch-queues-filters tasks-filters batch-queues-filters--compact">
<label>
<span data-i18n="tasksPage.statusFilter">状态筛选</span>
<select id="batch-queues-status-filter" onchange="filterBatchQueues()">
<select id="batch-queues-status-filter" class="tasks-filter-select">
<option value="all" data-i18n="knowledgePage.all">全部</option>
<option value="pending" data-i18n="tasksPage.statusPending">待执行</option>
<option value="running" data-i18n="dashboard.executing">执行中</option>
@@ -2616,7 +2616,7 @@
</div>
<div class="form-group" id="workflow-prop-type-wrap">
<label for="workflow-prop-type" data-i18n="workflows.propType">类型</label>
<select id="workflow-prop-type" onchange="updateWorkflowSelectedProperty()">
<select id="workflow-prop-type" class="workflow-form-select-native" onchange="updateWorkflowSelectedProperty()">
<option value="start" data-i18n="workflows.nodes.start">开始</option>
<option value="tool" data-i18n="workflows.nodes.tool">工具</option>
<option value="agent" data-i18n="workflows.nodes.agent">Agent</option>
@@ -5065,14 +5065,14 @@
</div>
<div class="form-group">
<label for="role-workflow-id" data-i18n="roleModal.bindWorkflow">绑定图编排流程</label>
<select id="role-workflow-id">
<select id="role-workflow-id" class="role-form-select-native">
<option value="" data-i18n="roleModal.noWorkflowBind">不绑定流程</option>
</select>
<small class="form-hint" data-i18n="roleModal.bindWorkflowHint">选中流程后,对话页使用该角色会自动触发绑定图;流程字段由图定义 JSON 自由配置。</small>
</div>
<div class="form-group">
<label for="role-workflow-policy" data-i18n="roleModal.workflowPolicy">流程触发策略</label>
<select id="role-workflow-policy">
<select id="role-workflow-policy" class="role-form-select-native">
<option value="auto" data-i18n="roleModal.workflowPolicyAuto">自动触发</option>
<option value="off" data-i18n="roleModal.workflowPolicyOff">关闭</option>
</select>