@goodandready/dsh-cron 0.2.9 → 0.2.11
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/README.md +27 -2
- package/README.ru.md +481 -0
- package/README.zh.md +481 -0
- package/docs/README.ru.md +34 -1
- package/docs/README.zh.md +34 -1
- package/lib/api.js +224 -2
- package/lib/channels.js +3 -2
- package/lib/client.js +594 -3
- package/lib/index.js +21 -2
- package/lib/llm-actions.js +141 -0
- package/lib/metrics.js +24 -1
- package/lib/runner.js +24 -11
- package/lib/scheduler.js +302 -13
- package/lib/store.js +90 -0
- package/lib/task-transfer.js +10 -0
- package/lib/telegram.js +43 -1
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -291,7 +291,265 @@ window.__ModuleLoader__.load({
|
|
|
291
291
|
'dsh.startFailed': 'Failed to start the agent dialog: {error}',
|
|
292
292
|
'dsh.launchError': 'Launch failed: {error}',
|
|
293
293
|
'confirm.delete': 'Delete this task?',
|
|
294
|
-
'list.loadErrorPrefix': 'Failed to load tasks'
|
|
294
|
+
'list.loadErrorPrefix': 'Failed to load tasks',
|
|
295
|
+
'actions.dryRun': 'Dry run',
|
|
296
|
+
'actions.dryRunTitle': 'Dry run (test execution without side effects or notifications)',
|
|
297
|
+
'actions.archive': 'Archive',
|
|
298
|
+
'actions.archiveTitle': 'Execution archive and logs',
|
|
299
|
+
'dryRun.title': '🧪 Dry Run Simulation',
|
|
300
|
+
'dryRun.task': 'Task',
|
|
301
|
+
'dryRun.status': 'Simulation Status',
|
|
302
|
+
'dryRun.duration': 'Duration',
|
|
303
|
+
'dryRun.output': 'Simulated Output',
|
|
304
|
+
'dryRun.preflight': 'Preflight Check',
|
|
305
|
+
'dryRun.close': 'Close',
|
|
306
|
+
'archive.title': '📜 Execution Archive',
|
|
307
|
+
'archive.totalRuns': '{total} total runs recorded',
|
|
308
|
+
'archive.empty': 'No archived runs recorded for this task yet.',
|
|
309
|
+
'archive.loadMore': 'Load more ({shown} of {total})',
|
|
310
|
+
'archive.close': 'Close',
|
|
311
|
+
'form.reliabilityTitle': '🛡️ Reliability, Heartbeat & Self-Healing',
|
|
312
|
+
'form.preflightLabel': 'Pre-flight check',
|
|
313
|
+
'form.preflightNone': 'None',
|
|
314
|
+
'form.preflightHttp': 'HTTP (2xx probe)',
|
|
315
|
+
'form.preflightShell': 'Shell command (exit code 0)',
|
|
316
|
+
'form.preflightDisk': 'Disk space (path:minPercent)',
|
|
317
|
+
'form.preflightTargetLabel': 'Pre-flight target',
|
|
318
|
+
'form.preflightTargetPlaceholder': 'https://example.com/healthz OR ls /data OR /:10%',
|
|
319
|
+
'form.heartbeatIntervalLabel': 'Heartbeat deadline (seconds, 0 = disabled)',
|
|
320
|
+
'form.gracePeriodLabel': 'Heartbeat grace period (seconds)',
|
|
321
|
+
'form.priorityLabel': 'Queue priority (1-10, higher runs first)',
|
|
322
|
+
'form.concurrencyGroupLabel': 'Concurrency group',
|
|
323
|
+
'form.selfHealingLabel': 'Self-healing runbook command (shell)',
|
|
324
|
+
'form.selfHealingPlaceholder': 'systemctl restart app || true',
|
|
325
|
+
'form.autoDiagnoseLabel': 'Auto-diagnose failures with AI agent',
|
|
326
|
+
'form.previewSchedule': 'Preview next runs',
|
|
327
|
+
'form.previewScheduleTitle': 'Next 5 scheduled runs:',
|
|
328
|
+
},
|
|
329
|
+
zh: {
|
|
330
|
+
'sidebar.label': '定时任务',
|
|
331
|
+
'sidebar.jobsTitle': '运行中任务',
|
|
332
|
+
'sidebar.noActiveJobs': '无运行中任务',
|
|
333
|
+
'sidebar.moreJobs': '及另外 {count} 个',
|
|
334
|
+
'chip.title': '定时任务 (Cron)',
|
|
335
|
+
'panel.back': '返回对话',
|
|
336
|
+
'panel.title': '定时任务',
|
|
337
|
+
'panel.subtitle': '让 DSH 规划任务、设置提醒或跟踪状态',
|
|
338
|
+
'actions.create': '新建',
|
|
339
|
+
'actions.createWithDsh': '💬 让 DSH 创建',
|
|
340
|
+
'actions.setupManually': '✏️ 手动配置',
|
|
341
|
+
'actions.runNow': '立即运行',
|
|
342
|
+
'actions.runNowShort': '运行',
|
|
343
|
+
'actions.edit': '编辑 / 详情',
|
|
344
|
+
'actions.duplicate': '复制任务',
|
|
345
|
+
'actions.tuneWithDsh': '让 DSH 优化',
|
|
346
|
+
'tune.startFailed': '无法启动优化会话: {error}',
|
|
347
|
+
'actions.delete': '删除任务',
|
|
348
|
+
'duplicate.done': '已创建副本并处于暂停状态:恢复前请先核对调度时间。',
|
|
349
|
+
'duplicate.oneShotHint': '该副本为一次性任务且已暂停,请设定新的执行时间后再启用。',
|
|
350
|
+
'duplicate.failed': '复制任务失败: {error}',
|
|
351
|
+
'transfer.exportTitle': '导出任务',
|
|
352
|
+
'transfer.importTitle': '导入任务',
|
|
353
|
+
'transfer.exported': '已成功导出 {count} 个任务。',
|
|
354
|
+
'transfer.exportFailed': '导出失败: {error}',
|
|
355
|
+
'transfer.importTitleModal': '导入任务',
|
|
356
|
+
'transfer.importHint': '仅导出任务静态配置:不包含历史记录与计数器。不包含密钥——渠道通过凭据名称引用。',
|
|
357
|
+
'transfer.strategyAdd': '作为新任务导入(分配新 ID)',
|
|
358
|
+
'transfer.strategyReplace': '覆盖同 ID 任务',
|
|
359
|
+
'transfer.strategySkip': '跳过已存在的同 ID 任务',
|
|
360
|
+
'transfer.summary': '该文件包含 {total} 个任务:{add} 个新增,{replace} 个覆盖,{skip} 个跳过。',
|
|
361
|
+
'transfer.confirm': '开始导入',
|
|
362
|
+
'transfer.imported': '已成功导出 {count} 个任务。',
|
|
363
|
+
'transfer.importFailed': '导入失败: {error}',
|
|
364
|
+
'transfer.badFile': '所选文件不是有效的 JSON 导出文件: {error}',
|
|
365
|
+
'actions.pause': '暂停',
|
|
366
|
+
'actions.resume': '恢复',
|
|
367
|
+
'actions.settings': '通知与渠道设置',
|
|
368
|
+
'actions.dryRun': '空跑测试',
|
|
369
|
+
'actions.dryRunTitle': '空跑测试(模拟执行,不产生副作用且不发送通知)',
|
|
370
|
+
'actions.archive': '历史归档',
|
|
371
|
+
'actions.archiveTitle': '查看执行历史与日志归档',
|
|
372
|
+
'dryRun.title': '🧪 空跑模拟结果',
|
|
373
|
+
'dryRun.task': '任务',
|
|
374
|
+
'dryRun.status': '模拟状态',
|
|
375
|
+
'dryRun.duration': '耗时',
|
|
376
|
+
'dryRun.output': '模拟输出',
|
|
377
|
+
'dryRun.preflight': '预检结果',
|
|
378
|
+
'dryRun.close': '关闭',
|
|
379
|
+
'archive.title': '📜 运行历史归档',
|
|
380
|
+
'archive.totalRuns': '共记录 {total} 次运行',
|
|
381
|
+
'archive.empty': '该任务暂无历史运行归档。',
|
|
382
|
+
'archive.loadMore': '加载更多 ({shown} / {total})',
|
|
383
|
+
'archive.close': '关闭',
|
|
384
|
+
'form.reliabilityTitle': '🛡️ 可靠性、心跳监听与自愈',
|
|
385
|
+
'form.preflightLabel': '预检健康探测',
|
|
386
|
+
'form.preflightNone': '无',
|
|
387
|
+
'form.preflightHttp': 'HTTP (2xx 探测)',
|
|
388
|
+
'form.preflightShell': 'Shell 探测 (退出码 0)',
|
|
389
|
+
'form.preflightDisk': '磁盘空间探测 (挂载点:最小百分比)',
|
|
390
|
+
'form.preflightTargetLabel': '预检目标参数',
|
|
391
|
+
'form.preflightTargetPlaceholder': 'https://example.com/healthz 或 ls /data 或 /:10%',
|
|
392
|
+
'form.heartbeatIntervalLabel': '心跳打卡时限 (秒, 0 = 禁用)',
|
|
393
|
+
'form.gracePeriodLabel': '心跳宽限期 (秒)',
|
|
394
|
+
'form.priorityLabel': '队列优先级 (1-10,数值越大越先执行)',
|
|
395
|
+
'form.concurrencyGroupLabel': '并发分组名称',
|
|
396
|
+
'form.selfHealingLabel': '故障自愈脚本 (Shell)',
|
|
397
|
+
'form.selfHealingPlaceholder': 'systemctl restart app || true',
|
|
398
|
+
'form.autoDiagnoseLabel': '任务失败时启用 AI 智能分析诊断',
|
|
399
|
+
'form.previewSchedule': '预测下次运行时间',
|
|
400
|
+
'form.previewScheduleTitle': '未来 5 次计划运行时间:',
|
|
401
|
+
'search.placeholder': '搜索定时任务',
|
|
402
|
+
'filters.title': '筛选',
|
|
403
|
+
'filters.type': '类型',
|
|
404
|
+
'filters.model': '模型',
|
|
405
|
+
'filters.channel': '渠道',
|
|
406
|
+
'filters.any': '全部',
|
|
407
|
+
'filters.reset': '重置筛选',
|
|
408
|
+
'filters.empty': '没有匹配当前筛选条件的任务。',
|
|
409
|
+
'filters.showing': '显示 {shown} / 共 {total}',
|
|
410
|
+
'stats.activeTasks': '活跃任务',
|
|
411
|
+
'stats.totalRuns': '总运行次数',
|
|
412
|
+
'stats.totalTokens': '消耗 Token',
|
|
413
|
+
'stats.totalCost': '预估成本',
|
|
414
|
+
'tabs.all': '全部',
|
|
415
|
+
'tabs.active': '运行中',
|
|
416
|
+
'tabs.paused': '已暂停',
|
|
417
|
+
'tabs.completed': '已完成',
|
|
418
|
+
'list.empty': '暂无定时任务',
|
|
419
|
+
'list.emptyHint': '点击下方按钮让 DSH 规划任务,或手动配置。',
|
|
420
|
+
'list.oneShot': '一次性',
|
|
421
|
+
'list.runCount': '{count} 次运行',
|
|
422
|
+
'list.consecutiveFailures': '连续 {count} 次失败',
|
|
423
|
+
'list.lastRun': '上次: {time}',
|
|
424
|
+
'list.nextRun': '下次: {time}',
|
|
425
|
+
'list.badgeActive': '活跃',
|
|
426
|
+
'list.badgePaused': '已暂停',
|
|
427
|
+
'list.badgeCompleted': '已完成',
|
|
428
|
+
'list.configManaged': '配置只读',
|
|
429
|
+
'channel.telegram': 'Telegram',
|
|
430
|
+
'channel.discord': 'Discord',
|
|
431
|
+
'channel.slack': 'Slack',
|
|
432
|
+
'channel.ntfy': 'ntfy',
|
|
433
|
+
'channel.bark': 'Bark',
|
|
434
|
+
'channel.pushplus': 'PushPlus',
|
|
435
|
+
'channel.voice': '语音通话',
|
|
436
|
+
'channel.gitea': 'Gitea',
|
|
437
|
+
'channel.webhook': 'Webhook',
|
|
438
|
+
'modal.editTitle': '编辑任务',
|
|
439
|
+
'modal.newTitle': '新建定时任务',
|
|
440
|
+
'modal.statusActive': '活跃',
|
|
441
|
+
'modal.statusPaused': '已暂停',
|
|
442
|
+
'modal.params': '配置参数',
|
|
443
|
+
'modal.history': '运行历史 ({count})',
|
|
444
|
+
'modal.cancel': '取消',
|
|
445
|
+
'modal.save': '保存修改',
|
|
446
|
+
'modal.create': '创建任务',
|
|
447
|
+
'history.loading': '正在加载历史记录...',
|
|
448
|
+
'history.empty': '暂无运行记录',
|
|
449
|
+
'history.summaryTokens': '消耗 Token: {count}',
|
|
450
|
+
'history.summaryCost': '预估成本: ${cost}',
|
|
451
|
+
'history.errorLabel': '错误详情:',
|
|
452
|
+
'history.outputLabel': '执行输出:',
|
|
453
|
+
'form.titleLabel': '任务名称 *',
|
|
454
|
+
'form.titlePlaceholder': '例如: 每周备份检查',
|
|
455
|
+
'form.typeLabel': '执行类型 *',
|
|
456
|
+
'form.promptLabel': '提示词内容 *',
|
|
457
|
+
'form.promptPlaceholder': '输入发送给智能体的执行指令...',
|
|
458
|
+
'form.modelLabel': 'AI 模型',
|
|
459
|
+
'form.modelDefault': '默认模型',
|
|
460
|
+
'form.fallbackModelLabel': '备用模型 (主模型失败时使用)',
|
|
461
|
+
'form.fallbackModelNone': '不使用备用模型',
|
|
462
|
+
'form.pipelineGroupLabel': '任务流水线串联',
|
|
463
|
+
'form.onSuccessLabel': '成功后触发任务 (ID 或名称)',
|
|
464
|
+
'form.onSuccessPlaceholder': '例如: task_upload_backup',
|
|
465
|
+
'form.onFailureLabel': '失败后触发告警任务 (ID 或名称)',
|
|
466
|
+
'form.onFailurePlaceholder': '例如: task_notify_incident',
|
|
467
|
+
'form.scheduleLabel': '调度计划 (Cron 或相对时间) *',
|
|
468
|
+
'form.schedulePlaceholder': '例如: 0 9 * * 1-5 或 15m 或 in 2 hours',
|
|
469
|
+
'form.channelsLabel': '通知渠道',
|
|
470
|
+
'form.channelsHint': '任务执行完毕后推送报告的目标渠道。',
|
|
471
|
+
'form.templateLabel': '自定义消息模板',
|
|
472
|
+
'form.templatePlaceholder': '留空使用默认摘要',
|
|
473
|
+
'form.templateHint': '支持变量: {title}, {status}, {output}, {error}, {durationMs}, {time}',
|
|
474
|
+
'form.silentRuleLabel': '静默规则 (可选)',
|
|
475
|
+
'form.silentRulePlaceholder': '例如: ALL_OK 或 status == 200',
|
|
476
|
+
'form.silentRuleHint': '若输出符合此规则,将不发送任何通知。',
|
|
477
|
+
'form.advancedToggle': '高级执行选项',
|
|
478
|
+
'form.timezoneLabel': '时区 (IANA 时区名)',
|
|
479
|
+
'form.timezonePlaceholder': '例如: Asia/Shanghai 或 UTC (留空使用服务器时区)',
|
|
480
|
+
'form.misfireLabel': '错过执行策略 (Misfire Policy)',
|
|
481
|
+
'form.misfireSkip': '跳过 (默认)',
|
|
482
|
+
'form.misfireRunOnce': '补跑一次',
|
|
483
|
+
'form.maxRetriesLabel': '失败重试次数 (0 = 不重试)',
|
|
484
|
+
'form.permissionPresetLabel': '权限预设',
|
|
485
|
+
'form.permissionDefault': '默认 (继承主配置)',
|
|
486
|
+
'form.permissionRestricted': '受限模式 (只读)',
|
|
487
|
+
'form.permissionFull': '完全访问权限',
|
|
488
|
+
'form.envLabel': '环境变量 (KEY=VALUE, 每行一个)',
|
|
489
|
+
'form.envPlaceholder': 'FOO=bar\nAPI_KEY=secret',
|
|
490
|
+
'form.cwdLabel': '工作目录 (绝对路径)',
|
|
491
|
+
'form.cwdPlaceholder': '/var/www/my-project',
|
|
492
|
+
'form.workspaceIdLabel': '关联 DSH Workspace ID',
|
|
493
|
+
'form.workspaceIdPlaceholder': '留空使用默认工作区',
|
|
494
|
+
'form.worktreeLabel': '在独立的临时 Git Worktree 中执行',
|
|
495
|
+
'form.keepWorktreeLabel': '执行结束后保留 Git Worktree (不自动清理)',
|
|
496
|
+
'form.timeoutLabel': '超时时间 (秒)',
|
|
497
|
+
'form.overlapLabel': '并发策略',
|
|
498
|
+
'form.overlapSkip': '跳过 (默认)',
|
|
499
|
+
'form.overlapQueue': '排队等待',
|
|
500
|
+
'form.overlapTerminate': '终止前次运行',
|
|
501
|
+
'form.overlapAllow': '允许并发',
|
|
502
|
+
'form.kanbanLabel': '看板同步模式',
|
|
503
|
+
'form.kanbanNone': '不同步到看板',
|
|
504
|
+
'form.kanbanCreate': '仅创建新卡片',
|
|
505
|
+
'form.kanbanSync': '保持卡片同步更新',
|
|
506
|
+
'settings.deliveryChannels': '推送渠道配置',
|
|
507
|
+
'settings.credentialsHint': '安全凭据在 DSH Credentials 服务中统一管理。',
|
|
508
|
+
'settings.saveChannels': '保存渠道配置',
|
|
509
|
+
'settings.testing': '正在测试...',
|
|
510
|
+
'settings.testSuccess': '✅ 测试成功!',
|
|
511
|
+
'settings.automationHint': '用于静默规则研判和故障诊断的辅助模型。留空沿用任务自身模型。',
|
|
512
|
+
'settings.silentRuleModelLabel': '静默规则研判模型',
|
|
513
|
+
'settings.secretsOnCard': '设置中引用的凭据将在前端脱敏展示,并通过名称引用。',
|
|
514
|
+
'form.nextRunLabel': '下次执行: ',
|
|
515
|
+
'form.notScheduled': '未安排计划',
|
|
516
|
+
'settings.loading': '正在加载设置...',
|
|
517
|
+
'settings.unavailable': '设置当前不可用。',
|
|
518
|
+
'settings.retry': '重试',
|
|
519
|
+
'settings.cardTitle': '⏰ 定时任务调度器 (Cron)',
|
|
520
|
+
'settings.cardDesc': '定时计划任务(Cron/间隔)、后台智能体场景以及支持 Telegram、Discord、Slack、ntfy、Bark、PushPlus、语音和 Gitea 的通知推送。',
|
|
521
|
+
'settings.openPanel': '打开任务管理看板',
|
|
522
|
+
'settings.modalTitle': '通知与渠道设置',
|
|
523
|
+
'settings.tokenLabel': 'Telegram Bot Token',
|
|
524
|
+
'settings.tokenPlaceholder': '123456789:ABCdefGhIJKlmNoPQRsTUVwxyZ',
|
|
525
|
+
'settings.tokenPlaceholderDefault': '使用 DSH 全局配置的机器人 (或在此输入自定义 Token)',
|
|
526
|
+
'settings.defaultHint': '✓ 检测到 DSH 环境已配置默认 Bot',
|
|
527
|
+
'settings.chatIdLabel': 'Telegram Chat ID',
|
|
528
|
+
'settings.chatIdPlaceholder': '例如: 345678901',
|
|
529
|
+
'settings.kanbanUrlLabel': 'Kanban 服务基础地址',
|
|
530
|
+
'settings.globalNotify': '全局开启所有任务结果推送至 Telegram',
|
|
531
|
+
'settings.globalOnlyFailure': '全局模式:仅在失败时推送 (成功静默)',
|
|
532
|
+
'settings.testTelegram': '🔔 测试 Telegram 发送',
|
|
533
|
+
'settings.testKanban': '📋 测试 Kanban',
|
|
534
|
+
'settings.sending': '正在发送...',
|
|
535
|
+
'settings.checkingKanban': '正在连接 Kanban...',
|
|
536
|
+
'settings.testOk': '✅ 测试消息已成功发送至 Telegram!',
|
|
537
|
+
'settings.testFail': '❌ 发送失败: {error}',
|
|
538
|
+
'settings.kanbanOk': '✅ Kanban 卡片创建成功',
|
|
539
|
+
'settings.kanbanFail': '❌ {error}',
|
|
540
|
+
'settings.save': '保存设置',
|
|
541
|
+
'settings.saved': '✓ 已保存',
|
|
542
|
+
'settings.saveError': '保存失败: {error}',
|
|
543
|
+
'settings.saveErrorUnknown': '未知错误',
|
|
544
|
+
'dsh.modalTitle': '让 DSH 智能体创建任务',
|
|
545
|
+
'dsh.descLabel': '请描述你的任务目标和执行频率:',
|
|
546
|
+
'dsh.placeholder': '例如: 每两小时检查一次磁盘空间和失败的 systemd 守护进程,并在异常时报警...',
|
|
547
|
+
'dsh.sendHint': '按 Ctrl+Enter 发送',
|
|
548
|
+
'dsh.sendTitle': '提交给智能体',
|
|
549
|
+
'dsh.startFailed': '启动智能体对话失败: {error}',
|
|
550
|
+
'dsh.launchError': '执行错误: {error}',
|
|
551
|
+
'confirm.delete': '确定删除该任务吗?',
|
|
552
|
+
'list.loadErrorPrefix': '加载任务列表失败'
|
|
295
553
|
}
|
|
296
554
|
};
|
|
297
555
|
|
|
@@ -875,6 +1133,24 @@ window.__ModuleLoader__.load({
|
|
|
875
1133
|
const [formTimeoutSeconds, setFormTimeoutSeconds] = React.useState(1800);
|
|
876
1134
|
const [formOverlapPolicy, setFormOverlapPolicy] = React.useState('skip');
|
|
877
1135
|
const [formKanbanMode, setFormKanbanMode] = React.useState('none');
|
|
1136
|
+
const [formOnSuccess, setFormOnSuccess] = React.useState('');
|
|
1137
|
+
const [formOnFailure, setFormOnFailure] = React.useState('');
|
|
1138
|
+
const [formHeartbeatIntervalSeconds, setFormHeartbeatIntervalSeconds] = React.useState(0);
|
|
1139
|
+
const [formGracePeriodSeconds, setFormGracePeriodSeconds] = React.useState(300);
|
|
1140
|
+
const [formPreflightType, setFormPreflightType] = React.useState('none');
|
|
1141
|
+
const [formPreflightTarget, setFormPreflightTarget] = React.useState('');
|
|
1142
|
+
const [formPriority, setFormPriority] = React.useState(5);
|
|
1143
|
+
const [formConcurrencyGroup, setFormConcurrencyGroup] = React.useState('default');
|
|
1144
|
+
const [formSelfHealingCommand, setFormSelfHealingCommand] = React.useState('');
|
|
1145
|
+
const [formAutoDiagnose, setFormAutoDiagnose] = React.useState(false);
|
|
1146
|
+
const [schedulePreviewRuns, setSchedulePreviewRuns] = React.useState(null);
|
|
1147
|
+
const [dryRunResult, setDryRunResult] = React.useState(null);
|
|
1148
|
+
const [archiveModalOpen, setArchiveModalOpen] = React.useState(false);
|
|
1149
|
+
const [archiveTask, setArchiveTask] = React.useState(null);
|
|
1150
|
+
const [archiveRuns, setArchiveRuns] = React.useState([]);
|
|
1151
|
+
const [archiveLoading, setArchiveLoading] = React.useState(false);
|
|
1152
|
+
const [archiveOffset, setArchiveOffset] = React.useState(0);
|
|
1153
|
+
const [archiveTotal, setArchiveTotal] = React.useState(0);
|
|
878
1154
|
const [formTimezone, setFormTimezone] = React.useState('');
|
|
879
1155
|
const [formMisfirePolicy, setFormMisfirePolicy] = React.useState('skip');
|
|
880
1156
|
const [formMaxRetries, setFormMaxRetries] = React.useState(0);
|
|
@@ -1116,6 +1392,53 @@ window.__ModuleLoader__.load({
|
|
|
1116
1392
|
};
|
|
1117
1393
|
|
|
1118
1394
|
/** #41: duplicate a task; the server copies config, resets run state (#41). */
|
|
1395
|
+
const handleDryRun = async (taskId) => {
|
|
1396
|
+
try {
|
|
1397
|
+
const res = await fetch('/dsh-cron/tasks/' + encodeURIComponent(taskId) + '/dry-run', { method: 'POST' });
|
|
1398
|
+
const data = await res.json();
|
|
1399
|
+
setDryRunResult(data);
|
|
1400
|
+
} catch (err) {
|
|
1401
|
+
alert('Dry run error: ' + err.message);
|
|
1402
|
+
}
|
|
1403
|
+
};
|
|
1404
|
+
|
|
1405
|
+
const openArchiveModal = async (task, offset = 0) => {
|
|
1406
|
+
setArchiveTask(task);
|
|
1407
|
+
setArchiveOffset(offset);
|
|
1408
|
+
setArchiveLoading(true);
|
|
1409
|
+
setArchiveModalOpen(true);
|
|
1410
|
+
try {
|
|
1411
|
+
const res = await fetch('/dsh-cron/tasks/' + encodeURIComponent(task.id) + '/archive?limit=15&offset=' + offset);
|
|
1412
|
+
const data = await res.json();
|
|
1413
|
+
if (data.ok) {
|
|
1414
|
+
setArchiveRuns(data.runs || []);
|
|
1415
|
+
setArchiveTotal(data.total || 0);
|
|
1416
|
+
}
|
|
1417
|
+
} catch (err) {
|
|
1418
|
+
console.error('[dsh-cron] archive load error:', err);
|
|
1419
|
+
} finally {
|
|
1420
|
+
setArchiveLoading(false);
|
|
1421
|
+
}
|
|
1422
|
+
};
|
|
1423
|
+
|
|
1424
|
+
const handlePreviewSchedule = async (scheduleExpr) => {
|
|
1425
|
+
try {
|
|
1426
|
+
const res = await fetch('/dsh-cron/schedule/preview', {
|
|
1427
|
+
method: 'POST',
|
|
1428
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1429
|
+
body: JSON.stringify({ schedule: scheduleExpr, timezone: formTimezone })
|
|
1430
|
+
});
|
|
1431
|
+
const data = await res.json();
|
|
1432
|
+
if (data.ok && data.runs) {
|
|
1433
|
+
setSchedulePreviewRuns(data.runs);
|
|
1434
|
+
} else {
|
|
1435
|
+
setSchedulePreviewRuns([]);
|
|
1436
|
+
}
|
|
1437
|
+
} catch {
|
|
1438
|
+
setSchedulePreviewRuns([]);
|
|
1439
|
+
}
|
|
1440
|
+
};
|
|
1441
|
+
|
|
1119
1442
|
const handleDuplicate = async (task) => {
|
|
1120
1443
|
try {
|
|
1121
1444
|
const res = await fetch('/dsh-cron/tasks/' + encodeURIComponent(task.id) + '/duplicate', { method: 'POST' });
|
|
@@ -1465,6 +1788,17 @@ window.__ModuleLoader__.load({
|
|
|
1465
1788
|
setFormTimeoutSeconds(task.timeoutSeconds !== undefined ? task.timeoutSeconds : 1800);
|
|
1466
1789
|
setFormOverlapPolicy(task.overlapPolicy || 'skip');
|
|
1467
1790
|
setFormKanbanMode(task.kanbanMode || 'none');
|
|
1791
|
+
setFormOnSuccess(task.onSuccess || '');
|
|
1792
|
+
setFormOnFailure(task.onFailure || '');
|
|
1793
|
+
setFormHeartbeatIntervalSeconds(task.heartbeatIntervalSeconds || 0);
|
|
1794
|
+
setFormGracePeriodSeconds(task.gracePeriodSeconds || 300);
|
|
1795
|
+
setFormPreflightType(task.preflightType || 'none');
|
|
1796
|
+
setFormPreflightTarget(task.preflightTarget || '');
|
|
1797
|
+
setFormPriority(task.priority !== undefined ? task.priority : 5);
|
|
1798
|
+
setFormConcurrencyGroup(task.concurrencyGroup || 'default');
|
|
1799
|
+
setFormSelfHealingCommand(task.selfHealingCommand || '');
|
|
1800
|
+
setFormAutoDiagnose(Boolean(task.autoDiagnose));
|
|
1801
|
+
setSchedulePreviewRuns(null);
|
|
1468
1802
|
setFormTimezone(task.timezone || '');
|
|
1469
1803
|
setFormMisfirePolicy(task.misfirePolicy || 'skip');
|
|
1470
1804
|
setFormMaxRetries(task.maxRetries !== undefined ? task.maxRetries : 0);
|
|
@@ -1527,6 +1861,16 @@ window.__ModuleLoader__.load({
|
|
|
1527
1861
|
maxRetries: Number(formMaxRetries) || 0,
|
|
1528
1862
|
permissionPreset: formPermissionPreset,
|
|
1529
1863
|
kanbanMode: formKanbanMode,
|
|
1864
|
+
onSuccess: formOnSuccess.trim() || undefined,
|
|
1865
|
+
onFailure: formOnFailure.trim() || undefined,
|
|
1866
|
+
heartbeatIntervalSeconds: Number(formHeartbeatIntervalSeconds) || 0,
|
|
1867
|
+
gracePeriodSeconds: Number(formGracePeriodSeconds) || 300,
|
|
1868
|
+
preflightType: formPreflightType || 'none',
|
|
1869
|
+
preflightTarget: formPreflightTarget.trim() || undefined,
|
|
1870
|
+
priority: Number(formPriority) || 5,
|
|
1871
|
+
concurrencyGroup: formConcurrencyGroup.trim() || 'default',
|
|
1872
|
+
selfHealingCommand: formSelfHealingCommand.trim() || undefined,
|
|
1873
|
+
autoDiagnose: Boolean(formAutoDiagnose),
|
|
1530
1874
|
env: parseEnvText(formEnv),
|
|
1531
1875
|
cwd: formCwd.trim(),
|
|
1532
1876
|
workspaceId: formWorkspaceId.trim(),
|
|
@@ -1832,6 +2176,11 @@ window.__ModuleLoader__.load({
|
|
|
1832
2176
|
className: 'dsh-cron-config-tag',
|
|
1833
2177
|
title: T_KEY(t, 'list.configManagedHint')
|
|
1834
2178
|
}, T_KEY(t, 'list.configManaged')) : null,
|
|
2179
|
+
task.lastDurationMs > 0 ? React.createElement('span', { className: 'dsh-cron-cost-tag', title: 'Last execution duration' }, task.lastDurationMs + 'ms') : null,
|
|
2180
|
+
task.onSuccess ? React.createElement('span', { className: 'dsh-cron-type-tag', title: 'On success: triggers ' + task.onSuccess, style: { background: 'rgba(56, 189, 248, 0.15)', color: '#38bdf8' } }, '➜ ' + task.onSuccess) : null,
|
|
2181
|
+
task.onFailure ? React.createElement('span', { className: 'dsh-cron-type-tag', title: 'On failure: triggers ' + task.onFailure, style: { background: 'rgba(239, 68, 68, 0.15)', color: '#ef4444' } }, '↳ ' + task.onFailure) : null,
|
|
2182
|
+
task.heartbeatIntervalSeconds > 0 ? React.createElement('span', { className: 'dsh-cron-type-tag', title: 'Heartbeat: every ' + task.heartbeatIntervalSeconds + 's', style: { background: 'rgba(168, 85, 247, 0.15)', color: '#a855f7' } }, '💓 ' + task.heartbeatIntervalSeconds + 's') : null,
|
|
2183
|
+
task.preflightType && task.preflightType !== 'none' ? React.createElement('span', { className: 'dsh-cron-type-tag', title: 'Preflight: ' + task.preflightType, style: { background: 'rgba(234, 179, 8, 0.15)', color: '#eab308' } }, '🛡️ ' + task.preflightType) : null,
|
|
1835
2184
|
task.totalCostUsd > 0 ? React.createElement('span', { className: 'dsh-cron-cost-tag' }, '$' + task.totalCostUsd.toFixed(4)) : null,
|
|
1836
2185
|
task.totalTokens > 0 ? React.createElement('span', { className: 'dsh-cron-tokens-tag' }, (task.totalTokens > 1000 ? Math.round(task.totalTokens / 1000) + 'k' : task.totalTokens) + ' tok') : null
|
|
1837
2186
|
),
|
|
@@ -1851,6 +2200,18 @@ window.__ModuleLoader__.load({
|
|
|
1851
2200
|
className: 'dsh-cron-task-actions',
|
|
1852
2201
|
onClick: (e) => e.stopPropagation()
|
|
1853
2202
|
},
|
|
2203
|
+
React.createElement('button', {
|
|
2204
|
+
type: 'button',
|
|
2205
|
+
className: 'dsh-cron-icon-btn',
|
|
2206
|
+
title: T_KEY(t, 'actions.dryRunTitle'),
|
|
2207
|
+
onClick: () => handleDryRun(task.id)
|
|
2208
|
+
}, '🧪'),
|
|
2209
|
+
React.createElement('button', {
|
|
2210
|
+
type: 'button',
|
|
2211
|
+
className: 'dsh-cron-icon-btn',
|
|
2212
|
+
title: T_KEY(t, 'actions.archiveTitle'),
|
|
2213
|
+
onClick: () => openArchiveModal(task, 0)
|
|
2214
|
+
}, '📜'),
|
|
1854
2215
|
React.createElement('button', {
|
|
1855
2216
|
type: 'button',
|
|
1856
2217
|
className: 'dsh-cron-icon-btn',
|
|
@@ -2066,7 +2427,18 @@ window.__ModuleLoader__.load({
|
|
|
2066
2427
|
value: formSchedule,
|
|
2067
2428
|
placeholder: T_KEY(t, 'form.schedulePlaceholder'),
|
|
2068
2429
|
onChange: (e) => setFormSchedule(e.target.value)
|
|
2069
|
-
})
|
|
2430
|
+
}),
|
|
2431
|
+
React.createElement('div', { style: { display: 'flex', gap: '8px', alignItems: 'center', marginTop: '6px' } },
|
|
2432
|
+
React.createElement('button', {
|
|
2433
|
+
type: 'button',
|
|
2434
|
+
className: 'dsh-cron-btn-secondary',
|
|
2435
|
+
style: { padding: '3px 8px', fontSize: '11.5px' },
|
|
2436
|
+
onClick: () => handlePreviewSchedule(formSchedule)
|
|
2437
|
+
}, T_KEY(t, 'form.previewSchedule')),
|
|
2438
|
+
schedulePreviewRuns && schedulePreviewRuns.length > 0 && React.createElement('div', { style: { fontSize: '11px', color: 'var(--dsw-alias-label-secondary, #999)' } },
|
|
2439
|
+
T_KEY(t, 'form.previewScheduleTitle') + ' ' + schedulePreviewRuns.slice(0, 3).map(r => new Date(r).toLocaleTimeString()).join(', ')
|
|
2440
|
+
)
|
|
2441
|
+
)
|
|
2070
2442
|
)
|
|
2071
2443
|
),
|
|
2072
2444
|
React.createElement('div', { className: 'dsh-cron-form-row' },
|
|
@@ -2424,6 +2796,98 @@ window.__ModuleLoader__.load({
|
|
|
2424
2796
|
React.createElement('div', { className: 'dsh-cron-hint' }, T_KEY(t, 'form.silentRuleHint'))
|
|
2425
2797
|
)
|
|
2426
2798
|
),
|
|
2799
|
+
|
|
2800
|
+
// Reliability, Heartbeat & Self-Healing section
|
|
2801
|
+
React.createElement('div', {
|
|
2802
|
+
style: {
|
|
2803
|
+
marginTop: '16px',
|
|
2804
|
+
paddingTop: '14px',
|
|
2805
|
+
borderTop: '1px solid var(--dsw-alias-border-l2, #2e2e2e)'
|
|
2806
|
+
}
|
|
2807
|
+
},
|
|
2808
|
+
React.createElement('div', { style: { fontWeight: '600', fontSize: '13px', marginBottom: '10px' } }, T_KEY(t, 'form.reliabilityTitle')),
|
|
2809
|
+
React.createElement('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '10px' } },
|
|
2810
|
+
React.createElement('div', { className: 'dsh-cron-form-group' },
|
|
2811
|
+
React.createElement('label', null, T_KEY(t, 'form.preflightLabel')),
|
|
2812
|
+
React.createElement('select', {
|
|
2813
|
+
value: formPreflightType,
|
|
2814
|
+
onChange: (e) => setFormPreflightType(e.target.value)
|
|
2815
|
+
},
|
|
2816
|
+
React.createElement('option', { value: 'none' }, T_KEY(t, 'form.preflightNone')),
|
|
2817
|
+
React.createElement('option', { value: 'http' }, T_KEY(t, 'form.preflightHttp')),
|
|
2818
|
+
React.createElement('option', { value: 'shell' }, T_KEY(t, 'form.preflightShell')),
|
|
2819
|
+
React.createElement('option', { value: 'disk' }, T_KEY(t, 'form.preflightDisk'))
|
|
2820
|
+
)
|
|
2821
|
+
),
|
|
2822
|
+
formPreflightType !== 'none' && React.createElement('div', { className: 'dsh-cron-form-group' },
|
|
2823
|
+
React.createElement('label', null, T_KEY(t, 'form.preflightTargetLabel')),
|
|
2824
|
+
React.createElement('input', {
|
|
2825
|
+
type: 'text',
|
|
2826
|
+
value: formPreflightTarget,
|
|
2827
|
+
placeholder: T_KEY(t, 'form.preflightTargetPlaceholder'),
|
|
2828
|
+
onChange: (e) => setFormPreflightTarget(e.target.value)
|
|
2829
|
+
})
|
|
2830
|
+
)
|
|
2831
|
+
),
|
|
2832
|
+
React.createElement('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '10px' } },
|
|
2833
|
+
React.createElement('div', { className: 'dsh-cron-form-group' },
|
|
2834
|
+
React.createElement('label', null, T_KEY(t, 'form.heartbeatIntervalLabel')),
|
|
2835
|
+
React.createElement('input', {
|
|
2836
|
+
type: 'number',
|
|
2837
|
+
value: formHeartbeatIntervalSeconds,
|
|
2838
|
+
min: 0,
|
|
2839
|
+
onChange: (e) => setFormHeartbeatIntervalSeconds(Number(e.target.value))
|
|
2840
|
+
})
|
|
2841
|
+
),
|
|
2842
|
+
formHeartbeatIntervalSeconds > 0 && React.createElement('div', { className: 'dsh-cron-form-group' },
|
|
2843
|
+
React.createElement('label', null, T_KEY(t, 'form.gracePeriodLabel')),
|
|
2844
|
+
React.createElement('input', {
|
|
2845
|
+
type: 'number',
|
|
2846
|
+
value: formGracePeriodSeconds,
|
|
2847
|
+
min: 1,
|
|
2848
|
+
onChange: (e) => setFormGracePeriodSeconds(Number(e.target.value))
|
|
2849
|
+
})
|
|
2850
|
+
)
|
|
2851
|
+
),
|
|
2852
|
+
React.createElement('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '10px' } },
|
|
2853
|
+
React.createElement('div', { className: 'dsh-cron-form-group' },
|
|
2854
|
+
React.createElement('label', null, T_KEY(t, 'form.priorityLabel')),
|
|
2855
|
+
React.createElement('input', {
|
|
2856
|
+
type: 'number',
|
|
2857
|
+
value: formPriority,
|
|
2858
|
+
min: 1,
|
|
2859
|
+
max: 10,
|
|
2860
|
+
onChange: (e) => setFormPriority(Number(e.target.value))
|
|
2861
|
+
})
|
|
2862
|
+
),
|
|
2863
|
+
React.createElement('div', { className: 'dsh-cron-form-group' },
|
|
2864
|
+
React.createElement('label', null, T_KEY(t, 'form.concurrencyGroupLabel')),
|
|
2865
|
+
React.createElement('input', {
|
|
2866
|
+
type: 'text',
|
|
2867
|
+
value: formConcurrencyGroup,
|
|
2868
|
+
onChange: (e) => setFormConcurrencyGroup(e.target.value)
|
|
2869
|
+
})
|
|
2870
|
+
)
|
|
2871
|
+
),
|
|
2872
|
+
React.createElement('div', { className: 'dsh-cron-form-group' },
|
|
2873
|
+
React.createElement('label', null, T_KEY(t, 'form.selfHealingLabel')),
|
|
2874
|
+
React.createElement('input', {
|
|
2875
|
+
type: 'text',
|
|
2876
|
+
value: formSelfHealingCommand,
|
|
2877
|
+
placeholder: T_KEY(t, 'form.selfHealingPlaceholder'),
|
|
2878
|
+
onChange: (e) => setFormSelfHealingCommand(e.target.value)
|
|
2879
|
+
})
|
|
2880
|
+
),
|
|
2881
|
+
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: '8px', marginTop: '6px' } },
|
|
2882
|
+
React.createElement('input', {
|
|
2883
|
+
type: 'checkbox',
|
|
2884
|
+
id: 'formAutoDiagnoseCheckbox',
|
|
2885
|
+
checked: formAutoDiagnose,
|
|
2886
|
+
onChange: (e) => setFormAutoDiagnose(e.target.checked)
|
|
2887
|
+
}),
|
|
2888
|
+
React.createElement('label', { htmlFor: 'formAutoDiagnoseCheckbox', style: { margin: 0, fontSize: '12.5px', cursor: 'pointer' } }, T_KEY(t, 'form.autoDiagnoseLabel'))
|
|
2889
|
+
)
|
|
2890
|
+
),
|
|
2427
2891
|
selectedTaskMeta && React.createElement('div', {
|
|
2428
2892
|
style: {
|
|
2429
2893
|
background: 'var(--dsw-alias-bg-layer-3, rgba(255,255,255,0.03))',
|
|
@@ -2467,7 +2931,123 @@ window.__ModuleLoader__.load({
|
|
|
2467
2931
|
)
|
|
2468
2932
|
),
|
|
2469
2933
|
|
|
2470
|
-
|
|
2934
|
+
|
|
2935
|
+
// Dry Run simulation modal
|
|
2936
|
+
dryRunResult && React.createElement('div', { className: 'dsh-cron-modal-overlay', onClick: () => setDryRunResult(null) },
|
|
2937
|
+
React.createElement('div', {
|
|
2938
|
+
className: 'dsh-cron-modal',
|
|
2939
|
+
style: { maxWidth: '580px' },
|
|
2940
|
+
role: 'dialog',
|
|
2941
|
+
'aria-modal': 'true',
|
|
2942
|
+
onClick: (e) => e.stopPropagation()
|
|
2943
|
+
},
|
|
2944
|
+
React.createElement('div', { className: 'dsh-cron-modal-head', style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' } },
|
|
2945
|
+
React.createElement('div', { className: 'dsh-cron-modal-title', style: { margin: 0 } }, T_KEY(t, 'dryRun.title')),
|
|
2946
|
+
React.createElement('button', {
|
|
2947
|
+
type: 'button',
|
|
2948
|
+
className: 'dsh-cron-icon-btn',
|
|
2949
|
+
onClick: () => setDryRunResult(null)
|
|
2950
|
+
}, '✕')
|
|
2951
|
+
),
|
|
2952
|
+
React.createElement('div', { style: { marginTop: '12px', display: 'flex', flexDirection: 'column', gap: '8px', fontSize: '13px' } },
|
|
2953
|
+
React.createElement('div', null, React.createElement('strong', null, T_KEY(t, 'dryRun.status') + ': '), React.createElement('span', { style: { color: dryRunResult.ok ? 'var(--dsh-cron-success, #22c55e)' : 'var(--dsh-cron-danger, #ef4444)' } }, dryRunResult.ok ? (dryRunResult.result && dryRunResult.result.status || 'success') : 'failed')),
|
|
2954
|
+
dryRunResult.result && dryRunResult.result.preflight && React.createElement('div', null, React.createElement('strong', null, T_KEY(t, 'dryRun.preflight') + ': '), React.createElement('span', null, dryRunResult.result.preflight.passed ? '✓ Passed' : '✗ Failed (' + (dryRunResult.result.preflight.reason || '') + ')')),
|
|
2955
|
+
dryRunResult.result && React.createElement('div', null, React.createElement('strong', null, T_KEY(t, 'dryRun.duration') + ': '), React.createElement('span', null, (dryRunResult.result.durationMs || 0) + 'ms')),
|
|
2956
|
+
React.createElement('div', { style: { marginTop: '6px' } }, React.createElement('strong', null, T_KEY(t, 'dryRun.output') + ':')),
|
|
2957
|
+
React.createElement('pre', {
|
|
2958
|
+
style: {
|
|
2959
|
+
background: 'var(--dsw-alias-bg-layer-2, #18181b)',
|
|
2960
|
+
border: '1px solid var(--dsw-alias-border-l2, #2e2e2e)',
|
|
2961
|
+
borderRadius: '6px',
|
|
2962
|
+
padding: '10px',
|
|
2963
|
+
maxHeight: '220px',
|
|
2964
|
+
overflowY: 'auto',
|
|
2965
|
+
fontSize: '12px',
|
|
2966
|
+
whiteSpace: 'pre-wrap',
|
|
2967
|
+
wordBreak: 'break-word',
|
|
2968
|
+
color: 'var(--dsw-alias-label-primary, #e4e4e7)'
|
|
2969
|
+
}
|
|
2970
|
+
}, (dryRunResult.result && dryRunResult.result.output) || (dryRunResult.error) || '(no output)')
|
|
2971
|
+
),
|
|
2972
|
+
React.createElement('div', { className: 'dsh-cron-modal-foot', style: { marginTop: '16px' } },
|
|
2973
|
+
React.createElement('button', {
|
|
2974
|
+
type: 'button',
|
|
2975
|
+
className: 'dsh-cron-btn-primary',
|
|
2976
|
+
onClick: () => setDryRunResult(null)
|
|
2977
|
+
}, T_KEY(t, 'dryRun.close'))
|
|
2978
|
+
)
|
|
2979
|
+
)
|
|
2980
|
+
),
|
|
2981
|
+
|
|
2982
|
+
// Archive runs modal
|
|
2983
|
+
archiveModalOpen && React.createElement('div', { className: 'dsh-cron-modal-overlay', onClick: () => setArchiveModalOpen(false) },
|
|
2984
|
+
React.createElement('div', {
|
|
2985
|
+
className: 'dsh-cron-modal',
|
|
2986
|
+
style: { maxWidth: '700px' },
|
|
2987
|
+
role: 'dialog',
|
|
2988
|
+
'aria-modal': 'true',
|
|
2989
|
+
onClick: (e) => e.stopPropagation()
|
|
2990
|
+
},
|
|
2991
|
+
React.createElement('div', { className: 'dsh-cron-modal-head', style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' } },
|
|
2992
|
+
React.createElement('div', { className: 'dsh-cron-modal-title', style: { margin: 0 } }, T_KEY(t, 'archive.title') + (archiveTask ? ' - ' + archiveTask.title : '')),
|
|
2993
|
+
React.createElement('button', {
|
|
2994
|
+
type: 'button',
|
|
2995
|
+
className: 'dsh-cron-icon-btn',
|
|
2996
|
+
onClick: () => setArchiveModalOpen(false)
|
|
2997
|
+
}, '✕')
|
|
2998
|
+
),
|
|
2999
|
+
React.createElement('div', { style: { fontSize: '12px', color: 'var(--dsw-alias-label-secondary, #999)', margin: '4px 0 12px 0' } },
|
|
3000
|
+
T_KEY(t, 'archive.totalRuns', { total: archiveTotal })
|
|
3001
|
+
),
|
|
3002
|
+
archiveLoading ? React.createElement('div', { className: 'dsh-cron-history-empty' }, T_KEY(t, 'history.loading')) :
|
|
3003
|
+
archiveRuns.length === 0 ? React.createElement('div', { className: 'dsh-cron-history-empty' }, T_KEY(t, 'archive.empty')) :
|
|
3004
|
+
React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: '8px', maxHeight: '380px', overflowY: 'auto' } },
|
|
3005
|
+
archiveRuns.map((r, idx) => React.createElement('div', {
|
|
3006
|
+
key: idx,
|
|
3007
|
+
style: {
|
|
3008
|
+
background: 'var(--dsw-alias-bg-layer-2, #18181b)',
|
|
3009
|
+
border: '1px solid var(--dsw-alias-border-l2, #2e2e2e)',
|
|
3010
|
+
borderRadius: '6px',
|
|
3011
|
+
padding: '8px 12px',
|
|
3012
|
+
fontSize: '12px'
|
|
3013
|
+
}
|
|
3014
|
+
},
|
|
3015
|
+
React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', marginBottom: '4px' } },
|
|
3016
|
+
React.createElement('span', { style: { fontWeight: '600', color: r.status === 'success' ? 'var(--dsh-cron-success, #22c55e)' : 'var(--dsh-cron-danger, #ef4444)' } }, (r.status || 'unknown').toUpperCase()),
|
|
3017
|
+
React.createElement('span', { style: { color: 'var(--dsw-alias-label-secondary, #888)' } }, new Date(r.timestamp || r.startedAt || Date.now()).toLocaleString())
|
|
3018
|
+
),
|
|
3019
|
+
r.durationMs > 0 && React.createElement('div', { style: { color: 'var(--dsw-alias-label-secondary, #888)', marginBottom: '4px' } }, 'Duration: ' + r.durationMs + 'ms'),
|
|
3020
|
+
r.error && React.createElement('div', { style: { color: 'var(--dsh-cron-danger, #ef4444)', marginBottom: '4px' } }, 'Error: ' + r.error),
|
|
3021
|
+
r.output && React.createElement('div', {
|
|
3022
|
+
style: {
|
|
3023
|
+
color: 'var(--dsw-alias-label-primary, #ccc)',
|
|
3024
|
+
whiteSpace: 'pre-wrap',
|
|
3025
|
+
wordBreak: 'break-word',
|
|
3026
|
+
maxHeight: '80px',
|
|
3027
|
+
overflowY: 'auto'
|
|
3028
|
+
}
|
|
3029
|
+
}, r.output.slice(0, 300) + (r.output.length > 300 ? '...' : ''))
|
|
3030
|
+
))
|
|
3031
|
+
),
|
|
3032
|
+
React.createElement('div', { className: 'dsh-cron-modal-foot', style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginTop: '16px' } },
|
|
3033
|
+
archiveRuns.length < archiveTotal ? React.createElement('button', {
|
|
3034
|
+
type: 'button',
|
|
3035
|
+
className: 'dsh-cron-btn-secondary',
|
|
3036
|
+
onClick: () => {
|
|
3037
|
+
const nextOffset = archiveOffset + 15;
|
|
3038
|
+
if (archiveTask) openArchiveModal(archiveTask, nextOffset);
|
|
3039
|
+
}
|
|
3040
|
+
}, T_KEY(t, 'archive.loadMore', { shown: archiveRuns.length, total: archiveTotal })) : React.createElement('div', null),
|
|
3041
|
+
React.createElement('button', {
|
|
3042
|
+
type: 'button',
|
|
3043
|
+
className: 'dsh-cron-btn-primary',
|
|
3044
|
+
onClick: () => setArchiveModalOpen(false)
|
|
3045
|
+
}, T_KEY(t, 'archive.close'))
|
|
3046
|
+
)
|
|
3047
|
+
)
|
|
3048
|
+
),
|
|
3049
|
+
|
|
3050
|
+
// 2. Settings modal
|
|
2471
3051
|
settingsModalOpen && React.createElement('div', { className: 'dsh-cron-modal-overlay', onClick: () => setSettingsModalOpen(false) },
|
|
2472
3052
|
React.createElement('div', { className: 'dsh-cron-modal dsh-cron-modal-scroll', role: 'dialog', 'aria-modal': 'true', onClick: (e) => e.stopPropagation() },
|
|
2473
3053
|
React.createElement('div', { className: 'dsh-cron-modal-head', style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '18px' } },
|
|
@@ -2653,6 +3233,7 @@ window.__ModuleLoader__.load({
|
|
|
2653
3233
|
const [chatId, setChatId] = React.useState('');
|
|
2654
3234
|
const [notify, setNotify] = React.useState(false);
|
|
2655
3235
|
const [onlyFailure, setOnlyFailure] = React.useState(false);
|
|
3236
|
+
const [llmActionsEnabled, setLlmActionsEnabled] = React.useState(false);
|
|
2656
3237
|
const [kanbanUrl, setKanbanUrl] = React.useState('http://127.0.0.1:3000');
|
|
2657
3238
|
const [delivery, setDelivery] = React.useState({});
|
|
2658
3239
|
const [sections, setSections] = React.useState({ channels: true });
|
|
@@ -2679,6 +3260,7 @@ window.__ModuleLoader__.load({
|
|
|
2679
3260
|
setChatId(data.settings.chatId || '');
|
|
2680
3261
|
setNotify(Boolean(data.settings.notifyTelegram));
|
|
2681
3262
|
setOnlyFailure(Boolean(data.settings.onlyOnFailure));
|
|
3263
|
+
setLlmActionsEnabled(Boolean(data.settings.llmActionsEnabled));
|
|
2682
3264
|
setKanbanUrl(data.settings.kanbanBaseUrl || 'http://127.0.0.1:3000');
|
|
2683
3265
|
setDelivery(pickDeliverySettings(data.settings));
|
|
2684
3266
|
setHasDefault(Boolean(data.settings.hasDefaultCredentials));
|
|
@@ -2702,6 +3284,7 @@ window.__ModuleLoader__.load({
|
|
|
2702
3284
|
chatId,
|
|
2703
3285
|
notifyTelegram: notify,
|
|
2704
3286
|
onlyOnFailure: onlyFailure,
|
|
3287
|
+
llmActionsEnabled: llmActionsEnabled,
|
|
2705
3288
|
kanbanBaseUrl: kanbanUrl
|
|
2706
3289
|
}, delivery))
|
|
2707
3290
|
});
|
|
@@ -2831,6 +3414,14 @@ window.__ModuleLoader__.load({
|
|
|
2831
3414
|
onChange: (e) => setOnlyFailure(e.target.checked)
|
|
2832
3415
|
}),
|
|
2833
3416
|
React.createElement('span', null, T_KEY(t, 'settings.globalOnlyFailure'))
|
|
3417
|
+
),
|
|
3418
|
+
React.createElement('label', { className: 'dsh-cron-checkbox-row' },
|
|
3419
|
+
React.createElement('input', {
|
|
3420
|
+
type: 'checkbox',
|
|
3421
|
+
checked: llmActionsEnabled,
|
|
3422
|
+
onChange: (e) => setLlmActionsEnabled(e.target.checked)
|
|
3423
|
+
}),
|
|
3424
|
+
React.createElement('span', null, 'Enable Structured LLM Actions (trigger tasks, notify, create issues)')
|
|
2834
3425
|
)
|
|
2835
3426
|
),
|
|
2836
3427
|
...renderDeliverySettings({
|