@goodandready/dsh-cron 0.3.4 → 0.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/api-helpers.js +22 -0
- package/lib/api.js +6 -0
- package/lib/client.js +554 -264
- package/lib/routes.js +21 -0
- package/lib/timeline.js +230 -0
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -285,6 +285,12 @@ en: {
|
|
|
285
285
|
'form.adaptiveEnabled': 'Adaptive Polling', 'form.adaptiveHint': 'Exponential backoff when no changes are detected',
|
|
286
286
|
'liveLog.modalTitle': 'Real-Time Streaming Execution Logs', 'liveLog.viewLog': 'Live Stream',
|
|
287
287
|
'liveLog.autoScroll': 'Auto-scroll', 'liveLog.connecting': 'Connecting to log stream...', 'liveLog.waiting': 'Waiting for runner output...', 'liveLog.completed': 'Execution stream completed', 'liveLog.close': 'Close Stream',
|
|
288
|
+
'timeline.modalTitle': '24-Hour Schedule Timeline', 'timeline.buttonLabel': 'Timeline',
|
|
289
|
+
'timeline.buttonTitle': 'View 24-hour projected schedule and concurrency timeline',
|
|
290
|
+
'timeline.tabTimeline': 'Schedule Timeline', 'timeline.legendDesc': 'Projected run distribution across 24 hourly buckets:',
|
|
291
|
+
'timeline.peakWarning': 'Peak concurrency warning',
|
|
292
|
+
'heatmap.modalTitle': 'Cost & Token Burn Heatmap', 'heatmap.buttonLabel': 'Heatmap',
|
|
293
|
+
'heatmap.tabHeatmap': 'Cost Heatmap', 'heatmap.matrixDesc': 'Weekly hour-of-day cost concentration (7 days × 24 hours):',
|
|
288
294
|
'digest.modalTitle': 'Automation & Cost Digest', 'digest.buttonLabel': 'Digest',
|
|
289
295
|
'digest.buttonTitle': 'View 7-day or 30-day automation analytics and cost digest',
|
|
290
296
|
'digest.period7': 'Last 7 Days', 'digest.period30': 'Last 30 Days',
|
|
@@ -461,6 +467,12 @@ zh: {
|
|
|
461
467
|
'form.adaptiveEnabled': '自适应轮询 (Adaptive Polling)', 'form.adaptiveHint': '数据无变化时指数级延长执行间隔',
|
|
462
468
|
'liveLog.modalTitle': '实时任务执行日志流', 'liveLog.viewLog': '实时日志',
|
|
463
469
|
'liveLog.autoScroll': '自动滚动', 'liveLog.connecting': '正在连接日志流...', 'liveLog.waiting': '等待运行器输出...', 'liveLog.completed': '日志流已结束', 'liveLog.close': '关闭日志流',
|
|
470
|
+
'timeline.modalTitle': '24小时调度时间线', 'timeline.buttonLabel': '时间线',
|
|
471
|
+
'timeline.buttonTitle': '查看24小时预测任务调度与并发时间线',
|
|
472
|
+
'timeline.tabTimeline': '调度时间线', 'timeline.legendDesc': '未来24小时各时段任务预计执行分布:',
|
|
473
|
+
'timeline.peakWarning': '并发峰值预警',
|
|
474
|
+
'heatmap.modalTitle': '成本与Token消耗热力图', 'heatmap.buttonLabel': '热力图',
|
|
475
|
+
'heatmap.tabHeatmap': '成本热力图', 'heatmap.matrixDesc': '每周各时段成本分布矩阵(7天 × 24小时):',
|
|
464
476
|
'digest.modalTitle': '自动化与成本分析摘要', 'digest.buttonLabel': '分析摘要',
|
|
465
477
|
'digest.buttonTitle': '查看 7 天或 30 天运行统计与花费摘要',
|
|
466
478
|
'digest.period7': '最近 7 天', 'digest.period30': '最近 30 天',
|
|
@@ -489,233 +501,222 @@ return s;
|
|
|
489
501
|
};
|
|
490
502
|
const T = (key, vars) => translate(key, vars);
|
|
491
503
|
const STYLES = `
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
.dsh-cron-task-title { flex-wrap: wrap; row-gap: 4px; }
|
|
709
|
-
.dsh-cron-modal { padding: 14px; }
|
|
710
|
-
.dsh-cron-modal-foot { flex-direction: column-reverse; }
|
|
711
|
-
.dsh-cron-modal-foot > button { width: 100%; }
|
|
712
|
-
.dsh-cron-filter-panel { padding: 8px 10px; }
|
|
713
|
-
.dsh-cron-card { padding: 14px; }
|
|
714
|
-
/* 16px keeps iOS Safari from zooming the page when a field is focused. */
|
|
715
|
-
.dsh-cron-modal input, .dsh-cron-modal select, .dsh-cron-modal textarea,
|
|
716
|
-
.dsh-cron-card input, .dsh-cron-search-input, .dsh-cron-filter-field select { font-size: 16px; }
|
|
717
|
-
}
|
|
718
|
-
`;
|
|
504
|
+
:root {
|
|
505
|
+
--dsh-cron-success: var(--dsw-alias-state-success-primary);
|
|
506
|
+
--dsh-cron-success-bg: color-mix(in srgb, var(--dsh-cron-success) 10%, transparent);
|
|
507
|
+
--dsh-cron-danger: var(--dsw-alias-state-error-primary);
|
|
508
|
+
--dsh-cron-danger-bg: color-mix(in srgb, var(--dsh-cron-danger) 10%, transparent);
|
|
509
|
+
--dsh-cron-info: var(--dsw-alias-state-brand-primary);
|
|
510
|
+
--dsh-cron-info-bg: color-mix(in srgb, var(--dsh-cron-info) 12%, transparent);
|
|
511
|
+
--dsh-cron-accent: var(--dsw-alias-state-info-primary);
|
|
512
|
+
--dsh-cron-accent-bg: color-mix(in srgb, var(--dsh-cron-accent) 12%, transparent);
|
|
513
|
+
--dsh-cron-warning: var(--dsw-alias-state-warning-primary);
|
|
514
|
+
--dsh-cron-warning-bg: color-mix(in srgb, var(--dsh-cron-warning) 12%, transparent);
|
|
515
|
+
}
|
|
516
|
+
[data-pane=conversation], [class*=centerCol] {
|
|
517
|
+
position: relative !important;
|
|
518
|
+
}
|
|
519
|
+
[${VIEW_ATTR}] {
|
|
520
|
+
z-index: 60;
|
|
521
|
+
background: var(--dsw-alias-bg-base);
|
|
522
|
+
display: none;
|
|
523
|
+
position: absolute;
|
|
524
|
+
inset: 0;
|
|
525
|
+
}
|
|
526
|
+
html[${ACTIVE_ATTR}] [${VIEW_ATTR}] {
|
|
527
|
+
display: block !important;
|
|
528
|
+
}
|
|
529
|
+
html[${ACTIVE_ATTR}] [data-pane=conversation] > *:not([${VIEW_ATTR}]),
|
|
530
|
+
html[${ACTIVE_ATTR}] [class*=centerCol] > *:not([${VIEW_ATTR}]) {
|
|
531
|
+
display: none !important;
|
|
532
|
+
}
|
|
533
|
+
.dsh-cron-entry-clone { display: flex; align-items: center; gap: 8px; }
|
|
534
|
+
.dsh-cron-entry-icon { display: flex; align-items: center; justify-content: center; flex: none; width: 16px; height: 16px; }
|
|
535
|
+
.dsh-cron-entry-label { text-overflow: ellipsis; overflow: hidden; }
|
|
536
|
+
[data-dsh-frame][data-sidebar-collapsed] .dsh-cron-entry-label { display: none; }
|
|
537
|
+
.dsh-cron-overlay { position: absolute; inset: 0; display: flex; flex-direction: column; background: var(--dsw-alias-bg-base); color: var(--dsw-alias-label-primary); overflow-y: auto; padding: 24px 48px 32px; z-index: 100; box-sizing: border-box; }
|
|
538
|
+
.dsh-cron-container { max-width: 960px; width: 100%; margin: 0 auto; display: flex; flex-direction: column; gap: 20px; padding: 8px 0 32px; box-sizing: border-box; }
|
|
539
|
+
.dsh-cron-top-bar { display: flex; align-items: center; gap: 12px; }
|
|
540
|
+
.dsh-cron-back-btn { appearance: none; font: inherit; cursor: pointer; color: var(--dsw-alias-label-secondary); background: 0 0; border: 1px solid var(--dsw-alias-border-l2); border-radius: 8px; align-items: center; gap: 6px; padding: 4px 12px; font-size: 13px; display: inline-flex; flex: none; height: 32px; box-sizing: border-box; }
|
|
541
|
+
.dsh-cron-back-btn:hover { color: var(--dsw-alias-label-primary); background: var(--dsw-alias-bg-layer-4, var(--dsw-alias-interactive-bg-hover, color-mix(in srgb, var(--dsw-alias-label-primary) 6%, transparent))); }
|
|
542
|
+
.dsh-cron-header { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; padding-bottom: 16px; border-bottom: 1px solid var(--dsw-alias-border-l2); }
|
|
543
|
+
.dsh-cron-title { font-size: 22px; font-weight: 700; letter-spacing: -0.02em; margin-bottom: 6px; }
|
|
544
|
+
.dsh-cron-subtitle { font-size: 14px; color: var(--dsw-alias-label-secondary); line-height: 1.5; }
|
|
545
|
+
.dsh-cron-create-btn { appearance: none; display: inline-flex; align-items: center; gap: 6px; background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-primary); border: 1px solid var(--dsw-alias-border-l2); border-radius: 9999px; padding: 8px 16px; font-size: 14px; font-weight: 500; cursor: pointer; position: relative; }
|
|
546
|
+
.dsh-cron-create-btn:hover { background: var(--dsw-alias-bg-layer-hover, var(--dsw-alias-interactive-bg-hover)); }
|
|
547
|
+
.dsh-cron-dropdown { position: absolute; top: calc(100% + 6px); right: 0; width: 200px; background: var(--dsw-alias-bg-layer-3); border: 1px solid var(--dsw-alias-border-l2); border-radius: 12px; padding: 6px; box-shadow: 0 10px 25px color-mix(in srgb, var(--dsw-alias-bg-mask) 50%, transparent); z-index: 200; }
|
|
548
|
+
.dsh-cron-dropdown-item { display: flex; align-items: center; gap: 10px; width: 100%; padding: 10px 12px; background: transparent; border: none; border-radius: 8px; color: var(--dsw-alias-label-primary); font-size: 13.5px; text-align: left; cursor: pointer; }
|
|
549
|
+
.dsh-cron-dropdown-item:hover { background: var(--dsw-alias-interactive-bg-hover); }
|
|
550
|
+
.dsh-cron-search-bar { position: relative; margin-bottom: 0; }
|
|
551
|
+
.dsh-cron-search-input { width: 100%; height: 36px; background: var(--dsw-alias-bg-layer-2); border: 1px solid var(--dsw-alias-border-l2); border-radius: 8px; padding: 0 12px 0 38px; color: var(--dsw-alias-label-primary, inherit); font-size: 13px; outline: none; box-sizing: border-box; }
|
|
552
|
+
.dsh-cron-search-input:focus { outline: none; border-color: var(--dsh-cron-info); }
|
|
553
|
+
.dsh-cron-search-icon { position: absolute; left: 12px; top: 50%; transform: translateY(-50%); color: var(--dsw-alias-label-tertiary); }
|
|
554
|
+
.dsh-cron-filters { margin-top: 8px; }
|
|
555
|
+
.dsh-cron-filter-toggle { background: none; border: 1px solid var(--dsw-alias-border-l2); color: var(--dsw-alias-label-secondary); border-radius: 8px; padding: 4px 10px; font-size: 12px; cursor: pointer; }
|
|
556
|
+
.dsh-cron-filter-toggle:hover { color: var(--dsw-alias-label-primary); border-color: var(--dsh-cron-info); }
|
|
557
|
+
.dsh-cron-filter-panel { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 10px; margin-top: 10px; padding: 10px 12px; border: 1px solid var(--dsw-alias-border-l2); border-radius: 8px; background: var(--dsw-alias-bg-layer-3, color-mix(in srgb, var(--dsw-alias-label-primary) 2%, transparent)); }
|
|
558
|
+
.dsh-cron-filter-field { display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: var(--dsw-alias-label-tertiary); }
|
|
559
|
+
.dsh-cron-filter-field select { background: var(--dsw-alias-bg-layer-2); border: 1px solid var(--dsw-alias-border-l2); color: var(--dsw-alias-label-primary, inherit); border-radius: 6px; padding: 5px 8px; font-size: 12.5px; }
|
|
560
|
+
.dsh-cron-tabs { display: flex; flex-wrap: wrap; gap: 8px; }
|
|
561
|
+
.dsh-cron-tab { appearance: none; background: transparent; border: 1px solid var(--dsw-alias-border-l2); padding: 4px 12px; border-radius: 999px; color: var(--dsw-alias-label-secondary); font-size: 12px; font-weight: 500; cursor: pointer; }
|
|
562
|
+
.dsh-cron-tab[data-active="true"] { background: var(--dsw-alias-bg-layer-4); color: var(--dsw-alias-label-primary); border-color: var(--dsw-alias-label-dimmed, var(--dsw-alias-border-l2)); }
|
|
563
|
+
.dsh-cron-task-list { display: flex; flex-direction: column; gap: 12px; margin-bottom: 0; }
|
|
564
|
+
.dsh-cron-task-item { display: flex; align-items: center; gap: 16px; padding: 14px 18px; background: var(--dsw-alias-bg-layer-3); border: 1px solid var(--dsw-alias-border-l2); border-radius: 12px; cursor: pointer; transition: background 0.15s, border-color 0.15s; }
|
|
565
|
+
.dsh-cron-task-item:hover, .dsh-cron-task-item:focus-visible { background: var(--dsw-alias-bg-layer-4); border-color: var(--dsw-alias-label-dimmed, var(--dsw-alias-border-l1)); outline: none; }
|
|
566
|
+
.dsh-cron-task-prompt-preview { font-size: 12.5px; color: var(--dsw-alias-label-secondary); margin-top: 5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 520px; line-height: 1.3; }
|
|
567
|
+
.dsh-cron-type-tag { display: inline-flex; align-items: center; font-size: 11px; padding: 2px 8px; border-radius: 999px; border: 1px solid var(--dsw-alias-border-l2); text-transform: uppercase; font-weight: 500; margin-left: 8px; vertical-align: middle; }
|
|
568
|
+
.dsh-cron-tag-success { background: var(--dsh-cron-info-bg); color: var(--dsh-cron-info); border-color: var(--dsh-cron-info); }
|
|
569
|
+
.dsh-cron-tag-failure { background: var(--dsh-cron-danger-bg); color: var(--dsh-cron-danger); border-color: var(--dsh-cron-danger); }
|
|
570
|
+
.dsh-cron-tag-heartbeat { background: var(--dsh-cron-accent-bg); color: var(--dsh-cron-accent); border-color: var(--dsh-cron-accent); }
|
|
571
|
+
.dsh-cron-tag-session { background: var(--dsh-cron-info-bg); color: var(--dsh-cron-info); border-color: var(--dsh-cron-info); }
|
|
572
|
+
.dsh-cron-tag-preflight { background: var(--dsh-cron-warning-bg); color: var(--dsh-cron-warning); border-color: var(--dsh-cron-warning); }
|
|
573
|
+
.dsh-cron-task-model-tag { display: inline-flex; align-items: center; font-size: 11px; padding: 2px 8px; border-radius: 999px; border: 1px solid var(--dsw-alias-border-l2); background: var(--dsw-alias-bg-layer-2, color-mix(in srgb, var(--dsw-alias-label-primary) 6%, transparent)); color: var(--dsw-alias-label-secondary); margin-left: 8px; vertical-align: middle; }
|
|
574
|
+
.dsh-cron-task-actions { display: flex; gap: 8px; }
|
|
575
|
+
.dsh-cron-icon-btn { appearance: none; font: inherit; background: var(--dsw-alias-bg-layer-2, transparent); border: 1px solid var(--dsw-alias-border-l2); border-radius: 8px; padding: 6px 12px; font-size: 12.5px; font-weight: 500; color: var(--dsw-alias-label-primary); cursor: pointer; display: inline-flex; align-items: center; gap: 6px; transition: all 0.15s ease; }
|
|
576
|
+
.dsh-cron-icon-btn:hover { background: var(--dsw-alias-bg-layer-4); border-color: var(--dsw-alias-label-dimmed, var(--dsw-alias-border-l2)); }
|
|
577
|
+
.dsh-cron-icon-btn.dsh-cron-danger { color: var(--dsh-cron-danger); border-color: var(--dsh-cron-danger-bg); }
|
|
578
|
+
.dsh-cron-icon-btn.dsh-cron-danger:hover { background: var(--dsh-cron-danger-bg); border-color: var(--dsh-cron-danger); }
|
|
579
|
+
.dsh-cron-task-status-btn { background: none; border: 1px solid var(--dsw-alias-border-l2); border-radius: 50%; width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; color: var(--dsw-alias-label-tertiary); cursor: pointer; flex-shrink: 0; }
|
|
580
|
+
.dsh-cron-task-status-btn[data-active="true"] { color: var(--dsh-cron-success); border-color: var(--dsh-cron-success); }
|
|
581
|
+
.dsh-cron-recs-title { font-size: 16px; font-weight: 600; margin-bottom: 14px; }
|
|
582
|
+
.dsh-cron-recs-list { display: flex; flex-direction: column; gap: 10px; }
|
|
583
|
+
.dsh-cron-rec-card { display: flex; align-items: center; gap: 16px; padding: 14px 18px; background: var(--dsw-alias-bg-layer-3); border: 1px solid var(--dsw-alias-border-l2); border-radius: 12px; cursor: pointer; transition: background 0.15s; }
|
|
584
|
+
.dsh-cron-rec-card:hover, .dsh-cron-rec-card:focus-visible { background: var(--dsw-alias-bg-layer-4); border-color: var(--dsw-alias-label-dimmed, var(--dsw-alias-border-l2)); outline: none; }
|
|
585
|
+
.dsh-cron-rec-icon { width: 36px; height: 36px; border-radius: 10px; background: var(--dsw-alias-bg-layer-2, color-mix(in srgb, var(--dsw-alias-label-primary) 3%, transparent)); display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
|
|
586
|
+
.dsh-cron-rec-content { flex: 1; min-width: 0; }
|
|
587
|
+
.dsh-cron-rec-title { font-size: 14.5px; font-weight: 500; margin-bottom: 4px; }
|
|
588
|
+
.dsh-cron-rec-time { font-size: 13px; color: var(--dsw-alias-label-secondary); font-weight: normal; margin-left: 8px; }
|
|
589
|
+
.dsh-cron-rec-desc { font-size: 13px; color: var(--dsw-alias-label-tertiary); }
|
|
590
|
+
.dsh-cron-modal-overlay { position: fixed; inset: 0; background: var(--dsw-alias-bg-mask, color-mix(in srgb, #000 75%, transparent)); backdrop-filter: blur(4px); display: flex; align-items: center; justify-content: center; z-index: 1000; padding: 16px; box-sizing: border-box; overflow-y: auto; }
|
|
591
|
+
.dsh-cron-modal { background: var(--dsw-alias-bg-layer-2); border: 1px solid var(--dsw-alias-border-l2); border-radius: 16px; width: 100%; max-width: 540px; max-height: min(90vh, calc(100vh - 36px)); overflow-y: auto; padding: 24px; box-shadow: 0 20px 40px color-mix(in srgb, var(--dsw-alias-bg-mask) 60%, transparent); box-sizing: border-box; }
|
|
592
|
+
.dsh-cron-modal-scroll { max-width: 640px; max-height: min(90vh, calc(100vh - 36px)); overflow-y: auto; }
|
|
593
|
+
.dsh-cron-modal::-webkit-scrollbar { width: 6px; }
|
|
594
|
+
.dsh-cron-modal::-webkit-scrollbar-track { background: transparent; }
|
|
595
|
+
.dsh-cron-modal::-webkit-scrollbar-thumb { background: var(--dsw-alias-border-l2); border-radius: 3px; }
|
|
596
|
+
.dsh-cron-modal::-webkit-scrollbar-thumb:hover { background: var(--dsw-alias-label-tertiary); }
|
|
597
|
+
.dsh-cron-section-head { display: flex; align-items: center; justify-content: space-between; width: 100%; background: none; border: none; padding: 0; margin-bottom: 10px; font: inherit; font-weight: 500; font-size: 12.5px; color: var(--dsw-alias-label-secondary); cursor: pointer; }
|
|
598
|
+
.dsh-cron-section-head:hover { color: var(--dsw-alias-label-primary); }
|
|
599
|
+
.dsh-cron-section-chevron { transition: transform 0.15s ease; display: inline-block; }
|
|
600
|
+
.dsh-cron-section-chevron[data-open="false"] { transform: rotate(-90deg); }
|
|
601
|
+
.dsh-cron-section-body { padding-bottom: 4px; }
|
|
602
|
+
.dsh-cron-modal-title { font-size: 18px; font-weight: 600; margin-bottom: 18px; }
|
|
603
|
+
.dsh-cron-form-group { margin-bottom: 16px; }
|
|
604
|
+
.dsh-cron-form-group label { display: block; font-size: 13px; color: var(--dsw-alias-label-secondary); margin-bottom: 6px; }
|
|
605
|
+
.dsh-cron-form-group input, .dsh-cron-form-group textarea, .dsh-cron-form-group select { width: 100%; background: var(--dsw-alias-bg-layer-2); border: 1px solid var(--dsw-alias-border-l2); border-radius: 8px; padding: 8px 12px; color: var(--dsw-alias-label-primary); font-size: 13px; outline: none; box-sizing: border-box; }
|
|
606
|
+
.dsh-cron-form-group input:focus, .dsh-cron-form-group textarea:focus, .dsh-cron-form-group select:focus { outline: none; border-color: var(--dsh-cron-info); }
|
|
607
|
+
.dsh-cron-form-group select { cursor: pointer; }
|
|
608
|
+
.dsh-cron-form-group textarea { min-height: 80px; resize: vertical; }
|
|
609
|
+
.dsh-cron-form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
|
|
610
|
+
.dsh-cron-modal-foot { display: flex; justify-content: flex-end; align-items: center; gap: 10px; margin-top: 20px; position: sticky; bottom: -24px; background: var(--dsw-alias-bg-layer-2); border-top: 1px solid var(--dsw-alias-border-l2); padding-top: 14px; padding-bottom: 4px; margin-left: -24px; margin-right: -24px; padding-left: 24px; padding-right: 24px; z-index: 10; }
|
|
611
|
+
.dsh-cron-btn-primary { background: var(--dsw-alias-label-primary); color: var(--dsw-alias-bg-layer-3); border: 1px solid transparent; border-radius: 8px; padding: 7px 14px; font-size: 13px; font-weight: 500; cursor: pointer; display: inline-flex; align-items: center; gap: 6px; }
|
|
612
|
+
.dsh-cron-btn-primary:hover:not(:disabled) { opacity: 0.88; }
|
|
613
|
+
.dsh-cron-btn-primary:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
614
|
+
.dsh-cron-btn-secondary { appearance: none; font: inherit; background: var(--dsw-alias-bg-layer-2, transparent); color: var(--dsw-alias-label-primary); border: 1px solid var(--dsw-alias-border-l2); border-radius: 8px; padding: 7px 14px; font-size: 13px; font-weight: 500; cursor: pointer; display: inline-flex; align-items: center; gap: 6px; transition: all 0.15s ease; }
|
|
615
|
+
.dsh-cron-btn-secondary:hover:not(:disabled) { background: var(--dsw-alias-bg-layer-4); border-color: var(--dsw-alias-label-dimmed, var(--dsw-alias-border-l2)); }
|
|
616
|
+
.dsh-cron-chat-box { position: relative; background: var(--dsw-alias-bg-base); border: 1px solid var(--dsw-alias-border-l2); border-radius: 12px; padding: 12px; }
|
|
617
|
+
.dsh-cron-chat-input { width: 100%; background: transparent; border: none; color: var(--dsw-alias-label-primary); font-size: 14px; min-height: 100px; resize: vertical; outline: none; box-sizing: border-box; line-height: 1.5; }
|
|
618
|
+
.dsh-cron-chat-foot { display: flex; justify-content: space-between; align-items: center; margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--dsw-alias-border-l1); }
|
|
619
|
+
.dsh-cron-chat-hint { font-size: 12px; color: var(--dsw-alias-label-tertiary); }
|
|
620
|
+
.dsh-cron-send-btn { width: 34px; height: 34px; border-radius: 8px; background: var(--dsw-alias-label-primary); color: var(--dsw-alias-bg-base); border: none; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: transform 0.1s, background 0.15s; }
|
|
621
|
+
.dsh-cron-send-btn:hover:not(:disabled) { transform: translateY(-1px); }
|
|
622
|
+
.dsh-cron-send-btn:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
623
|
+
.dsh-cron-card { background: var(--dsw-alias-bg-layer-3); border: 1px solid var(--dsw-alias-border-l2); border-radius: 12px; padding: 18px 20px; margin-bottom: 0; display: flex; flex-direction: column; gap: 14px; }
|
|
624
|
+
.dsh-cron-card-head-btn { appearance: none; width: 100%; font: inherit; color: inherit; text-align: left; cursor: pointer; background: 0 0; border: 0; border-radius: 12px; display: flex; align-items: center; gap: 12px; padding: 0; }
|
|
625
|
+
.dsh-cron-card-head-btn .dsh-cron-card-title { flex: 1; }
|
|
626
|
+
.dsh-cron-card-title { font-size: 16px; font-weight: 600; color: var(--dsw-alias-label-primary); }
|
|
627
|
+
.dsh-cron-chev { margin-left: auto; flex: none; color: var(--dsw-alias-label-tertiary); transition: transform 0.16s; display: flex; align-items: center; }
|
|
628
|
+
.dsh-cron-chev-open { transform: rotate(180deg); }
|
|
629
|
+
.dsh-cron-card-body { border-top: 1px solid var(--dsw-alias-border-l2); margin-top: 2px; padding-top: 14px; }
|
|
630
|
+
.dsh-cron-card-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; }
|
|
631
|
+
.dsh-cron-card-desc { font-size: 13px; color: var(--dsw-alias-label-secondary); line-height: 1.4; }
|
|
632
|
+
.dsh-cron-modal-nav { display: flex; gap: 4px; border-bottom: 1px solid var(--dsw-alias-border-l2); margin-bottom: 18px; padding-bottom: 2px; }
|
|
633
|
+
.dsh-cron-modal-tab { appearance: none; background: transparent; border: none; border-bottom: 2px solid transparent; padding: 8px 14px; font-size: 13.5px; color: var(--dsw-alias-label-tertiary); cursor: pointer; font-weight: 500; transition: color 0.15s, border-color 0.15s; }
|
|
634
|
+
.dsh-cron-modal-tab:hover { color: var(--dsw-alias-label-secondary); }
|
|
635
|
+
.dsh-cron-modal-tab[data-active="true"] { color: var(--dsw-alias-label-primary); border-bottom-color: var(--dsw-alias-label-primary); }
|
|
636
|
+
.dsh-cron-history-list { display: flex; flex-direction: column; gap: 10px; max-height: 420px; overflow-y: auto; padding-right: 4px; }
|
|
637
|
+
.dsh-cron-history-item { background: var(--dsw-alias-bg-base); border: 1px solid var(--dsw-alias-border-l1); border-radius: 8px; padding: 12px 14px; font-size: 12.5px; }
|
|
638
|
+
.dsh-cron-history-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px; }
|
|
639
|
+
.dsh-cron-history-status { font-weight: 500; font-size: 11px; text-transform: uppercase; padding: 2px 8px; border-radius: 999px; border: 1px solid var(--dsw-alias-border-l2); }
|
|
640
|
+
.dsh-cron-history-status[data-status="success"] { background: var(--dsh-cron-success-bg); color: var(--dsh-cron-success); border-color: var(--dsh-cron-success); }
|
|
641
|
+
.dsh-cron-history-status[data-status="validation_failed"] { background: var(--dsh-cron-danger-bg); color: var(--dsh-cron-danger); border-color: var(--dsh-cron-danger); }
|
|
642
|
+
.dsh-cron-stage-pill { font-size: 10.5px; padding: 1px 7px; border-radius: 4px; border: 1px solid var(--dsw-alias-border-l2); color: var(--dsw-alias-label-secondary); }
|
|
643
|
+
.dsh-cron-stage-pill[data-status="success"], .dsh-cron-stage-pill[data-status="delivered"] { background: var(--dsh-cron-success-bg); color: var(--dsh-cron-success); border-color: var(--dsh-cron-success); }
|
|
644
|
+
.dsh-cron-stage-pill[data-status="failed"], .dsh-cron-stage-pill[data-status="error"] { background: var(--dsh-cron-danger-bg); color: var(--dsh-cron-danger); border-color: var(--dsh-cron-danger); }
|
|
645
|
+
.dsh-cron-stage-pill[data-status="partial"] { background: var(--dsh-cron-warning-bg); color: var(--dsh-cron-warning); border-color: var(--dsh-cron-warning); }
|
|
646
|
+
.dsh-cron-stage-pill[data-status="skipped"] { opacity: 0.6; }
|
|
647
|
+
.dsh-cron-history-status[data-status="error"], .dsh-cron-history-status[data-status="timeout"] { background: var(--dsh-cron-danger-bg); color: var(--dsh-cron-danger); border-color: var(--dsh-cron-danger); }
|
|
648
|
+
.dsh-cron-history-status[data-status="skipped"], .dsh-cron-history-status[data-status="missed"] { background: var(--dsh-cron-warning-bg); color: var(--dsh-cron-warning); border-color: var(--dsh-cron-warning); }
|
|
649
|
+
.dsh-cron-history-time { color: var(--dsw-alias-label-tertiary); font-size: 12px; }
|
|
650
|
+
.dsh-cron-history-output { background: var(--dsw-alias-bg-base); border: 1px solid var(--dsw-alias-border-l1); border-radius: 6px; padding: 8px 10px; font-family: monospace; font-size: 12px; color: var(--dsw-alias-label-secondary); max-height: 140px; overflow-y: auto; white-space: pre-wrap; word-break: break-all; margin-top: 6px; }
|
|
651
|
+
.dsh-cron-history-empty { text-align: center; padding: 36px 0; color: var(--dsw-alias-label-tertiary); font-size: 13.5px; }
|
|
652
|
+
.dsh-cron-stats-bar { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; margin-bottom: 0; }
|
|
653
|
+
.dsh-cron-stat-card { background: var(--dsw-alias-bg-layer-2, color-mix(in srgb, var(--dsw-alias-label-primary) 2%, transparent)); border: 1px solid var(--dsw-alias-border-l2); border-radius: 8px; padding: 12px 14px; display: flex; flex-direction: column; gap: 4px; }
|
|
654
|
+
.dsh-cron-stat-val { font-size: 18px; font-weight: 700; color: var(--dsw-alias-label-primary); }
|
|
655
|
+
.dsh-cron-stat-lbl { font-size: 12px; color: var(--dsw-alias-label-secondary); margin-top: 0; }
|
|
656
|
+
.dsh-cron-oneshot-tag { display: inline-flex; align-items: center; font-size: 11px; padding: 2px 8px; border-radius: 999px; border: 1px solid var(--dsh-cron-accent); background: var(--dsh-cron-accent-bg); color: var(--dsh-cron-accent); font-weight: 500; }
|
|
657
|
+
.dsh-cron-config-tag { display: inline-flex; align-items: center; font-size: 11px; padding: 2px 8px; border-radius: 999px; border: 1px solid var(--dsw-alias-border-l2); color: var(--dsw-alias-label-secondary); font-weight: 500; }
|
|
658
|
+
.dsh-cron-cost-tag { display: inline-flex; align-items: center; font-size: 11px; padding: 2px 8px; border-radius: 999px; border: 1px solid var(--dsh-cron-success); background: var(--dsh-cron-success-bg); color: var(--dsh-cron-success); font-weight: 500; }
|
|
659
|
+
.dsh-cron-tokens-tag { display: inline-flex; align-items: center; font-size: 11px; padding: 2px 8px; border-radius: 999px; border: 1px solid var(--dsh-cron-info); background: var(--dsh-cron-info-bg); color: var(--dsh-cron-info); }
|
|
660
|
+
.dsh-cron-checkbox-row { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--dsw-alias-label-secondary); cursor: pointer; user-select: none; }
|
|
661
|
+
.dsh-cron-checkbox-row input { width: 15px; height: 15px; margin: 0; cursor: pointer; accent-color: var(--dsw-alias-label-primary); }
|
|
662
|
+
.dsh-cron-hint { font-size: 12px; color: var(--dsw-alias-label-tertiary); margin-top: 4px; }
|
|
663
|
+
.dsh-cron-channel-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 6px 12px; }
|
|
664
|
+
.dsh-cron-section { border-top: 1px solid var(--dsw-alias-border-l2); margin-top: 16px; padding-top: 12px; }
|
|
665
|
+
.dsh-cron-section-title { font-weight: 500; font-size: 12.5px; color: var(--dsw-alias-label-secondary); margin-bottom: 10px; }
|
|
666
|
+
.dsh-cron-secret-row { display: flex; align-items: center; gap: 8px; }
|
|
667
|
+
.dsh-cron-secret-row input { flex: 1; }
|
|
668
|
+
.dsh-cron-badge-ref { font-size: 11px; padding: 2px 6px; border-radius: 4px; background: var(--dsw-alias-bg-layer-3, color-mix(in srgb, var(--dsw-alias-label-primary) 6%, transparent)); color: var(--dsw-alias-label-tertiary); white-space: nowrap; }
|
|
669
|
+
.dsh-cron-status-banner { padding: 10px 14px; border-radius: 8px; font-size: 13px; margin-top: 12px; }
|
|
670
|
+
.dsh-cron-status-banner[data-type="success"] { background: var(--dsh-cron-success-bg); border: 1px solid var(--dsh-cron-success); color: var(--dsh-cron-success); }
|
|
671
|
+
.dsh-cron-status-banner[data-type="error"] { background: var(--dsh-cron-danger-bg); border: 1px solid var(--dsh-cron-danger); color: var(--dsh-cron-danger); }
|
|
672
|
+
.dsh-cron-status-banner[data-type="info"] { background: var(--dsh-cron-info-bg); border: 1px solid var(--dsh-cron-info); color: var(--dsh-cron-info); }
|
|
673
|
+
@keyframes dsh-cron-pulse-ring { 0% { box-shadow: 0 0 0 0 var(--dsh-cron-success-bg); } 70% { box-shadow: 0 0 0 9px transparent; } 100% { box-shadow: 0 0 0 0 transparent; } }
|
|
674
|
+
.dsh-cron-pulse { animation: dsh-cron-pulse-ring 1.6s infinite; border-color: var(--dsh-cron-success); color: var(--dsh-cron-success); }
|
|
675
|
+
/* #34: sidebar job list */
|
|
676
|
+
.dsh-cron-sidebar-jobs { display: flex; flex-direction: column; gap: 2px; margin: 2px 0 6px 0; }
|
|
677
|
+
.dsh-cron-sidebar-jobs-head { display: flex; align-items: center; justify-content: space-between; width: 100%; background: none; border: none; padding: 4px 8px; font: inherit; font-size: 12px; color: var(--dsw-alias-label-tertiary); cursor: pointer; border-radius: 6px; text-align: left; }
|
|
678
|
+
.dsh-cron-sidebar-jobs-head:hover { color: var(--dsw-alias-label-primary); background: var(--dsw-alias-interactive-bg-hover, color-mix(in srgb, var(--dsw-alias-label-primary) 4%, transparent)); }
|
|
679
|
+
.dsh-cron-sidebar-jobs-head::after { content: '▾'; font-size: 10px; transition: transform 0.15s ease; }
|
|
680
|
+
.dsh-cron-sidebar-jobs[data-open="false"] .dsh-cron-sidebar-jobs-head::after { transform: rotate(-90deg); }
|
|
681
|
+
.dsh-cron-sidebar-jobs[data-open="false"] .dsh-cron-sidebar-jobs-list { display: none; }
|
|
682
|
+
.dsh-cron-sidebar-jobs-list { display: flex; flex-direction: column; gap: 2px; padding-left: 10px; }
|
|
683
|
+
.dsh-cron-sidebar-job { display: flex; flex-direction: column; align-items: flex-start; gap: 1px; width: 100%; background: none; border: none; border-left: 2px solid transparent; padding: 3px 8px; font: inherit; cursor: pointer; border-radius: 0 6px 6px 0; text-align: left; }
|
|
684
|
+
.dsh-cron-sidebar-job:hover { background: var(--dsw-alias-interactive-bg-hover, color-mix(in srgb, var(--dsw-alias-label-primary) 4%, transparent)); border-left-color: var(--dsh-cron-info); }
|
|
685
|
+
.dsh-cron-sidebar-job-title { font-size: 12px; color: var(--dsw-alias-label-secondary); max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
686
|
+
.dsh-cron-sidebar-job-meta { font-size: 11px; color: var(--dsw-alias-label-tertiary); }
|
|
687
|
+
.dsh-cron-sidebar-empty, .dsh-cron-sidebar-more { font-size: 11px; color: var(--dsw-alias-label-tertiary); padding: 2px 8px; }
|
|
688
|
+
.dsh-cron-task-highlight { border-color: var(--dsh-cron-info) !important; box-shadow: 0 0 0 2px var(--dsh-cron-info-bg); }
|
|
689
|
+
/* #39: narrow screens. Only layout changes — no information is hidden. */
|
|
690
|
+
@media (max-width: 900px) {
|
|
691
|
+
.dsh-cron-container { padding: 8px 12px 24px; gap: 14px; }
|
|
692
|
+
.dsh-cron-form-row { grid-template-columns: 1fr; }
|
|
693
|
+
.dsh-cron-channel-grid { grid-template-columns: 1fr; }
|
|
694
|
+
.dsh-cron-filter-panel { grid-template-columns: 1fr; }
|
|
695
|
+
.dsh-cron-modal { padding: 16px; border-radius: 12px; }
|
|
696
|
+
.dsh-cron-modal-foot { bottom: -16px; margin-left: -16px; margin-right: -16px; padding-left: 16px; padding-right: 16px; }
|
|
697
|
+
.dsh-cron-modal-scroll { max-height: 92vh; }
|
|
698
|
+
.dsh-cron-task-item { flex-wrap: wrap; gap: 10px; }
|
|
699
|
+
.dsh-cron-task-actions { margin-left: auto; }
|
|
700
|
+
.dsh-cron-history-list { max-height: 320px; }
|
|
701
|
+
}
|
|
702
|
+
@media (max-width: 640px) {
|
|
703
|
+
.dsh-cron-container { padding: 6px 8px 20px; }
|
|
704
|
+
.dsh-cron-top-bar { flex-wrap: wrap; gap: 8px; }
|
|
705
|
+
.dsh-cron-tabs { flex-wrap: nowrap; overflow-x: auto; padding-bottom: 4px; }
|
|
706
|
+
.dsh-cron-tab { white-space: nowrap; }
|
|
707
|
+
.dsh-cron-stats-bar { grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); }
|
|
708
|
+
.dsh-cron-task-item { align-items: flex-start; padding: 12px; }
|
|
709
|
+
.dsh-cron-task-title { flex-wrap: wrap; row-gap: 4px; }
|
|
710
|
+
.dsh-cron-modal { padding: 14px; }
|
|
711
|
+
.dsh-cron-modal-foot { flex-direction: column-reverse; }
|
|
712
|
+
.dsh-cron-modal-foot > button { width: 100%; }
|
|
713
|
+
.dsh-cron-filter-panel { padding: 8px 10px; }
|
|
714
|
+
.dsh-cron-card { padding: 14px; }
|
|
715
|
+
/* 16px keeps iOS Safari from zooming the page when a field is focused. */
|
|
716
|
+
.dsh-cron-modal input, .dsh-cron-modal select, .dsh-cron-modal textarea,
|
|
717
|
+
.dsh-cron-card input, .dsh-cron-search-input, .dsh-cron-filter-field select { font-size: 16px; }
|
|
718
|
+
}
|
|
719
|
+
`;
|
|
719
720
|
const STYLE_ID = 'dsh-cron-styles';
|
|
720
721
|
function ensureStyles() {
|
|
721
722
|
if (typeof document === 'undefined') return null;
|
|
@@ -1050,6 +1051,41 @@ onChange: (e) => onChannelTemplate(id, e.target.value)
|
|
|
1050
1051
|
]),
|
|
1051
1052
|
];
|
|
1052
1053
|
}
|
|
1054
|
+
function parseChainTargets(raw) {
|
|
1055
|
+
if (!raw) return [];
|
|
1056
|
+
if (Array.isArray(raw)) return raw.filter((x) => typeof x === 'string' && x.trim().length > 0);
|
|
1057
|
+
if (typeof raw === 'string') return raw.split(',').map((s) => s.trim()).filter((s) => s.length > 0);
|
|
1058
|
+
return [];
|
|
1059
|
+
}
|
|
1060
|
+
const sanitizeDependsOn = parseChainTargets;
|
|
1061
|
+
const sanitizeJoinPolicy = (raw) => (raw === 'any_success' ? 'any_success' : 'all_success');
|
|
1062
|
+
function buildTaskGraphModel(tasks = [], snapshots = [], focusTaskId = null) {
|
|
1063
|
+
const taskMap = new Map();
|
|
1064
|
+
for (const t of tasks) if (t?.id) taskMap.set(t.id, t);
|
|
1065
|
+
const snapMap = new Map();
|
|
1066
|
+
for (const s of snapshots) if (s?.stepTaskId) snapMap.set(s.stepTaskId, s);
|
|
1067
|
+
const nodes = [], edges = [];
|
|
1068
|
+
let hasFailure = false;
|
|
1069
|
+
for (const [id, task] of taskMap.entries()) {
|
|
1070
|
+
const snap = snapMap.get(id);
|
|
1071
|
+
let nodeStatus = 'pending', durationMs = 0, outputSnippet = '', retryCount = 0;
|
|
1072
|
+
if (snap) {
|
|
1073
|
+
nodeStatus = snap.status === 'completed' || snap.status === 'success' ? 'success' : 'failed';
|
|
1074
|
+
durationMs = snap.durationMs || 0;
|
|
1075
|
+
retryCount = snap.retryCount || 0;
|
|
1076
|
+
if (snap.output) outputSnippet = String(snap.output).slice(0, 200);
|
|
1077
|
+
if (nodeStatus === 'failed') hasFailure = true;
|
|
1078
|
+
} else if (task.status === 'running') {
|
|
1079
|
+
nodeStatus = 'running';
|
|
1080
|
+
}
|
|
1081
|
+
const deps = sanitizeDependsOn(task.dependsOn);
|
|
1082
|
+
nodes.push({ id, title: task.title || id, type: task.type || 'prompt', status: nodeStatus, durationMs, retryCount, outputSnippet, isFocus: Boolean(focusTaskId && id === focusTaskId), isRetryable: nodeStatus === 'failed', dependsOn: deps, joinPolicy: sanitizeJoinPolicy(task.joinPolicy) });
|
|
1083
|
+
for (const d of deps) edges.push({ from: d, to: id, type: 'dependsOn', label: task.joinPolicy === 'any_success' ? 'join:any' : 'join:all' });
|
|
1084
|
+
for (const t of parseChainTargets(task.onSuccess)) edges.push({ from: id, to: t, type: 'onSuccess', label: 'onSuccess' });
|
|
1085
|
+
for (const f of parseChainTargets(task.onFailure)) edges.push({ from: id, to: f, type: 'onFailure', label: 'onFailure' });
|
|
1086
|
+
}
|
|
1087
|
+
return { nodes, edges, hasFailure };
|
|
1088
|
+
}
|
|
1053
1089
|
function renderChainGraphModal({ isOpen, onClose, data, loading, onRetry, t }) {
|
|
1054
1090
|
if (!isOpen) return null;
|
|
1055
1091
|
const graph = data?.graph || { nodes: [], edges: [], hasFailure: false };
|
|
@@ -1341,7 +1377,7 @@ topExpensiveTasks: []
|
|
|
1341
1377
|
const copyMarkdown = () => {
|
|
1342
1378
|
if (!digestData) return;
|
|
1343
1379
|
const text = [
|
|
1344
|
-
'# ' + (T_KEY(t, 'digest.modalTitle')
|
|
1380
|
+
'# ' + (T_KEY(t, 'digest.modalTitle')),
|
|
1345
1381
|
'Period: Last ' + (periodDays || 7) + ' days',
|
|
1346
1382
|
'Total Runs: ' + digest.totalRuns + ' (' + digest.successRate + '% SR)',
|
|
1347
1383
|
'Tokens: ' + digest.totalTokens + ' (~$' + digest.totalCostUsd + ' USD)',
|
|
@@ -1367,10 +1403,10 @@ React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap
|
|
|
1367
1403
|
React.createElement('span', { style: { fontSize: '20px' } }, '📊'),
|
|
1368
1404
|
React.createElement('div', null,
|
|
1369
1405
|
React.createElement('div', { style: { fontWeight: '600', fontSize: '15px', color: 'var(--dsw-alias-label-primary, #fff)' } },
|
|
1370
|
-
T_KEY(t, 'digest.modalTitle')
|
|
1406
|
+
T_KEY(t, 'digest.modalTitle')
|
|
1371
1407
|
),
|
|
1372
1408
|
React.createElement('div', { style: { fontSize: '11.5px', color: 'var(--dsw-alias-label-secondary, #888)', marginTop: '2px' } },
|
|
1373
|
-
(T_KEY(t, 'digest.buttonTitle')
|
|
1409
|
+
(T_KEY(t, 'digest.buttonTitle')) + ' · ' + (periodDays || 7) + 'd'
|
|
1374
1410
|
)
|
|
1375
1411
|
)
|
|
1376
1412
|
),
|
|
@@ -1380,13 +1416,13 @@ type: 'button',
|
|
|
1380
1416
|
className: 'dsh-cron-btn-secondary',
|
|
1381
1417
|
style: { padding: '4px 10px', fontSize: '12px', fontWeight: periodDays === 7 ? '600' : '400', background: periodDays === 7 ? 'var(--dsw-alias-bg-layer-3, #222)' : 'transparent' },
|
|
1382
1418
|
onClick: () => onSelectPeriod && onSelectPeriod(7)
|
|
1383
|
-
}, T_KEY(t, 'digest.period7')
|
|
1419
|
+
}, T_KEY(t, 'digest.period7')),
|
|
1384
1420
|
React.createElement('button', {
|
|
1385
1421
|
type: 'button',
|
|
1386
1422
|
className: 'dsh-cron-btn-secondary',
|
|
1387
1423
|
style: { padding: '4px 10px', fontSize: '12px', fontWeight: periodDays === 30 ? '600' : '400', background: periodDays === 30 ? 'var(--dsw-alias-bg-layer-3, #222)' : 'transparent' },
|
|
1388
1424
|
onClick: () => onSelectPeriod && onSelectPeriod(30)
|
|
1389
|
-
}, T_KEY(t, 'digest.period30')
|
|
1425
|
+
}, T_KEY(t, 'digest.period30')),
|
|
1390
1426
|
React.createElement('button', {
|
|
1391
1427
|
type: 'button',
|
|
1392
1428
|
className: 'dsh-cron-icon-btn',
|
|
@@ -1399,33 +1435,22 @@ React.createElement('div', {
|
|
|
1399
1435
|
style: { padding: '16px 18px', overflowY: 'auto', flex: 1, display: 'flex', flexDirection: 'column', gap: '16px' }
|
|
1400
1436
|
},
|
|
1401
1437
|
isLoading ? React.createElement('div', { style: { padding: '30px', textAlign: 'center', color: 'var(--dsw-alias-label-secondary, #888)' } }, 'Loading digest metrics...') : React.createElement(React.Fragment, null,
|
|
1402
|
-
|
|
1403
|
-
style: {
|
|
1404
|
-
},
|
|
1405
|
-
React.createElement('div', { style: {
|
|
1406
|
-
React.createElement('div', { style: { fontSize: '11px', color: 'var(--dsw-alias-label-secondary, #888)'
|
|
1407
|
-
|
|
1408
|
-
React.createElement('div', { style: {
|
|
1409
|
-
),
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
),
|
|
1415
|
-
React.createElement('div', { style: { background: 'var(--dsw-alias-bg-layer-2, #111)', border: '1px solid var(--dsw-alias-border-l2, #333)', borderRadius: '6px', padding: '10px 12px' } },
|
|
1416
|
-
React.createElement('div', { style: { fontSize: '11px', color: 'var(--dsw-alias-label-secondary, #888)' } }, T_KEY(t, 'digest.cost') || 'Cost'),
|
|
1417
|
-
React.createElement('div', { style: { fontSize: '18px', fontWeight: '700', color: 'var(--dsw-alias-label-primary, #fff)', marginTop: '4px' } }, '$' + Number(digest.totalCostUsd || 0).toFixed(4)),
|
|
1418
|
-
React.createElement('div', { style: { fontSize: '11px', color: 'var(--dsw-alias-label-secondary, #888)', marginTop: '2px' } }, 'USD estimated')
|
|
1419
|
-
),
|
|
1420
|
-
React.createElement('div', { style: { background: 'var(--dsw-alias-bg-layer-2, #111)', border: '1px solid var(--dsw-alias-border-l2, #333)', borderRadius: '6px', padding: '10px 12px' } },
|
|
1421
|
-
React.createElement('div', { style: { fontSize: '11px', color: 'var(--dsw-alias-label-secondary, #888)' } }, T_KEY(t, 'digest.fleet') || 'Fleet'),
|
|
1422
|
-
React.createElement('div', { style: { fontSize: '18px', fontWeight: '700', color: 'var(--dsw-alias-label-primary, #fff)', marginTop: '4px' } }, digest.activeTasksCount),
|
|
1423
|
-
React.createElement('div', { style: { fontSize: '11px', color: 'var(--dsw-alias-label-secondary, #888)', marginTop: '2px' } }, digest.pausedTasksCount + ' paused')
|
|
1424
|
-
)
|
|
1425
|
-
),
|
|
1438
|
+
(() => {
|
|
1439
|
+
const renderKpi = (label, val, sub, subCol) => React.createElement('div', { style: { background: 'var(--dsw-alias-bg-layer-2, #111)', border: '1px solid var(--dsw-alias-border-l2, #333)', borderRadius: '6px', padding: '10px 12px' } },
|
|
1440
|
+
React.createElement('div', { style: { fontSize: '11px', color: 'var(--dsw-alias-label-secondary, #888)' } }, label),
|
|
1441
|
+
React.createElement('div', { style: { fontSize: '18px', fontWeight: '700', color: 'var(--dsw-alias-label-primary, #fff)', marginTop: '4px' } }, val),
|
|
1442
|
+
React.createElement('div', { style: { fontSize: '11px', color: subCol || 'var(--dsw-alias-label-secondary, #888)', marginTop: '2px' } }, sub)
|
|
1443
|
+
);
|
|
1444
|
+
return React.createElement('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '10px' } },
|
|
1445
|
+
renderKpi(T_KEY(t, 'digest.executions'), digest.totalRuns, digest.successRate + '% SR', digest.successRate >= 90 ? 'var(--dsh-cron-success, #2a2)' : 'var(--dsh-cron-warning, #fa0)'),
|
|
1446
|
+
renderKpi(T_KEY(t, 'digest.tokens'), Number(digest.totalTokens || 0).toLocaleString(), 'LLM prompt+out'),
|
|
1447
|
+
renderKpi(T_KEY(t, 'digest.cost'), '$' + Number(digest.totalCostUsd || 0).toFixed(4), 'USD estimated'),
|
|
1448
|
+
renderKpi(T_KEY(t, 'digest.fleet'), digest.activeTasksCount, digest.pausedTasksCount + ' paused')
|
|
1449
|
+
);
|
|
1450
|
+
})(),
|
|
1426
1451
|
React.createElement('div', { style: { background: 'var(--dsw-alias-bg-layer-2, #111)', border: '1px solid var(--dsw-alias-border-l2, #333)', borderRadius: '6px', padding: '12px' } },
|
|
1427
1452
|
React.createElement('div', { style: { fontWeight: '600', fontSize: '13px', color: 'var(--dsh-cron-warning, #fa0)', marginBottom: '8px' } },
|
|
1428
|
-
'⚠️ ' + (T_KEY(t, 'digest.unstableTitle')
|
|
1453
|
+
'⚠️ ' + (T_KEY(t, 'digest.unstableTitle'))
|
|
1429
1454
|
),
|
|
1430
1455
|
(digest.topUnstableTasks && digest.topUnstableTasks.length > 0) ? React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: '6px' } },
|
|
1431
1456
|
digest.topUnstableTasks.map((tItem, idx) => React.createElement('div', {
|
|
@@ -1441,7 +1466,7 @@ tItem.failures + ' fail (' + tItem.successRate + '% SR)'
|
|
|
1441
1466
|
),
|
|
1442
1467
|
React.createElement('div', { style: { background: 'var(--dsw-alias-bg-layer-2, #111)', border: '1px solid var(--dsw-alias-border-l2, #333)', borderRadius: '6px', padding: '12px' } },
|
|
1443
1468
|
React.createElement('div', { style: { fontWeight: '600', fontSize: '13px', color: 'var(--dsh-cron-info, #4af)', marginBottom: '8px' } },
|
|
1444
|
-
'💰 ' + (T_KEY(t, 'digest.expensiveTitle')
|
|
1469
|
+
'💰 ' + (T_KEY(t, 'digest.expensiveTitle'))
|
|
1445
1470
|
),
|
|
1446
1471
|
(digest.topExpensiveTasks && digest.topExpensiveTasks.length > 0) ? React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: '6px' } },
|
|
1447
1472
|
digest.topExpensiveTasks.map((tItem, idx) => React.createElement('div', {
|
|
@@ -1469,18 +1494,180 @@ type: 'button',
|
|
|
1469
1494
|
className: 'dsh-cron-btn-secondary',
|
|
1470
1495
|
onClick: copyMarkdown,
|
|
1471
1496
|
style: { padding: '6px 14px', fontSize: '12px' }
|
|
1472
|
-
}, T_KEY(t, 'digest.copyMarkdown')
|
|
1497
|
+
}, T_KEY(t, 'digest.copyMarkdown')),
|
|
1473
1498
|
React.createElement('button', {
|
|
1474
1499
|
type: 'button',
|
|
1475
1500
|
className: 'dsh-cron-btn-primary',
|
|
1476
1501
|
onClick: onSendTelegram,
|
|
1477
1502
|
style: { padding: '6px 14px', fontSize: '12px' }
|
|
1478
|
-
}, T_KEY(t, 'digest.sendTelegram')
|
|
1503
|
+
}, T_KEY(t, 'digest.sendTelegram'))
|
|
1479
1504
|
)
|
|
1480
1505
|
)
|
|
1481
1506
|
)
|
|
1482
1507
|
);
|
|
1483
1508
|
}
|
|
1509
|
+
function renderTimelineHeatmapModal({
|
|
1510
|
+
isOpen,
|
|
1511
|
+
onClose,
|
|
1512
|
+
activeTab,
|
|
1513
|
+
onSelectTab,
|
|
1514
|
+
timelineData,
|
|
1515
|
+
heatmapData,
|
|
1516
|
+
isLoading,
|
|
1517
|
+
t
|
|
1518
|
+
}) {
|
|
1519
|
+
if (!isOpen) return null;
|
|
1520
|
+
const tab = activeTab || 'timeline';
|
|
1521
|
+
const timeline = timelineData || { buckets: [], totalRuns: 0, peakHoursCount: 0, maxConcurrent: 3 };
|
|
1522
|
+
const heatmap = heatmapData || { matrix: [], dayLabels: [], hourLabels: [], totalCostUsd: 0, totalTokens: 0, maxCostHour: 0 };
|
|
1523
|
+
return React.createElement('div', {
|
|
1524
|
+
className: 'dsh-cron-modal-overlay',
|
|
1525
|
+
onClick: onClose
|
|
1526
|
+
},
|
|
1527
|
+
React.createElement('div', {
|
|
1528
|
+
className: 'dsh-cron-modal',
|
|
1529
|
+
style: { maxWidth: '960px', width: '94vw', maxHeight: '88vh', display: 'flex', flexDirection: 'column', background: 'var(--dsw-alias-bg-base, #111)', color: 'var(--dsw-alias-label-primary, #ddd)', border: '1px solid var(--dsw-alias-border-l2, #333)', borderRadius: '8px' },
|
|
1530
|
+
role: 'dialog',
|
|
1531
|
+
'aria-modal': 'true',
|
|
1532
|
+
onClick: (e) => e.stopPropagation()
|
|
1533
|
+
},
|
|
1534
|
+
React.createElement('div', {
|
|
1535
|
+
style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '14px 18px', borderBottom: '1px solid var(--dsw-alias-border-l2, #333)', background: 'var(--dsw-alias-bg-layer-2, #111)' }
|
|
1536
|
+
},
|
|
1537
|
+
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: '10px' } },
|
|
1538
|
+
React.createElement('span', { style: { fontSize: '20px' } }, tab === 'timeline' ? '⏱' : '🔥'),
|
|
1539
|
+
React.createElement('div', null,
|
|
1540
|
+
React.createElement('div', { style: { fontWeight: '600', fontSize: '15px', color: 'var(--dsw-alias-label-primary, #fff)' } },
|
|
1541
|
+
tab === 'timeline' ? (T_KEY(t, 'timeline.modalTitle')) : (T_KEY(t, 'heatmap.modalTitle'))
|
|
1542
|
+
),
|
|
1543
|
+
React.createElement('div', { style: { fontSize: '11.5px', color: 'var(--dsw-alias-label-secondary, #888)', marginTop: '2px' } },
|
|
1544
|
+
tab === 'timeline'
|
|
1545
|
+
? (timeline.totalRuns + ' runs projected · ' + (timeline.peakHoursCount ? '⚠️ ' + timeline.peakHoursCount + ' peak hours' : '✓ balanced concurrency'))
|
|
1546
|
+
: ('$' + Number(heatmap.totalCostUsd || 0).toFixed(4) + ' USD · ' + Number(heatmap.totalTokens || 0).toLocaleString() + ' tokens')
|
|
1547
|
+
)
|
|
1548
|
+
)
|
|
1549
|
+
),
|
|
1550
|
+
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: '8px' } },
|
|
1551
|
+
React.createElement('button', {
|
|
1552
|
+
type: 'button',
|
|
1553
|
+
className: 'dsh-cron-btn-secondary',
|
|
1554
|
+
style: { padding: '4px 12px', fontSize: '12px', fontWeight: tab === 'timeline' ? '600' : '400', background: tab === 'timeline' ? 'var(--dsw-alias-bg-layer-3, #222)' : 'transparent' },
|
|
1555
|
+
onClick: () => onSelectTab && onSelectTab('timeline')
|
|
1556
|
+
}, '⏱ ' + (T_KEY(t, 'timeline.tabTimeline'))),
|
|
1557
|
+
React.createElement('button', {
|
|
1558
|
+
type: 'button',
|
|
1559
|
+
className: 'dsh-cron-btn-secondary',
|
|
1560
|
+
style: { padding: '4px 12px', fontSize: '12px', fontWeight: tab === 'heatmap' ? '600' : '400', background: tab === 'heatmap' ? 'var(--dsw-alias-bg-layer-3, #222)' : 'transparent' },
|
|
1561
|
+
onClick: () => onSelectTab && onSelectTab('heatmap')
|
|
1562
|
+
}, '🔥 ' + (T_KEY(t, 'heatmap.tabHeatmap'))),
|
|
1563
|
+
React.createElement('button', {
|
|
1564
|
+
type: 'button',
|
|
1565
|
+
className: 'dsh-cron-icon-btn',
|
|
1566
|
+
onClick: onClose,
|
|
1567
|
+
style: { marginLeft: '8px' }
|
|
1568
|
+
}, React.createElement('span', null, '✕'))
|
|
1569
|
+
)
|
|
1570
|
+
),
|
|
1571
|
+
React.createElement('div', {
|
|
1572
|
+
style: { padding: '16px 18px', overflowY: 'auto', flex: 1, display: 'flex', flexDirection: 'column', gap: '14px' }
|
|
1573
|
+
},
|
|
1574
|
+
isLoading ? React.createElement('div', { style: { padding: '40px', textAlign: 'center', color: 'var(--dsw-alias-label-secondary, #888)' } }, 'Loading visualization data...') : (
|
|
1575
|
+
tab === 'timeline' ? (
|
|
1576
|
+
React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: '8px' } },
|
|
1577
|
+
React.createElement('div', { style: { fontSize: '12px', color: 'var(--dsw-alias-label-secondary, #888)', display: 'flex', justifyContent: 'space-between' } },
|
|
1578
|
+
React.createElement('span', null, T_KEY(t, 'timeline.legendDesc')),
|
|
1579
|
+
React.createElement('span', { style: { color: 'var(--dsh-cron-warning, #fa0)' } }, '⚠️ ' + (T_KEY(t, 'timeline.peakWarning')) + ' (' + timeline.maxConcurrent + ')')
|
|
1580
|
+
),
|
|
1581
|
+
React.createElement('div', {
|
|
1582
|
+
style: { display: 'grid', gridTemplateColumns: 'repeat(12, 1fr)', gap: '8px', marginTop: '6px' }
|
|
1583
|
+
},
|
|
1584
|
+
(timeline.buckets || []).map((b) => {
|
|
1585
|
+
const isPeak = b.isPeak, hasRuns = b.count > 0;
|
|
1586
|
+
return React.createElement('div', {
|
|
1587
|
+
key: b.index,
|
|
1588
|
+
style: {
|
|
1589
|
+
background: isPeak ? 'color-mix(in srgb, var(--dsh-cron-danger, #e33) 12%, var(--dsw-alias-bg-layer-2, #111))' : (hasRuns ? 'var(--dsw-alias-bg-layer-2, #111)' : 'var(--dsw-alias-bg-base, #111)'),
|
|
1590
|
+
border: isPeak ? '1px solid var(--dsh-cron-danger, #e33)' : (hasRuns ? '1px solid var(--dsw-alias-border-l2, #333)' : '1px dashed var(--dsw-alias-border-l2, #222)'),
|
|
1591
|
+
borderRadius: '6px', padding: '8px', minHeight: '75px', display: 'flex', flexDirection: 'column', justifyContent: 'space-between'
|
|
1592
|
+
}
|
|
1593
|
+
},
|
|
1594
|
+
React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', fontSize: '11px' } },
|
|
1595
|
+
React.createElement('span', { style: { fontWeight: '600', color: isPeak ? 'var(--dsh-cron-danger, #e33)' : 'var(--dsw-alias-label-primary, #fff)' } }, b.label),
|
|
1596
|
+
hasRuns ? React.createElement('span', { style: { fontSize: '10px', padding: '1px 5px', borderRadius: '8px', background: isPeak ? 'var(--dsh-cron-danger, #e33)' : 'var(--dsw-alias-bg-layer-3, #222)', color: '#fff' } }, b.count) : null
|
|
1597
|
+
),
|
|
1598
|
+
React.createElement('div', { style: { display: 'flex', flexWrap: 'wrap', gap: '3px', marginTop: '4px' } },
|
|
1599
|
+
(b.runs || []).slice(0, 3).map((r, rIdx) => React.createElement('span', {
|
|
1600
|
+
key: rIdx, title: (r.title || r.taskId) + ' (' + r.time + ')',
|
|
1601
|
+
style: { fontSize: '9.5px', fontFamily: 'monospace', padding: '1px 4px', borderRadius: '3px', background: 'var(--dsw-alias-bg-base, #111)', border: '1px solid var(--dsw-alias-border-l2, #333)', maxWidth: '100%', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }
|
|
1602
|
+
}, r.title || r.taskId))
|
|
1603
|
+
)
|
|
1604
|
+
);
|
|
1605
|
+
})
|
|
1606
|
+
)
|
|
1607
|
+
)
|
|
1608
|
+
) : (
|
|
1609
|
+
React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: '10px' } },
|
|
1610
|
+
React.createElement('div', { style: { fontSize: '12px', color: 'var(--dsw-alias-label-secondary, #888)', display: 'flex', justifyContent: 'space-between' } },
|
|
1611
|
+
React.createElement('span', null, T_KEY(t, 'heatmap.matrixDesc')),
|
|
1612
|
+
React.createElement('span', null, 'Max hour spend: $' + Number(heatmap.maxCostHour || 0).toFixed(4))
|
|
1613
|
+
),
|
|
1614
|
+
React.createElement('div', {
|
|
1615
|
+
style: { overflowX: 'auto', paddingBottom: '8px' }
|
|
1616
|
+
},
|
|
1617
|
+
React.createElement('table', {
|
|
1618
|
+
style: { borderCollapse: 'collapse', width: '100%', minWidth: '700px', fontSize: '10.5px' }
|
|
1619
|
+
},
|
|
1620
|
+
React.createElement('thead', null,
|
|
1621
|
+
React.createElement('tr', null,
|
|
1622
|
+
React.createElement('th', { style: { padding: '4px 6px', textAlign: 'left', color: 'var(--dsw-alias-label-secondary, #888)' } }, 'Day'),
|
|
1623
|
+
Array.from({ length: 24 }, (_, h) => React.createElement('th', {
|
|
1624
|
+
key: h,
|
|
1625
|
+
style: { padding: '4px 2px', textAlign: 'center', color: 'var(--dsw-alias-label-secondary, #888)', fontWeight: '400' }
|
|
1626
|
+
}, String(h).padStart(2, '0')))
|
|
1627
|
+
)
|
|
1628
|
+
),
|
|
1629
|
+
React.createElement('tbody', null,
|
|
1630
|
+
(heatmap.matrix || []).map((row, dIdx) => React.createElement('tr', { key: dIdx },
|
|
1631
|
+
React.createElement('td', { style: { padding: '4px 6px', fontWeight: '600', color: 'var(--dsw-alias-label-primary, #ddd)' } }, (heatmap.dayLabels && heatmap.dayLabels[dIdx]) || 'Day'),
|
|
1632
|
+
row.map((cell, hIdx) => {
|
|
1633
|
+
const intensity = cell.costIntensity || 0;
|
|
1634
|
+
const hasSpend = cell.costUsd > 0;
|
|
1635
|
+
const bg = hasSpend
|
|
1636
|
+
? 'color-mix(in srgb, var(--dsh-cron-primary, #4af) ' + Math.max(15, Math.round(intensity * 100)) + '%, transparent)'
|
|
1637
|
+
: 'transparent';
|
|
1638
|
+
return React.createElement('td', {
|
|
1639
|
+
key: hIdx,
|
|
1640
|
+
title: cell.dayLabel + ' ' + cell.hourLabel + ': $' + cell.costUsd + ' (' + cell.tokens.toLocaleString() + ' tokens, ' + cell.runs + ' runs)',
|
|
1641
|
+
style: {
|
|
1642
|
+
padding: '3px',
|
|
1643
|
+
textAlign: 'center',
|
|
1644
|
+
background: bg,
|
|
1645
|
+
border: '1px solid var(--dsw-alias-border-l2, #222)',
|
|
1646
|
+
cursor: hasSpend ? 'pointer' : 'default'
|
|
1647
|
+
}
|
|
1648
|
+
}, hasSpend ? React.createElement('span', { style: { fontSize: '9px', color: intensity > 0.5 ? '#fff' : 'var(--dsw-alias-label-primary, #ddd)' } }, '$' + (cell.costUsd >= 0.01 ? cell.costUsd.toFixed(2) : cell.costUsd.toFixed(3).slice(1))) : '·');
|
|
1649
|
+
})
|
|
1650
|
+
))
|
|
1651
|
+
)
|
|
1652
|
+
)
|
|
1653
|
+
)
|
|
1654
|
+
)
|
|
1655
|
+
)
|
|
1656
|
+
)
|
|
1657
|
+
),
|
|
1658
|
+
React.createElement('div', {
|
|
1659
|
+
style: { display: 'flex', justifyContent: 'flex-end', padding: '12px 18px', borderTop: '1px solid var(--dsw-alias-border-l2, #333)', background: 'var(--dsw-alias-bg-layer-2, #111)' }
|
|
1660
|
+
},
|
|
1661
|
+
React.createElement('button', {
|
|
1662
|
+
type: 'button',
|
|
1663
|
+
className: 'dsh-cron-btn-secondary',
|
|
1664
|
+
onClick: onClose,
|
|
1665
|
+
style: { padding: '6px 16px', fontSize: '12px' }
|
|
1666
|
+
}, T_KEY(t, 'chainGraph.close'))
|
|
1667
|
+
)
|
|
1668
|
+
)
|
|
1669
|
+
);
|
|
1670
|
+
}
|
|
1484
1671
|
function CronScreen(props) {
|
|
1485
1672
|
const { ctx, toggle, onClose } = props;
|
|
1486
1673
|
const t = props.t || translate;
|
|
@@ -1545,6 +1732,17 @@ const [chainGraphModalOpen, setChainGraphModalOpen] = React.useState(false);
|
|
|
1545
1732
|
const [chainGraphData, setChainGraphData] = React.useState(null);
|
|
1546
1733
|
const [chainGraphLoading, setChainGraphLoading] = React.useState(false);
|
|
1547
1734
|
const [chainGraphRunId, setChainGraphRunId] = React.useState(null);
|
|
1735
|
+
const [digestOpen, setDigestOpen] = React.useState(false);
|
|
1736
|
+
const [digestData, setDigestData] = React.useState(null);
|
|
1737
|
+
const [digestMarkdown, setDigestMarkdown] = React.useState('');
|
|
1738
|
+
const [digestLoading, setDigestLoading] = React.useState(false);
|
|
1739
|
+
const [periodDays, setPeriodDays] = React.useState(7);
|
|
1740
|
+
const [sendStatus, setSendStatus] = React.useState(null);
|
|
1741
|
+
const [timelineOpen, setTimelineOpen] = React.useState(false);
|
|
1742
|
+
const [timelineData, setTimelineData] = React.useState(null);
|
|
1743
|
+
const [heatmapData, setHeatmapData] = React.useState(null);
|
|
1744
|
+
const [timelineLoading, setTimelineLoading] = React.useState(false);
|
|
1745
|
+
const [timelineTab, setTimelineTab] = React.useState('timeline');
|
|
1548
1746
|
const [formHeartbeatIntervalSeconds, setFormHeartbeatIntervalSeconds] = React.useState(0);
|
|
1549
1747
|
const [formGracePeriodSeconds, setFormGracePeriodSeconds] = React.useState(300);
|
|
1550
1748
|
const [formPreflightType, setFormPreflightType] = React.useState('none');
|
|
@@ -2688,6 +2886,67 @@ window.__dshLiveLogsEvtSource = null;
|
|
|
2688
2886
|
}
|
|
2689
2887
|
setLiveLogsModalOpen(false);
|
|
2690
2888
|
}
|
|
2889
|
+
const openAnalyticsDigest = async (days = 7) => {
|
|
2890
|
+
setDigestOpen(true);
|
|
2891
|
+
setDigestLoading(true);
|
|
2892
|
+
setPeriodDays(days);
|
|
2893
|
+
setSendStatus(null);
|
|
2894
|
+
try {
|
|
2895
|
+
const { res, data } = await fetchJsonWithTimeout('/dsh-cron/analytics?days=' + encodeURIComponent(days));
|
|
2896
|
+
if (data && data.ok) {
|
|
2897
|
+
setDigestData(data.digest || null);
|
|
2898
|
+
setDigestMarkdown(data.markdown || '');
|
|
2899
|
+
}
|
|
2900
|
+
} catch (err) {
|
|
2901
|
+
console.debug('[dsh-cron] analytics digest fetch failed', err);
|
|
2902
|
+
} finally {
|
|
2903
|
+
setDigestLoading(false);
|
|
2904
|
+
}
|
|
2905
|
+
};
|
|
2906
|
+
const onSelectPeriod = async (days) => {
|
|
2907
|
+
setPeriodDays(days);
|
|
2908
|
+
await openAnalyticsDigest(days);
|
|
2909
|
+
};
|
|
2910
|
+
const onSendTelegram = async () => {
|
|
2911
|
+
setSendStatus('⏳ ' + (T_KEY(t, 'digest.sending') || 'Sending...'));
|
|
2912
|
+
try {
|
|
2913
|
+
const { res, data } = await fetchJsonWithTimeout('/dsh-cron/analytics', {
|
|
2914
|
+
method: 'POST',
|
|
2915
|
+
body: JSON.stringify({ days: periodDays })
|
|
2916
|
+
});
|
|
2917
|
+
if (data && data.ok) {
|
|
2918
|
+
setSendStatus('✅ ' + (T_KEY(t, 'digest.sent') || 'Sent!'));
|
|
2919
|
+
} else {
|
|
2920
|
+
setSendStatus('❌ ' + (data?.error || 'Failed'));
|
|
2921
|
+
}
|
|
2922
|
+
} catch (err) {
|
|
2923
|
+
setSendStatus('❌ ' + (err.message || 'Error'));
|
|
2924
|
+
}
|
|
2925
|
+
};
|
|
2926
|
+
const openTimelineModal = async (tab = 'timeline') => {
|
|
2927
|
+
setTimelineOpen(true);
|
|
2928
|
+
setTimelineLoading(true);
|
|
2929
|
+
setTimelineTab(tab);
|
|
2930
|
+
try {
|
|
2931
|
+
const [timelineRes, heatmapRes] = await Promise.all([
|
|
2932
|
+
fetchJsonWithTimeout('/dsh-cron/schedule/timeline-24h?hours=24'),
|
|
2933
|
+
fetchJsonWithTimeout('/dsh-cron/analytics/heatmap')
|
|
2934
|
+
]);
|
|
2935
|
+
if (timelineRes?.data?.ok && timelineRes?.data?.timeline) {
|
|
2936
|
+
setTimelineData(timelineRes.data.timeline);
|
|
2937
|
+
}
|
|
2938
|
+
if (heatmapRes?.data?.ok && heatmapRes?.data?.heatmap) {
|
|
2939
|
+
setHeatmapData(heatmapRes.data.heatmap);
|
|
2940
|
+
}
|
|
2941
|
+
} catch (err) {
|
|
2942
|
+
console.debug('[dsh-cron] timeline fetch failed', err);
|
|
2943
|
+
} finally {
|
|
2944
|
+
setTimelineLoading(false);
|
|
2945
|
+
}
|
|
2946
|
+
};
|
|
2947
|
+
const onSelectTimelineTab = (tab) => {
|
|
2948
|
+
setTimelineTab(tab);
|
|
2949
|
+
};
|
|
2691
2950
|
return React.createElement('div', { className: 'dsh-cron-overlay' },
|
|
2692
2951
|
React.createElement('div', { className: 'dsh-cron-container' },
|
|
2693
2952
|
React.createElement('div', { className: 'dsh-cron-top-bar' },
|
|
@@ -2711,6 +2970,16 @@ React.createElement('button', {
|
|
|
2711
2970
|
type: 'button',
|
|
2712
2971
|
className: 'dsh-cron-btn-secondary',
|
|
2713
2972
|
style: { display: 'flex', alignItems: 'center', gap: '6px', height: '36px', padding: '0 12px' },
|
|
2973
|
+
title: T_KEY(t, 'timeline.buttonTitle'),
|
|
2974
|
+
onClick: () => openTimelineModal('timeline')
|
|
2975
|
+
},
|
|
2976
|
+
React.createElement('span', { style: { fontSize: '14px' } }, '⏱'),
|
|
2977
|
+
T_KEY(t, 'timeline.buttonLabel')
|
|
2978
|
+
),
|
|
2979
|
+
React.createElement('button', {
|
|
2980
|
+
type: 'button',
|
|
2981
|
+
className: 'dsh-cron-btn-secondary',
|
|
2982
|
+
style: { display: 'flex', alignItems: 'center', gap: '6px', height: '36px', padding: '0 12px' },
|
|
2714
2983
|
title: T_KEY(t, 'digest.buttonTitle'),
|
|
2715
2984
|
onClick: openAnalyticsDigest
|
|
2716
2985
|
},
|
|
@@ -3612,21 +3881,21 @@ React.createElement('span', null, T_KEY(t, 'form.keepWorktreeLabel'))
|
|
|
3612
3881
|
formType !== 'http' && React.createElement('div', { className: 'dsh-cron-form-group' },
|
|
3613
3882
|
React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '4px' } },
|
|
3614
3883
|
React.createElement('label', { style: { margin: 0 } }, AGENT_FORM_TYPES.includes(formType) ? T_KEY(t, 'form.promptLabelLlm') : T_KEY(t, 'form.promptLabelScript')),
|
|
3615
|
-
(
|
|
3884
|
+
(selectedTaskMeta && selectedTaskMeta.promptHistory && selectedTaskMeta.promptHistory.length > 0) ? React.createElement('div', { style: { display: 'flex', gap: '6px', alignItems: 'center' } },
|
|
3616
3885
|
React.createElement('span', {
|
|
3617
3886
|
style: { fontSize: '11px', padding: '1px 6px', borderRadius: '4px', background: 'var(--dsw-alias-bg-layer-3, #222)', color: 'var(--dsh-cron-info, #4af)', border: '1px solid var(--dsw-alias-border-l2, #333)' }
|
|
3618
|
-
}, 'v' + (
|
|
3887
|
+
}, 'v' + (selectedTaskMeta.promptVersion || 1) + ' (' + (selectedTaskMeta.promptHistory[selectedTaskMeta.promptHistory.length - 1]?.successRate !== undefined ? Math.round(selectedTaskMeta.promptHistory[selectedTaskMeta.promptHistory.length - 1].successRate * 100) : 100) + '% SR)'),
|
|
3619
3888
|
React.createElement('select', {
|
|
3620
3889
|
style: { fontSize: '11px', padding: '1px 4px', background: 'var(--dsw-alias-bg-base, #111)', color: 'var(--dsw-alias-label-primary, #ddd)', border: '1px solid var(--dsw-alias-border-l2, #333)', borderRadius: '4px' },
|
|
3621
3890
|
onChange: (e) => {
|
|
3622
3891
|
const targetV = Number(e.target.value);
|
|
3623
|
-
const entry =
|
|
3892
|
+
const entry = selectedTaskMeta.promptHistory.find((h) => h.version === targetV);
|
|
3624
3893
|
if (entry && entry.prompt) {
|
|
3625
3894
|
setFormPrompt(entry.prompt);
|
|
3626
3895
|
}
|
|
3627
3896
|
}
|
|
3628
3897
|
},
|
|
3629
|
-
|
|
3898
|
+
selectedTaskMeta.promptHistory.map((h) => React.createElement('option', { key: h.version, value: h.version }, 'v' + h.version + ' (' + Math.round((h.successRate || 1) * 100) + '%)'))
|
|
3630
3899
|
)
|
|
3631
3900
|
) : null
|
|
3632
3901
|
),
|
|
@@ -4242,6 +4511,27 @@ onToggleAutoScroll: () => setLiveLogsAutoScroll((v) => !v),
|
|
|
4242
4511
|
completed: liveLogsCompleted,
|
|
4243
4512
|
status: liveLogsStatus,
|
|
4244
4513
|
t
|
|
4514
|
+
}),
|
|
4515
|
+
renderAnalyticsDigestModal({
|
|
4516
|
+
isOpen: digestOpen,
|
|
4517
|
+
onClose: () => setDigestOpen(false),
|
|
4518
|
+
digestData,
|
|
4519
|
+
isLoading: digestLoading,
|
|
4520
|
+
periodDays,
|
|
4521
|
+
onSelectPeriod,
|
|
4522
|
+
onSendTelegram,
|
|
4523
|
+
sendStatus,
|
|
4524
|
+
t
|
|
4525
|
+
}),
|
|
4526
|
+
renderTimelineHeatmapModal({
|
|
4527
|
+
isOpen: timelineOpen,
|
|
4528
|
+
onClose: () => setTimelineOpen(false),
|
|
4529
|
+
activeTab: timelineTab,
|
|
4530
|
+
onSelectTab: onSelectTimelineTab,
|
|
4531
|
+
timelineData,
|
|
4532
|
+
heatmapData,
|
|
4533
|
+
isLoading: timelineLoading,
|
|
4534
|
+
t
|
|
4245
4535
|
})
|
|
4246
4536
|
);
|
|
4247
4537
|
}
|