mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-04-21 18:26:38 +02:00
Add files via upload
This commit is contained in:
+799
-10
@@ -8196,6 +8196,10 @@ header {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.batch-queues-filters.tasks-filters {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.batch-queues-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -8214,26 +8218,370 @@ header {
|
||||
.batch-queues-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
margin-bottom: 16px;
|
||||
gap: 12px;
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
|
||||
.batch-queue-item {
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
border-radius: 14px;
|
||||
padding: 14px 16px;
|
||||
box-shadow: var(--shadow-sm);
|
||||
transition: all 0.2s ease;
|
||||
transition: border-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.batch-queue-item:hover {
|
||||
box-shadow: var(--shadow-md);
|
||||
transform: translateY(-2px);
|
||||
transform: translateY(-1px);
|
||||
border-color: var(--accent-color);
|
||||
}
|
||||
|
||||
.batch-queue-item--cron-wait {
|
||||
border-left: 3px solid var(--accent-color);
|
||||
}
|
||||
|
||||
.batch-queue-item--cron-wait:hover {
|
||||
border-color: var(--accent-color);
|
||||
}
|
||||
|
||||
.batch-queue-item__inner {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.batch-queue-item__top {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 10px 14px;
|
||||
}
|
||||
|
||||
.batch-queue-item__top-actions {
|
||||
flex-shrink: 0;
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
.batch-queue-icon-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
padding: 0;
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--border-color);
|
||||
background: var(--bg-secondary);
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
|
||||
}
|
||||
|
||||
.batch-queue-icon-btn:hover {
|
||||
color: var(--error-color, #dc3545);
|
||||
border-color: rgba(220, 53, 69, 0.35);
|
||||
background: rgba(220, 53, 69, 0.06);
|
||||
}
|
||||
|
||||
.batch-queue-icon-btn__svg {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.batch-queue-item__band {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 12px 16px;
|
||||
padding: 12px 14px;
|
||||
margin: 0;
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.batch-queue-item__band-left {
|
||||
flex: 1 1 220px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.batch-queue-item__band-right {
|
||||
flex: 1 1 200px;
|
||||
min-width: 160px;
|
||||
max-width: 100%;
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.batch-queue-progress-meta--band {
|
||||
align-items: stretch;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.batch-queue-progress-meta--band .batch-queue-progress-note {
|
||||
text-align: right;
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
@media (max-width: 520px) {
|
||||
.batch-queue-item__band-right {
|
||||
margin-left: 0;
|
||||
flex-basis: 100%;
|
||||
}
|
||||
|
||||
.batch-queue-progress-meta--band {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.batch-queue-progress-meta--band .batch-queue-progress-note {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.batch-queue-card-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 16px 20px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.batch-queue-primary {
|
||||
flex: 1 1 280px;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.batch-queue-card-title {
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
line-height: 1.35;
|
||||
letter-spacing: -0.015em;
|
||||
}
|
||||
|
||||
.batch-queue-card-title--muted {
|
||||
color: var(--text-secondary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.batch-queue-chips {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.batch-queue-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
padding: 4px 11px;
|
||||
border-radius: 999px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
color: var(--text-secondary);
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border-color);
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.batch-queue-chip-emoji {
|
||||
font-size: 0.875rem;
|
||||
line-height: 1;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.batch-queue-chip-text {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
.batch-queue-chip--role .batch-queue-chip-text {
|
||||
max-width: 160px;
|
||||
}
|
||||
|
||||
.batch-queue-chip--schedule {
|
||||
color: var(--accent-color);
|
||||
border-color: var(--border-color);
|
||||
background: var(--bg-primary);
|
||||
box-shadow: inset 2px 0 0 0 var(--accent-color);
|
||||
}
|
||||
|
||||
.batch-queue-chip--warn {
|
||||
color: #b45309;
|
||||
border-color: rgba(180, 83, 9, 0.35);
|
||||
background: rgba(251, 191, 36, 0.12);
|
||||
}
|
||||
|
||||
.batch-queue-status-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 8px 12px;
|
||||
}
|
||||
|
||||
.batch-queue-next-run {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 6px 12px;
|
||||
border-radius: 10px;
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-left: 3px solid var(--accent-color);
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 500;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.batch-queue-next-run__icon {
|
||||
font-size: 1rem;
|
||||
line-height: 1;
|
||||
opacity: 0.88;
|
||||
}
|
||||
|
||||
.batch-queue-next-run__text {
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.batch-queue-footnotes {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 6px 8px;
|
||||
margin-top: 2px;
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.batch-queue-footnotes__id code {
|
||||
font-size: 0.7rem;
|
||||
padding: 2px 7px;
|
||||
border-radius: 6px;
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border-color);
|
||||
word-break: break-all;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.batch-queue-footnotes__sep {
|
||||
opacity: 0.45;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.batch-queue-side {
|
||||
flex: 0 1 232px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.batch-queue-delete-btn {
|
||||
font-size: 0.75rem !important;
|
||||
padding: 5px 12px !important;
|
||||
color: var(--text-secondary) !important;
|
||||
border-color: var(--border-color) !important;
|
||||
background: var(--bg-primary) !important;
|
||||
}
|
||||
|
||||
.batch-queue-delete-btn:hover {
|
||||
color: var(--error-color, #dc3545) !important;
|
||||
border-color: rgba(220, 53, 69, 0.4) !important;
|
||||
background: rgba(220, 53, 69, 0.06) !important;
|
||||
}
|
||||
|
||||
.batch-queue-progress-stack {
|
||||
width: 100%;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.batch-queue-progress-bar.batch-queue-progress-bar--card {
|
||||
height: 10px;
|
||||
border-radius: 999px;
|
||||
background: var(--bg-secondary);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.batch-queue-progress-fill--cron-wait {
|
||||
background: linear-gradient(90deg, var(--accent-color), var(--accent-hover), var(--accent-color));
|
||||
background-size: 200% 100%;
|
||||
animation: batch-queue-progress-shimmer 2.4s ease infinite;
|
||||
}
|
||||
|
||||
@keyframes batch-queue-progress-shimmer {
|
||||
0% {
|
||||
background-position: 100% 0;
|
||||
}
|
||||
100% {
|
||||
background-position: -100% 0;
|
||||
}
|
||||
}
|
||||
|
||||
.batch-queue-progress-stack .batch-queue-progress-meta {
|
||||
width: 100%;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.batch-queue-progress-fraction {
|
||||
color: var(--text-secondary);
|
||||
font-weight: 400;
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
|
||||
.batch-queue-stats--pills {
|
||||
margin: 0;
|
||||
padding-top: 10px;
|
||||
border-top: 1px solid var(--border-color);
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.batch-queue-stat-pill {
|
||||
display: inline-flex;
|
||||
align-items: baseline;
|
||||
gap: 6px;
|
||||
padding: 5px 11px;
|
||||
border-radius: 8px;
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border-color);
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.batch-queue-stat-pill__k {
|
||||
color: var(--text-secondary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.batch-queue-stat-pill__v {
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.batch-queue-stat-pill--ok .batch-queue-stat-pill__v {
|
||||
color: var(--success-color);
|
||||
}
|
||||
|
||||
.batch-queue-stat-pill--err .batch-queue-stat-pill__v {
|
||||
color: var(--error-color);
|
||||
}
|
||||
|
||||
.batch-queue-stat-pill--muted .batch-queue-stat-pill__v {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.batch-queue-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -8273,12 +8621,106 @@ header {
|
||||
border: 1px solid rgba(0, 123, 255, 0.3);
|
||||
}
|
||||
|
||||
.batch-queue-cron-active {
|
||||
box-shadow: 0 0 0 1px rgba(0, 123, 255, 0.15);
|
||||
}
|
||||
|
||||
.batch-queue-status-paused {
|
||||
background: rgba(255, 193, 7, 0.12);
|
||||
color: #b38600;
|
||||
border: 1px solid rgba(255, 193, 7, 0.45);
|
||||
}
|
||||
|
||||
.batch-queue-status-completed {
|
||||
background: rgba(40, 167, 69, 0.1);
|
||||
color: var(--success-color);
|
||||
border: 1px solid rgba(40, 167, 69, 0.3);
|
||||
}
|
||||
|
||||
/* Cron 队列:本轮跑完、等待下次触发(区别于「一次性任务已完成」) */
|
||||
.batch-queue-status-cron-cycle {
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
border: 1px dashed var(--accent-color);
|
||||
}
|
||||
|
||||
.batch-queue-status-wrap {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 4px;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.batch-queue-cron-sublabel {
|
||||
font-size: 0.75rem;
|
||||
color: var(--accent-color);
|
||||
font-weight: 500;
|
||||
line-height: 1.35;
|
||||
max-width: 280px;
|
||||
}
|
||||
|
||||
.batch-queue-cron-sublabel.detail {
|
||||
margin-top: 6px;
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.batch-queue-detail-status-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.batch-queue-progress-meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 4px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.batch-queue-progress-note {
|
||||
font-size: 0.72rem;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.35;
|
||||
text-align: right;
|
||||
max-width: 240px;
|
||||
}
|
||||
|
||||
.batch-queue-progress-note.detail {
|
||||
text-align: left;
|
||||
max-width: none;
|
||||
margin-top: 2px;
|
||||
opacity: 0.95;
|
||||
}
|
||||
|
||||
.batch-queue-cron-callout {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: flex-start;
|
||||
padding: 12px 14px;
|
||||
margin-bottom: 8px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-left: 3px solid var(--accent-color);
|
||||
background: var(--bg-secondary);
|
||||
}
|
||||
|
||||
.batch-queue-cron-callout-icon {
|
||||
font-size: 1.25rem;
|
||||
line-height: 1.2;
|
||||
flex-shrink: 0;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.batch-queue-cron-callout p {
|
||||
margin: 0;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.5;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.batch-queue-status-cancelled {
|
||||
background: rgba(108, 117, 125, 0.1);
|
||||
color: var(--text-secondary);
|
||||
@@ -8337,6 +8779,303 @@ header {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 批量队列详情:信息分层(状态 → 摘要 → 告警 → 折叠技术信息) */
|
||||
.batch-queue-detail-layout {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.batch-queue-detail-hero {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
padding: 14px 16px;
|
||||
margin-bottom: 14px;
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.batch-queue-detail-hero__sub {
|
||||
margin: 0;
|
||||
font-size: 0.875rem;
|
||||
color: var(--text-primary);
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.batch-queue-detail-hero__note {
|
||||
margin: 0;
|
||||
font-size: 0.8125rem;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.batch-queue-detail-kv {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
||||
gap: 10px 20px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.bq-kv {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(100px, 34%) 1fr;
|
||||
gap: 8px 12px;
|
||||
align-items: start;
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.bq-kv--block {
|
||||
grid-column: 1 / -1;
|
||||
grid-template-columns: minmax(100px, 28%) 1fr;
|
||||
}
|
||||
|
||||
.bq-kv:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.bq-kv__k {
|
||||
color: var(--text-secondary);
|
||||
font-weight: 500;
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
|
||||
.bq-kv__v {
|
||||
color: var(--text-primary);
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.bq-kv__v code {
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
font-size: 0.8125rem;
|
||||
background: var(--bg-secondary);
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.bq-kv__v--control {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 520px) {
|
||||
.bq-kv,
|
||||
.bq-kv--block {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.bq-cron-toggle {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
cursor: pointer;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.bq-cron-toggle input {
|
||||
margin-top: 3px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.bq-cron-toggle__hint {
|
||||
font-size: 0.8125rem;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.bq-alert {
|
||||
padding: 10px 12px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 12px;
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
|
||||
.bq-alert--err {
|
||||
background: rgba(220, 53, 69, 0.07);
|
||||
border: 1px solid rgba(220, 53, 69, 0.28);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.bq-alert--err strong {
|
||||
display: block;
|
||||
color: var(--error-color);
|
||||
font-size: 0.8125rem;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.bq-alert--err p {
|
||||
margin: 0;
|
||||
color: var(--text-primary);
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.batch-queue-detail-tech {
|
||||
margin-bottom: 8px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
background: var(--bg-primary);
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.batch-queue-detail-tech__sum {
|
||||
cursor: pointer;
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-secondary);
|
||||
padding: 10px 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.batch-queue-detail-tech__sum::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.batch-queue-detail-tech__body {
|
||||
padding: 4px 0 12px;
|
||||
border-top: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.batch-queue-detail-tech__body .bq-kv:first-child {
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.batch-queue-cron-callout--compact {
|
||||
margin-bottom: 12px;
|
||||
padding: 10px 12px;
|
||||
}
|
||||
|
||||
.batch-queue-cron-callout--compact p {
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
|
||||
/* 列表卡片紧凑布局 */
|
||||
.batch-queue-item__config {
|
||||
margin: 4px 0 0;
|
||||
font-size: 0.8125rem;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.batch-queue-item__idline {
|
||||
margin: 6px 0 0;
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-secondary);
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 4px 6px;
|
||||
}
|
||||
|
||||
.batch-queue-item__idline code {
|
||||
font-size: 0.7rem;
|
||||
padding: 1px 6px;
|
||||
border-radius: 4px;
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border-color);
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.batch-queue-item__idsep {
|
||||
opacity: 0.45;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.batch-queue-inline-warn {
|
||||
color: #b45309;
|
||||
font-weight: 500;
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
|
||||
.batch-queue-item__mid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 10px 16px;
|
||||
padding: 10px 0 6px;
|
||||
border-top: 1px solid var(--border-color);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.batch-queue-item__mid-left {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 8px 12px;
|
||||
flex: 1 1 200px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.batch-queue-item__sublabel {
|
||||
font-size: 0.8125rem;
|
||||
color: var(--text-primary);
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.batch-queue-item__mid-right {
|
||||
flex: 0 1 200px;
|
||||
min-width: 140px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.batch-queue-progress-bar--list {
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.batch-queue-item__pct {
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-secondary);
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.batch-queue-item__pct-frac {
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.batch-queue-statsline {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 4px 0;
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-secondary);
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
.batch-queue-statsline__sep {
|
||||
margin: 0 8px;
|
||||
opacity: 0.4;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.batch-queue-statsline__item--ok {
|
||||
color: var(--success-color);
|
||||
}
|
||||
|
||||
.batch-queue-statsline__item--err {
|
||||
color: var(--error-color);
|
||||
}
|
||||
|
||||
/* 任务列表与分页条分离,避免与最后一张卡片贴在一起 */
|
||||
#batch-queues-pagination.pagination-fixed {
|
||||
margin-top: 8px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
#batch-queues-pagination .pagination {
|
||||
padding: 12px 16px 12px;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
#batch-queues-pagination .pagination-info {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.batch-queue-detail-info {
|
||||
margin-bottom: 24px;
|
||||
padding: 20px;
|
||||
@@ -8421,12 +9160,21 @@ header {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.batch-queue-detail-layout + .batch-queue-tasks-list h4 {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.batch-queue-item__title-col {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.batch-task-item {
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin-bottom: 12px;
|
||||
padding: 12px 14px;
|
||||
margin-bottom: 10px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
@@ -8556,15 +9304,56 @@ header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
|
||||
.batch-queue-item__top {
|
||||
flex-wrap: wrap;
|
||||
gap: 8px 10px;
|
||||
}
|
||||
|
||||
.batch-queue-item__mid {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.batch-queue-item__mid-right {
|
||||
flex: 1 1 auto;
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.batch-queue-item__pct {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.batch-queue-card-row {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.batch-queue-side {
|
||||
flex-basis: auto;
|
||||
width: 100%;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.batch-queue-delete-btn {
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
.batch-queue-progress-stack {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.batch-queue-progress {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
.batch-queue-stats {
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.batch-queue-stats--pills {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.batch-task-header {
|
||||
flex-direction: column;
|
||||
|
||||
@@ -254,6 +254,14 @@
|
||||
"conversationIdLabel": "Conversation ID",
|
||||
"statusPending": "Pending",
|
||||
"statusPaused": "Paused",
|
||||
"statusCronCycleIdle": "Round done · scheduled loop",
|
||||
"statusCronRunning": "Running · cron queue",
|
||||
"cronNextRunLine": "Next run: {{time}}",
|
||||
"cronRoundDoneProgressHint": "Cron queue: subtasks finished; next round starts on schedule",
|
||||
"cronRunningProgressHint": "This round is running; the next full cycle follows Cron / next run time",
|
||||
"cronPendingScheduled": "Cron scheduled · next {{time}}",
|
||||
"cronPendingProgressNote": "Will start on schedule, or click Start to run a round now",
|
||||
"cronRecurringCallout": "Cron queues start a new round at each scheduled time. Turn off \"Allow Cron auto-run\" to stop looping.",
|
||||
"confirmCancelTasks": "Cancel {{n}} selected task(s)?",
|
||||
"batchCancelResultPartial": "Batch cancel: {{success}} succeeded, {{fail}} failed",
|
||||
"batchCancelResultSuccess": "Successfully cancelled {{n}} task(s)",
|
||||
@@ -276,6 +284,7 @@
|
||||
"deleteQueueConfirm": "Delete this batch queue? This cannot be undone.",
|
||||
"deleteQueueFailed": "Failed to delete batch queue",
|
||||
"batchQueueTitle": "Batch task queue",
|
||||
"batchQueueUntitled": "Untitled queue",
|
||||
"resumeExecute": "Resume",
|
||||
"taskIncomplete": "Task information incomplete",
|
||||
"cannotGetTaskMessageInput": "Cannot get task message input",
|
||||
@@ -1494,6 +1503,18 @@
|
||||
"role": "Role",
|
||||
"defaultRole": "Default",
|
||||
"roleHint": "Select a role; all tasks will be executed using that role's configuration (prompt and tools).",
|
||||
"agentMode": "Agent mode",
|
||||
"agentModeSingle": "Single-agent (ReAct)",
|
||||
"agentModeMulti": "Multi-agent (Eino)",
|
||||
"agentModeHint": "Single-agent is recommended by default; use multi-agent for complex tasks (requires system multi-agent enabled).",
|
||||
"scheduleMode": "Schedule mode",
|
||||
"scheduleModeManual": "Manual",
|
||||
"scheduleModeCron": "Cron expression",
|
||||
"scheduleModeHint": "Manual is for one-time runs; Cron is for recurring runs. Validate tasks manually first.",
|
||||
"cronExpr": "Cron expression",
|
||||
"cronExprPlaceholder": "e.g. 0 */2 * * * (run every 2 hours)",
|
||||
"cronExprHint": "Use standard 5-field Cron: minute hour day month weekday. Example: `0 2 * * *` runs at 02:00 daily.",
|
||||
"cronExprRequired": "Please fill in a Cron expression when Cron schedule is selected",
|
||||
"tasksList": "Task list (one task per line)",
|
||||
"tasksListPlaceholder": "Enter task list, one per line",
|
||||
"tasksListPlaceholderExample": "Enter task list, one per line, for example:\nScan open ports of 192.168.1.1\nCheck if https://example.com has SQL injection\nEnumerate subdomains of example.com",
|
||||
@@ -1514,13 +1535,22 @@
|
||||
"status": "Status",
|
||||
"createdAt": "Created at",
|
||||
"startedAt": "Started at",
|
||||
"nextRunAt": "Next run at",
|
||||
"scheduleCronAuto": "Allow Cron auto-run",
|
||||
"scheduleCronAutoHint": "When off, the cron expression is kept but the queue will not run on schedule; use Start to run manually.",
|
||||
"lastScheduleTriggerAt": "Last scheduled trigger",
|
||||
"lastScheduleError": "Last schedule error",
|
||||
"lastRunError": "Last run failure summary",
|
||||
"cronSchedulePausedBadge": "Schedule paused",
|
||||
"scheduleToggleFailed": "Failed to update schedule toggle",
|
||||
"completedAt": "Completed at",
|
||||
"taskTotal": "Total tasks",
|
||||
"taskList": "Task list",
|
||||
"startLabel": "Start",
|
||||
"completeLabel": "Complete",
|
||||
"errorLabel": "Error",
|
||||
"resultLabel": "Result"
|
||||
"resultLabel": "Result",
|
||||
"technicalDetails": "Technical details (ID, times, schedule)"
|
||||
},
|
||||
"editBatchTaskModal": {
|
||||
"title": "Edit task",
|
||||
|
||||
@@ -254,6 +254,14 @@
|
||||
"conversationIdLabel": "对话ID",
|
||||
"statusPending": "待执行",
|
||||
"statusPaused": "已暂停",
|
||||
"statusCronCycleIdle": "本轮已完成 · 定时循环中",
|
||||
"statusCronRunning": "执行中 · 定时队列",
|
||||
"cronNextRunLine": "下次执行:{{time}}",
|
||||
"cronRoundDoneProgressHint": "定时队列:子任务已跑完,到点将自动下一轮",
|
||||
"cronRunningProgressHint": "本轮执行中;下一整轮仍按 Cron 与「下次执行时间」排程",
|
||||
"cronPendingScheduled": "Cron 已排程 · 下次 {{time}}",
|
||||
"cronPendingProgressNote": "到点将自动开始;也可手动点「开始执行」立即跑一轮",
|
||||
"cronRecurringCallout": "Cron 队列会在「下次执行时间」自动开始新一轮;关闭「允许 Cron 自动调度」即停止循环。",
|
||||
"confirmCancelTasks": "确定要取消 {{n}} 个任务吗?",
|
||||
"batchCancelResultPartial": "批量取消完成:成功 {{success}} 个,失败 {{fail}} 个",
|
||||
"batchCancelResultSuccess": "成功取消 {{n}} 个任务",
|
||||
@@ -276,6 +284,7 @@
|
||||
"deleteQueueConfirm": "确定要删除这个批量任务队列吗?此操作不可恢复。",
|
||||
"deleteQueueFailed": "删除批量任务队列失败",
|
||||
"batchQueueTitle": "批量任务队列",
|
||||
"batchQueueUntitled": "未命名队列",
|
||||
"resumeExecute": "继续执行",
|
||||
"taskIncomplete": "任务信息不完整",
|
||||
"cannotGetTaskMessageInput": "无法获取任务消息输入框",
|
||||
@@ -1494,6 +1503,18 @@
|
||||
"role": "角色",
|
||||
"defaultRole": "默认",
|
||||
"roleHint": "选择一个角色,所有任务将使用该角色的配置(提示词和工具)执行。",
|
||||
"agentMode": "代理模式",
|
||||
"agentModeSingle": "单代理(ReAct)",
|
||||
"agentModeMulti": "多代理(Eino)",
|
||||
"agentModeHint": "建议默认单代理;复杂任务可使用多代理(需系统已启用多代理)。",
|
||||
"scheduleMode": "调度方式",
|
||||
"scheduleModeManual": "手工执行",
|
||||
"scheduleModeCron": "调度表达式(Cron)",
|
||||
"scheduleModeHint": "手工执行用于一次性任务;Cron 用于周期任务,建议先手工验证任务正确性。",
|
||||
"cronExpr": "Cron 表达式",
|
||||
"cronExprPlaceholder": "例如:0 */2 * * *(每2小时执行一次)",
|
||||
"cronExprHint": "采用标准 5 段 Cron:分 时 日 月 周,例如 `0 2 * * *` 表示每天 02:00 执行。",
|
||||
"cronExprRequired": "请选择 Cron 调度后填写 Cron 表达式",
|
||||
"tasksList": "任务列表(每行一个任务)",
|
||||
"tasksListPlaceholder": "请输入任务列表,每行一个任务",
|
||||
"tasksListPlaceholderExample": "请输入任务列表,每行一个任务,例如:\n扫描 192.168.1.1 的开放端口\n检查 https://example.com 是否存在SQL注入\n枚举 example.com 的子域名",
|
||||
@@ -1514,13 +1535,22 @@
|
||||
"status": "状态",
|
||||
"createdAt": "创建时间",
|
||||
"startedAt": "开始时间",
|
||||
"nextRunAt": "下次执行时间",
|
||||
"scheduleCronAuto": "允许 Cron 自动调度",
|
||||
"scheduleCronAutoHint": "关闭后仅保留表达式配置,不会按时间自动跑;可随时手工点「开始执行」。",
|
||||
"lastScheduleTriggerAt": "最近调度触发时间",
|
||||
"lastScheduleError": "最近调度失败原因",
|
||||
"lastRunError": "最近运行失败摘要",
|
||||
"cronSchedulePausedBadge": "调度已暂停",
|
||||
"scheduleToggleFailed": "更新调度开关失败",
|
||||
"completedAt": "完成时间",
|
||||
"taskTotal": "任务总数",
|
||||
"taskList": "任务列表",
|
||||
"startLabel": "开始",
|
||||
"completeLabel": "完成",
|
||||
"errorLabel": "错误",
|
||||
"resultLabel": "结果"
|
||||
"resultLabel": "结果",
|
||||
"technicalDetails": "技术信息(ID / 时间 / 调度)"
|
||||
},
|
||||
"editBatchTaskModal": {
|
||||
"title": "编辑任务",
|
||||
|
||||
@@ -125,8 +125,6 @@ async function loadConfig(loadTools = true) {
|
||||
if (maMode) maMode.value = (ma.default_mode === 'multi') ? 'multi' : 'single';
|
||||
const maRobot = document.getElementById('multi-agent-robot-use');
|
||||
if (maRobot) maRobot.checked = ma.robot_use_multi_agent === true;
|
||||
const maBatch = document.getElementById('multi-agent-batch-use');
|
||||
if (maBatch) maBatch.checked = ma.batch_use_multi_agent === true;
|
||||
|
||||
// 填充知识库配置
|
||||
const knowledgeEnabledCheckbox = document.getElementById('knowledge-enabled');
|
||||
@@ -820,7 +818,7 @@ async function applySettings() {
|
||||
enabled: document.getElementById('multi-agent-enabled')?.checked === true,
|
||||
default_mode: document.getElementById('multi-agent-default-mode')?.value === 'multi' ? 'multi' : 'single',
|
||||
robot_use_multi_agent: document.getElementById('multi-agent-robot-use')?.checked === true,
|
||||
batch_use_multi_agent: document.getElementById('multi-agent-batch-use')?.checked === true
|
||||
batch_use_multi_agent: false
|
||||
},
|
||||
knowledge: knowledgeConfig,
|
||||
robots: {
|
||||
|
||||
+226
-92
@@ -3,6 +3,60 @@ function _t(key, opts) {
|
||||
return typeof window.t === 'function' ? window.t(key, opts) : key;
|
||||
}
|
||||
|
||||
/** 插值不转 HTML 实体(避免日期里的 / 变成 / 再被 escapeHtml 成乱码) */
|
||||
function _tPlain(key, opts) {
|
||||
if (typeof window.t !== 'function') return key;
|
||||
const base = opts && typeof opts === 'object' ? opts : {};
|
||||
const interp = base.interpolation && typeof base.interpolation === 'object' ? base.interpolation : {};
|
||||
return window.t(key, {
|
||||
...base,
|
||||
interpolation: { escapeValue: false, ...interp }
|
||||
});
|
||||
}
|
||||
|
||||
/** Cron 队列在「本轮 completed」等状态下的展示文案(底层 status 不变,仅 UI 强调循环调度) */
|
||||
function getBatchQueueStatusPresentation(queue) {
|
||||
const map = {
|
||||
pending: { text: _t('tasks.statusPending'), class: 'batch-queue-status-pending' },
|
||||
running: { text: _t('tasks.statusRunning'), class: 'batch-queue-status-running' },
|
||||
paused: { text: _t('tasks.statusPaused'), class: 'batch-queue-status-paused' },
|
||||
completed: { text: _t('tasks.statusCompleted'), class: 'batch-queue-status-completed' },
|
||||
cancelled: { text: _t('tasks.statusCancelled'), class: 'batch-queue-status-cancelled' }
|
||||
};
|
||||
const base = map[queue.status] || { text: queue.status, class: 'batch-queue-status-unknown' };
|
||||
const cronOn = queue.scheduleMode === 'cron' && queue.scheduleEnabled !== false;
|
||||
const nextStr = queue.nextRunAt ? new Date(queue.nextRunAt).toLocaleString() : '';
|
||||
const empty = { sublabel: null, progressNote: null, callout: null };
|
||||
|
||||
if (cronOn && queue.status === 'completed') {
|
||||
return {
|
||||
text: _t('tasks.statusCronCycleIdle'),
|
||||
class: 'batch-queue-status-cron-cycle',
|
||||
sublabel: nextStr ? _tPlain('tasks.cronNextRunLine', { time: nextStr }) : null,
|
||||
progressNote: _t('tasks.cronRoundDoneProgressHint'),
|
||||
callout: _t('tasks.cronRecurringCallout')
|
||||
};
|
||||
}
|
||||
if (cronOn && queue.status === 'running') {
|
||||
return {
|
||||
text: _t('tasks.statusCronRunning'),
|
||||
class: 'batch-queue-status-running batch-queue-cron-active',
|
||||
sublabel: nextStr ? _tPlain('tasks.cronNextRunLine', { time: nextStr }) : null,
|
||||
progressNote: _t('tasks.cronRunningProgressHint'),
|
||||
callout: null
|
||||
};
|
||||
}
|
||||
if (cronOn && queue.status === 'pending' && nextStr) {
|
||||
return {
|
||||
...base,
|
||||
...empty,
|
||||
sublabel: _tPlain('tasks.cronPendingScheduled', { time: nextStr }),
|
||||
progressNote: _t('tasks.cronPendingProgressNote')
|
||||
};
|
||||
}
|
||||
return { ...base, ...empty };
|
||||
}
|
||||
|
||||
// HTML转义函数(如果未定义)
|
||||
if (typeof escapeHtml === 'undefined') {
|
||||
function escapeHtml(text) {
|
||||
@@ -725,6 +779,9 @@ async function showBatchImportModal() {
|
||||
const input = document.getElementById('batch-tasks-input');
|
||||
const titleInput = document.getElementById('batch-queue-title');
|
||||
const roleSelect = document.getElementById('batch-queue-role');
|
||||
const agentModeSelect = document.getElementById('batch-queue-agent-mode');
|
||||
const scheduleModeSelect = document.getElementById('batch-queue-schedule-mode');
|
||||
const cronExprInput = document.getElementById('batch-queue-cron-expr');
|
||||
if (modal && input) {
|
||||
input.value = '';
|
||||
if (titleInput) {
|
||||
@@ -734,6 +791,16 @@ async function showBatchImportModal() {
|
||||
if (roleSelect) {
|
||||
roleSelect.value = '';
|
||||
}
|
||||
if (agentModeSelect) {
|
||||
agentModeSelect.value = 'single';
|
||||
}
|
||||
if (scheduleModeSelect) {
|
||||
scheduleModeSelect.value = 'manual';
|
||||
}
|
||||
if (cronExprInput) {
|
||||
cronExprInput.value = '';
|
||||
}
|
||||
handleBatchScheduleModeChange();
|
||||
updateBatchImportStats('');
|
||||
|
||||
// 加载并填充角色列表
|
||||
@@ -776,6 +843,24 @@ function closeBatchImportModal() {
|
||||
}
|
||||
}
|
||||
|
||||
function handleBatchScheduleModeChange() {
|
||||
const scheduleModeSelect = document.getElementById('batch-queue-schedule-mode');
|
||||
const cronGroup = document.getElementById('batch-queue-cron-group');
|
||||
const cronExprInput = document.getElementById('batch-queue-cron-expr');
|
||||
const isCron = scheduleModeSelect && scheduleModeSelect.value === 'cron';
|
||||
if (cronGroup) {
|
||||
cronGroup.style.display = isCron ? 'block' : 'none';
|
||||
}
|
||||
if (cronExprInput) {
|
||||
if (isCron) {
|
||||
cronExprInput.setAttribute('required', 'required');
|
||||
} else {
|
||||
cronExprInput.removeAttribute('required');
|
||||
cronExprInput.value = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 更新新建任务统计
|
||||
function updateBatchImportStats(text) {
|
||||
const statsEl = document.getElementById('batch-import-stats');
|
||||
@@ -807,6 +892,9 @@ async function createBatchQueue() {
|
||||
const input = document.getElementById('batch-tasks-input');
|
||||
const titleInput = document.getElementById('batch-queue-title');
|
||||
const roleSelect = document.getElementById('batch-queue-role');
|
||||
const agentModeSelect = document.getElementById('batch-queue-agent-mode');
|
||||
const scheduleModeSelect = document.getElementById('batch-queue-schedule-mode');
|
||||
const cronExprInput = document.getElementById('batch-queue-cron-expr');
|
||||
if (!input) return;
|
||||
|
||||
const text = input.value.trim();
|
||||
@@ -827,6 +915,13 @@ async function createBatchQueue() {
|
||||
|
||||
// 获取角色(可选,空字符串表示默认角色)
|
||||
const role = roleSelect ? roleSelect.value || '' : '';
|
||||
const agentMode = agentModeSelect ? (agentModeSelect.value === 'multi' ? 'multi' : 'single') : 'single';
|
||||
const scheduleMode = scheduleModeSelect ? (scheduleModeSelect.value === 'cron' ? 'cron' : 'manual') : 'manual';
|
||||
const cronExpr = cronExprInput ? cronExprInput.value.trim() : '';
|
||||
if (scheduleMode === 'cron' && !cronExpr) {
|
||||
alert(_t('batchImportModal.cronExprRequired'));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await apiFetch('/api/batch-tasks', {
|
||||
@@ -834,7 +929,7 @@ async function createBatchQueue() {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ title, tasks, role }),
|
||||
body: JSON.stringify({ title, tasks, role, agentMode, scheduleMode, cronExpr }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
@@ -978,15 +1073,7 @@ function renderBatchQueues() {
|
||||
}
|
||||
|
||||
list.innerHTML = queues.map(queue => {
|
||||
const statusMap = {
|
||||
'pending': { text: _t('tasks.statusPending'), class: 'batch-queue-status-pending' },
|
||||
'running': { text: _t('tasks.statusRunning'), class: 'batch-queue-status-running' },
|
||||
'paused': { text: _t('tasks.statusPaused'), class: 'batch-queue-status-paused' },
|
||||
'completed': { text: _t('tasks.statusCompleted'), class: 'batch-queue-status-completed' },
|
||||
'cancelled': { text: _t('tasks.statusCancelled'), class: 'batch-queue-status-cancelled' }
|
||||
};
|
||||
|
||||
const status = statusMap[queue.status] || { text: queue.status, class: 'batch-queue-status-unknown' };
|
||||
const pres = getBatchQueueStatusPresentation(queue);
|
||||
|
||||
// 统计任务状态
|
||||
const stats = {
|
||||
@@ -1010,44 +1097,59 @@ function renderBatchQueues() {
|
||||
// 允许删除待执行、已完成或已取消状态的队列
|
||||
const canDelete = queue.status === 'pending' || queue.status === 'completed' || queue.status === 'cancelled';
|
||||
|
||||
const titleDisplay = queue.title ? `<span class="batch-queue-title" style="font-weight: 600; color: var(--text-primary); margin-right: 8px;">${escapeHtml(queue.title)}</span>` : '';
|
||||
|
||||
// 显示角色信息(使用正确的角色图标)
|
||||
const loadedRoles = batchQueuesState.loadedRoles || [];
|
||||
const roleIcon = getRoleIconForDisplay(queue.role, loadedRoles);
|
||||
const roleName = queue.role && queue.role !== '' ? queue.role : _t('batchQueueDetailModal.defaultRole');
|
||||
const roleDisplay = `<span class="batch-queue-role" style="margin-right: 8px;" title="${_t('batchQueueDetailModal.role')}: ${escapeHtml(roleName)}">${roleIcon} ${escapeHtml(roleName)}</span>`;
|
||||
|
||||
const isCronCycleIdle = queue.scheduleMode === 'cron' && queue.scheduleEnabled !== false && queue.status === 'completed';
|
||||
const cardMod = isCronCycleIdle ? ' batch-queue-item--cron-wait' : '';
|
||||
const progressFillMod = isCronCycleIdle ? ' batch-queue-progress-fill--cron-wait' : '';
|
||||
|
||||
const agentLabel = queue.agentMode === 'multi' ? _t('batchImportModal.agentModeMulti') : _t('batchImportModal.agentModeSingle');
|
||||
let scheduleLabel = queue.scheduleMode === 'cron' ? _t('batchImportModal.scheduleModeCron') : _t('batchImportModal.scheduleModeManual');
|
||||
if (queue.scheduleMode === 'cron' && queue.cronExpr) {
|
||||
scheduleLabel += ` (${queue.cronExpr})`;
|
||||
}
|
||||
const configLine = [roleName, agentLabel, scheduleLabel].map(s => escapeHtml(s)).join(' · ');
|
||||
const cronPausedNote = queue.scheduleMode === 'cron' && queue.scheduleEnabled === false
|
||||
? ` <span class="batch-queue-inline-warn" title="${escapeHtml(_t('batchQueueDetailModal.scheduleCronAutoHint'))}">(${escapeHtml(_t('batchQueueDetailModal.cronSchedulePausedBadge'))})</span>`
|
||||
: '';
|
||||
const shortId = queue.id.length > 14 ? escapeHtml(queue.id.slice(0, 12)) + '\u2026' : escapeHtml(queue.id);
|
||||
const titleBlock = queue.title
|
||||
? `<h4 class="batch-queue-card-title">${escapeHtml(queue.title)}</h4>`
|
||||
: `<h4 class="batch-queue-card-title batch-queue-card-title--muted">${escapeHtml(_t('tasks.batchQueueUntitled'))}</h4>`;
|
||||
const doneCount = stats.completed + stats.failed + stats.cancelled;
|
||||
const statsCompact = `<span class="batch-queue-statsline__item">${escapeHtml(_t('tasks.totalLabel'))}\u00a0${stats.total}</span><span class="batch-queue-statsline__sep">\u00b7</span><span class="batch-queue-statsline__item">${escapeHtml(_t('tasks.pendingLabel'))}\u00a0${stats.pending}</span><span class="batch-queue-statsline__sep">\u00b7</span><span class="batch-queue-statsline__item">${escapeHtml(_t('tasks.runningLabel'))}\u00a0${stats.running}</span><span class="batch-queue-statsline__sep">\u00b7</span><span class="batch-queue-statsline__item batch-queue-statsline__item--ok">${escapeHtml(_t('tasks.completedLabel'))}\u00a0${stats.completed}</span><span class="batch-queue-statsline__sep">\u00b7</span><span class="batch-queue-statsline__item batch-queue-statsline__item--err">${escapeHtml(_t('tasks.failedLabel'))}\u00a0${stats.failed}</span>${stats.cancelled > 0 ? `<span class="batch-queue-statsline__sep">\u00b7</span><span class="batch-queue-statsline__item">${escapeHtml(_t('tasks.cancelledLabel'))}\u00a0${stats.cancelled}</span>` : ''}`;
|
||||
|
||||
return `
|
||||
<div class="batch-queue-item" data-queue-id="${queue.id}" onclick="showBatchQueueDetail('${queue.id}')">
|
||||
<div class="batch-queue-header">
|
||||
<div class="batch-queue-info" style="flex: 1;">
|
||||
${titleDisplay}
|
||||
${roleDisplay}
|
||||
<span class="batch-queue-status ${status.class}">${status.text}</span>
|
||||
<span class="batch-queue-id">${_t('tasks.queueIdLabel')}: ${escapeHtml(queue.id)}</span>
|
||||
<span class="batch-queue-time">${_t('tasks.createdTimeLabel')}: ${new Date(queue.createdAt).toLocaleString()}</span>
|
||||
</div>
|
||||
<div class="batch-queue-progress">
|
||||
<div class="batch-queue-progress-bar">
|
||||
<div class="batch-queue-progress-fill" style="width: ${progress}%"></div>
|
||||
<div class="batch-queue-item batch-queue-item--compact${cardMod}" data-queue-id="${queue.id}" onclick="showBatchQueueDetail('${queue.id}')">
|
||||
<div class="batch-queue-item__inner">
|
||||
<div class="batch-queue-item__top">
|
||||
<div class="batch-queue-item__title-col">
|
||||
${titleBlock}
|
||||
<p class="batch-queue-item__config">${configLine}${cronPausedNote}</p>
|
||||
<p class="batch-queue-item__idline"><code title="${escapeHtml(queue.id)}">${shortId}</code><span class="batch-queue-item__idsep">\u00b7</span><span>${escapeHtml(_t('tasks.createdTimeLabel'))}\u00a0${escapeHtml(new Date(queue.createdAt).toLocaleString())}</span></p>
|
||||
</div>
|
||||
<div class="batch-queue-item__top-actions" onclick="event.stopPropagation();">
|
||||
${canDelete ? `<button type="button" class="batch-queue-icon-btn" onclick="deleteBatchQueueFromList('${queue.id}')" title="${escapeHtml(_t('tasks.deleteQueue'))}" aria-label="${escapeHtml(_t('tasks.deleteQueue'))}"><svg class="batch-queue-icon-btn__svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 6h18"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><path d="M10 11v6"/><path d="M14 11v6"/></svg></button>` : ''}
|
||||
</div>
|
||||
<span class="batch-queue-progress-text">${progress}% (${stats.completed + stats.failed + stats.cancelled}/${stats.total})</span>
|
||||
</div>
|
||||
<div class="batch-queue-actions" style="display: flex; align-items: center; gap: 8px; margin-left: 12px;" onclick="event.stopPropagation();">
|
||||
${canDelete ? `<button class="btn-secondary btn-small btn-danger" onclick="deleteBatchQueueFromList('${queue.id}')" title="${_t('tasks.deleteQueue')}">${_t('common.delete')}</button>` : ''}
|
||||
<div class="batch-queue-item__mid">
|
||||
<div class="batch-queue-item__mid-left">
|
||||
<span class="batch-queue-status ${pres.class}">${escapeHtml(pres.text)}</span>
|
||||
${pres.sublabel ? `<span class="batch-queue-item__sublabel">${escapeHtml(pres.sublabel)}</span>` : ''}
|
||||
</div>
|
||||
<div class="batch-queue-item__mid-right">
|
||||
<div class="batch-queue-progress-bar batch-queue-progress-bar--card batch-queue-progress-bar--list">
|
||||
<div class="batch-queue-progress-fill${progressFillMod}" style="width: ${progress}%"></div>
|
||||
</div>
|
||||
<span class="batch-queue-item__pct">${progress}%\u00a0<span class="batch-queue-item__pct-frac">(${doneCount}/${stats.total})</span></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="batch-queue-stats">
|
||||
<span>${_t('tasks.totalLabel')}: ${stats.total}</span>
|
||||
<span>${_t('tasks.pendingLabel')}: ${stats.pending}</span>
|
||||
<span>${_t('tasks.runningLabel')}: ${stats.running}</span>
|
||||
<span style="color: var(--success-color);">${_t('tasks.completedLabel')}: ${stats.completed}</span>
|
||||
<span style="color: var(--error-color);">${_t('tasks.failedLabel')}: ${stats.failed}</span>
|
||||
${stats.cancelled > 0 ? `<span style="color: var(--text-secondary);">${_t('tasks.cancelledLabel')}: ${stats.cancelled}</span>` : ''}
|
||||
<div class="batch-queue-statsline" aria-label="${escapeHtml(_t('tasks.batchQueueTitle'))}">${statsCompact}</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
}).join('');
|
||||
|
||||
// 渲染分页控件
|
||||
@@ -1198,7 +1300,8 @@ async function showBatchQueueDetail(queueId) {
|
||||
const result = await response.json();
|
||||
const queue = result.queue;
|
||||
batchQueuesState.currentQueueId = queueId;
|
||||
|
||||
const pres = getBatchQueueStatusPresentation(queue);
|
||||
|
||||
if (title) {
|
||||
// textContent 本身会做转义;这里不要再 escapeHtml,否则会把 && 显示成 &...(看起来像“变形/乱码”)
|
||||
title.textContent = queue.title ? _t('tasks.batchQueueTitle') + ' - ' + String(queue.title) : _t('tasks.batchQueueTitle');
|
||||
@@ -1227,15 +1330,6 @@ async function showBatchQueueDetail(queueId) {
|
||||
deleteBtn.style.display = (queue.status === 'pending' || queue.status === 'completed' || queue.status === 'cancelled' || queue.status === 'paused') ? 'inline-block' : 'none';
|
||||
}
|
||||
|
||||
// 队列状态映射
|
||||
const queueStatusMap = {
|
||||
'pending': { text: _t('tasks.statusPending'), class: 'batch-queue-status-pending' },
|
||||
'running': { text: _t('tasks.statusRunning'), class: 'batch-queue-status-running' },
|
||||
'paused': { text: _t('tasks.statusPaused'), class: 'batch-queue-status-paused' },
|
||||
'completed': { text: _t('tasks.statusCompleted'), class: 'batch-queue-status-completed' },
|
||||
'cancelled': { text: _t('tasks.statusCancelled'), class: 'batch-queue-status-cancelled' }
|
||||
};
|
||||
|
||||
// 任务状态映射
|
||||
const taskStatusMap = {
|
||||
'pending': { text: _t('tasks.statusPending'), class: 'batch-task-status-pending' },
|
||||
@@ -1245,13 +1339,10 @@ async function showBatchQueueDetail(queueId) {
|
||||
'cancelled': { text: _t('tasks.statusCancelled'), class: 'batch-task-status-cancelled' }
|
||||
};
|
||||
|
||||
// 获取角色信息(如果队列有角色配置)
|
||||
let roleDisplay = '';
|
||||
let roleLineVal = '';
|
||||
if (queue.role && queue.role !== '') {
|
||||
// 如果有角色配置,尝试获取角色详细信息
|
||||
let roleName = queue.role;
|
||||
let roleIcon = '👤';
|
||||
// 从已加载的角色列表中查找角色图标
|
||||
let roleIcon = '\uD83D\uDC64';
|
||||
if (Array.isArray(loadedRoles) && loadedRoles.length > 0) {
|
||||
const role = loadedRoles.find(r => r.name === roleName);
|
||||
if (role && role.icon) {
|
||||
@@ -1262,23 +1353,21 @@ async function showBatchQueueDetail(queueId) {
|
||||
const codePoint = parseInt(unicodeMatch[1], 16);
|
||||
icon = String.fromCodePoint(codePoint);
|
||||
} catch (e) {
|
||||
// 转换失败,使用默认图标
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
roleIcon = icon;
|
||||
}
|
||||
}
|
||||
roleDisplay = `<div class="detail-item">
|
||||
<span class="detail-label">` + _t('batchQueueDetailModal.role') + `</span>
|
||||
<span class="detail-value">${roleIcon} ${escapeHtml(roleName)}</span>
|
||||
</div>`;
|
||||
roleLineVal = roleIcon + ' ' + escapeHtml(roleName);
|
||||
} else {
|
||||
// 默认角色
|
||||
roleDisplay = `<div class="detail-item">
|
||||
<span class="detail-label">` + _t('batchQueueDetailModal.role') + `</span>
|
||||
<span class="detail-value">🔵 ` + _t('batchQueueDetailModal.defaultRole') + `</span>
|
||||
</div>`;
|
||||
roleLineVal = '\uD83D\uDD35 ' + escapeHtml(_t('batchQueueDetailModal.defaultRole'));
|
||||
}
|
||||
const agentModeText = queue.agentMode === 'multi' ? _t('batchImportModal.agentModeMulti') : _t('batchImportModal.agentModeSingle');
|
||||
const scheduleModeText = queue.scheduleMode === 'cron' ? _t('batchImportModal.scheduleModeCron') : _t('batchImportModal.scheduleModeManual');
|
||||
const scheduleDetail = escapeHtml(scheduleModeText) + (queue.scheduleMode === 'cron' && queue.cronExpr ? `(${escapeHtml(queue.cronExpr)})` : '');
|
||||
const showProgressNoteInModal = !!(pres.progressNote && !pres.callout);
|
||||
|
||||
|
||||
// 保存滚动位置,防止刷新时滚动条弹回顶部
|
||||
const modalBody = content.closest('.modal-body');
|
||||
@@ -1287,36 +1376,34 @@ async function showBatchQueueDetail(queueId) {
|
||||
const savedTasksListScrollTop = tasksList ? tasksList.scrollTop : 0;
|
||||
|
||||
content.innerHTML = `
|
||||
<div class="batch-queue-detail-info">
|
||||
${queue.title ? `<div class="detail-item">
|
||||
<span class="detail-label">` + _t('batchQueueDetailModal.queueTitle') + `</span>
|
||||
<span class="detail-value">${escapeHtml(queue.title)}</span>
|
||||
</div>` : ''}
|
||||
${roleDisplay}
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">` + _t('batchQueueDetailModal.queueId') + `</span>
|
||||
<span class="detail-value"><code>${escapeHtml(queue.id)}</code></span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">` + _t('batchQueueDetailModal.status') + `</span>
|
||||
<span class="detail-value"><span class="batch-queue-status ${queueStatusMap[queue.status]?.class || ''}">${queueStatusMap[queue.status]?.text || queue.status}</span></span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">` + _t('batchQueueDetailModal.createdAt') + `</span>
|
||||
<span class="detail-value">${new Date(queue.createdAt).toLocaleString()}</span>
|
||||
</div>
|
||||
${queue.startedAt ? `<div class="detail-item">
|
||||
<span class="detail-label">` + _t('batchQueueDetailModal.startedAt') + `</span>
|
||||
<span class="detail-value">${new Date(queue.startedAt).toLocaleString()}</span>
|
||||
</div>` : ''}
|
||||
${queue.completedAt ? `<div class="detail-item">
|
||||
<span class="detail-label">` + _t('batchQueueDetailModal.completedAt') + `</span>
|
||||
<span class="detail-value">${new Date(queue.completedAt).toLocaleString()}</span>
|
||||
</div>` : ''}
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">` + _t('batchQueueDetailModal.taskTotal') + `</span>
|
||||
<span class="detail-value">${queue.tasks.length}</span>
|
||||
<div class="batch-queue-detail-layout">
|
||||
<section class="batch-queue-detail-hero">
|
||||
<span class="batch-queue-status ${pres.class}">${escapeHtml(pres.text)}</span>
|
||||
${pres.sublabel ? `<p class="batch-queue-detail-hero__sub">${escapeHtml(pres.sublabel)}</p>` : ''}
|
||||
${showProgressNoteInModal ? `<p class="batch-queue-detail-hero__note">${escapeHtml(pres.progressNote)}</p>` : ''}
|
||||
</section>
|
||||
<section class="batch-queue-detail-kv">
|
||||
${queue.title ? `<div class="bq-kv"><span class="bq-kv__k">${escapeHtml(_t('batchQueueDetailModal.queueTitle'))}</span><span class="bq-kv__v">${escapeHtml(queue.title)}</span></div>` : ''}
|
||||
<div class="bq-kv"><span class="bq-kv__k">${escapeHtml(_t('batchQueueDetailModal.role'))}</span><span class="bq-kv__v">${roleLineVal}</span></div>
|
||||
<div class="bq-kv"><span class="bq-kv__k">${escapeHtml(_t('batchImportModal.agentMode'))}</span><span class="bq-kv__v">${escapeHtml(agentModeText)}</span></div>
|
||||
<div class="bq-kv"><span class="bq-kv__k">${escapeHtml(_t('batchImportModal.scheduleMode'))}</span><span class="bq-kv__v">${scheduleDetail}</span></div>
|
||||
<div class="bq-kv"><span class="bq-kv__k">${escapeHtml(_t('batchQueueDetailModal.taskTotal'))}</span><span class="bq-kv__v">${queue.tasks.length}</span></div>
|
||||
${queue.scheduleMode === 'cron' ? `<div class="bq-kv bq-kv--block"><span class="bq-kv__k">${escapeHtml(_t('batchQueueDetailModal.scheduleCronAuto'))}</span><span class="bq-kv__v bq-kv__v--control"><label class="bq-cron-toggle"><input type="checkbox" ${queue.scheduleEnabled !== false ? 'checked' : ''} onchange="updateBatchQueueScheduleEnabled(this.checked)" /><span class="bq-cron-toggle__hint">${escapeHtml(_t('batchQueueDetailModal.scheduleCronAutoHint'))}</span></label></span></div>` : ''}
|
||||
</section>
|
||||
${queue.lastScheduleError ? `<div class="bq-alert bq-alert--err"><strong>${escapeHtml(_t('batchQueueDetailModal.lastScheduleError'))}</strong><p>${escapeHtml(queue.lastScheduleError)}</p></div>` : ''}
|
||||
${queue.lastRunError ? `<div class="bq-alert bq-alert--err"><strong>${escapeHtml(_t('batchQueueDetailModal.lastRunError'))}</strong><p>${escapeHtml(queue.lastRunError)}</p></div>` : ''}
|
||||
${pres.callout ? `<div class="batch-queue-cron-callout batch-queue-cron-callout--compact"><span class="batch-queue-cron-callout-icon" aria-hidden="true">\u21BB</span><p>${escapeHtml(pres.callout)}</p></div>` : ''}
|
||||
<details class="batch-queue-detail-tech">
|
||||
<summary class="batch-queue-detail-tech__sum">${escapeHtml(_t('batchQueueDetailModal.technicalDetails'))}</summary>
|
||||
<div class="batch-queue-detail-tech__body">
|
||||
<div class="bq-kv"><span class="bq-kv__k">${escapeHtml(_t('batchQueueDetailModal.queueId'))}</span><span class="bq-kv__v"><code>${escapeHtml(queue.id)}</code></span></div>
|
||||
<div class="bq-kv"><span class="bq-kv__k">${escapeHtml(_t('batchQueueDetailModal.createdAt'))}</span><span class="bq-kv__v">${escapeHtml(new Date(queue.createdAt).toLocaleString())}</span></div>
|
||||
${queue.startedAt ? `<div class="bq-kv"><span class="bq-kv__k">${escapeHtml(_t('batchQueueDetailModal.startedAt'))}</span><span class="bq-kv__v">${escapeHtml(new Date(queue.startedAt).toLocaleString())}</span></div>` : ''}
|
||||
${queue.completedAt ? `<div class="bq-kv"><span class="bq-kv__k">${escapeHtml(_t('batchQueueDetailModal.completedAt'))}</span><span class="bq-kv__v">${escapeHtml(new Date(queue.completedAt).toLocaleString())}</span></div>` : ''}
|
||||
${queue.scheduleMode === 'cron' && queue.nextRunAt && !pres.sublabel ? `<div class="bq-kv"><span class="bq-kv__k">${escapeHtml(_t('batchQueueDetailModal.nextRunAt'))}</span><span class="bq-kv__v">${escapeHtml(new Date(queue.nextRunAt).toLocaleString())}</span></div>` : ''}
|
||||
${queue.lastScheduleTriggerAt ? `<div class="bq-kv"><span class="bq-kv__k">${escapeHtml(_t('batchQueueDetailModal.lastScheduleTriggerAt'))}</span><span class="bq-kv__v">${escapeHtml(new Date(queue.lastScheduleTriggerAt).toLocaleString())}</span></div>` : ''}
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
<div class="batch-queue-tasks-list">
|
||||
<h4>` + _t('batchQueueDetailModal.taskList') + `</h4>
|
||||
@@ -1834,6 +1921,28 @@ async function deleteBatchTask(queueId, taskId) {
|
||||
}
|
||||
}
|
||||
|
||||
async function updateBatchQueueScheduleEnabled(enabled) {
|
||||
const queueId = batchQueuesState.currentQueueId;
|
||||
if (!queueId) return;
|
||||
try {
|
||||
const response = await apiFetch(`/api/batch-tasks/${queueId}/schedule-enabled`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ scheduleEnabled: enabled }),
|
||||
});
|
||||
if (!response.ok) {
|
||||
const result = await response.json().catch(() => ({}));
|
||||
throw new Error(result.error || _t('batchQueueDetailModal.scheduleToggleFailed'));
|
||||
}
|
||||
showBatchQueueDetail(queueId);
|
||||
refreshBatchQueues();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
alert(_t('batchQueueDetailModal.scheduleToggleFailed') + ': ' + e.message);
|
||||
showBatchQueueDetail(queueId);
|
||||
}
|
||||
}
|
||||
|
||||
// 导出函数
|
||||
window.showBatchImportModal = showBatchImportModal;
|
||||
window.closeBatchImportModal = closeBatchImportModal;
|
||||
@@ -1857,3 +1966,28 @@ window.closeAddBatchTaskModal = closeAddBatchTaskModal;
|
||||
window.saveAddBatchTask = saveAddBatchTask;
|
||||
window.deleteBatchTaskFromElement = deleteBatchTaskFromElement;
|
||||
window.deleteBatchQueueFromList = deleteBatchQueueFromList;
|
||||
window.handleBatchScheduleModeChange = handleBatchScheduleModeChange;
|
||||
window.updateBatchQueueScheduleEnabled = updateBatchQueueScheduleEnabled;
|
||||
|
||||
// 语言切换后,列表/分页/详情弹窗由 JS 渲染的文案需用当前语言重绘(applyTranslations 不会处理 innerHTML 内容)
|
||||
document.addEventListener('languagechange', function () {
|
||||
try {
|
||||
const tasksPage = document.getElementById('page-tasks');
|
||||
if (!tasksPage || !tasksPage.classList.contains('active')) {
|
||||
return;
|
||||
}
|
||||
if (document.getElementById('batch-queues-list')) {
|
||||
renderBatchQueues();
|
||||
}
|
||||
const detailModal = document.getElementById('batch-queue-detail-modal');
|
||||
if (
|
||||
detailModal &&
|
||||
detailModal.style.display === 'block' &&
|
||||
batchQueuesState.currentQueueId
|
||||
) {
|
||||
showBatchQueueDetail(batchQueuesState.currentQueueId);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('languagechange tasks refresh failed', e);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1430,14 +1430,6 @@
|
||||
</label>
|
||||
<small class="form-hint" data-i18n="settingsBasic.multiAgentRobotUseHint">需同时勾选「启用多代理」;调用量与成本更高。</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" id="multi-agent-batch-use" class="modern-checkbox" />
|
||||
<span class="checkbox-custom"></span>
|
||||
<span class="checkbox-text" data-i18n="settingsBasic.multiAgentBatchUse">批量任务队列也使用多代理</span>
|
||||
</label>
|
||||
<small class="form-hint" data-i18n="settingsBasic.multiAgentBatchUseHint">开启后,任务管理中按队列执行的每个子任务将走 Eino DeepAgent(需启用多代理)。</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2322,6 +2314,27 @@ version: 1.0.0<br>
|
||||
</select>
|
||||
<div class="form-hint" style="margin-top: 4px;" data-i18n="batchImportModal.roleHint">选择一个角色,所有任务将使用该角色的配置(提示词和工具)执行。</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="batch-queue-agent-mode" data-i18n="batchImportModal.agentMode">代理模式</label>
|
||||
<select id="batch-queue-agent-mode" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; font-size: 0.875rem;">
|
||||
<option value="single" data-i18n="batchImportModal.agentModeSingle">单代理(ReAct)</option>
|
||||
<option value="multi" data-i18n="batchImportModal.agentModeMulti">多代理(Eino)</option>
|
||||
</select>
|
||||
<div class="form-hint" style="margin-top: 4px;" data-i18n="batchImportModal.agentModeHint">建议默认单代理;复杂任务可使用多代理(需系统已启用多代理)。</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="batch-queue-schedule-mode" data-i18n="batchImportModal.scheduleMode">调度方式</label>
|
||||
<select id="batch-queue-schedule-mode" onchange="handleBatchScheduleModeChange()" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; font-size: 0.875rem;">
|
||||
<option value="manual" data-i18n="batchImportModal.scheduleModeManual">手工执行</option>
|
||||
<option value="cron" data-i18n="batchImportModal.scheduleModeCron">调度表达式(Cron)</option>
|
||||
</select>
|
||||
<div class="form-hint" style="margin-top: 4px;" data-i18n="batchImportModal.scheduleModeHint">手工执行用于一次性任务;Cron 用于周期任务,建议先手工验证任务正确性。</div>
|
||||
</div>
|
||||
<div class="form-group" id="batch-queue-cron-group" style="display: none;">
|
||||
<label for="batch-queue-cron-expr"><span data-i18n="batchImportModal.cronExpr">Cron 表达式</span><span style="color: red;">*</span></label>
|
||||
<input type="text" id="batch-queue-cron-expr" data-i18n="batchImportModal.cronExprPlaceholder" data-i18n-attr="placeholder" placeholder="例如:0 */2 * * *(每2小时执行一次)" />
|
||||
<div class="form-hint" style="margin-top: 4px;" data-i18n="batchImportModal.cronExprHint">采用标准 5 段 Cron:分 时 日 月 周,例如 `0 2 * * *` 表示每天 02:00 执行。</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="batch-tasks-input"><span data-i18n="batchImportModal.tasksList">任务列表(每行一个任务)</span><span style="color: red;">*</span></label>
|
||||
<textarea id="batch-tasks-input" rows="15" data-i18n="batchImportModal.tasksListPlaceholderExample" data-i18n-attr="placeholder" placeholder="请输入任务列表,每行一个任务,例如: 扫描 192.168.1.1 的开放端口 检查 https://example.com 是否存在SQL注入 枚举 example.com 的子域名" style="font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; font-size: 0.875rem; line-height: 1.5;"></textarea>
|
||||
|
||||
Reference in New Issue
Block a user