@goodandready/dsh-cron 0.1.23 → 0.2.0
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 +117 -82
- package/docs/README.ru.md +133 -85
- package/docs/README.zh.md +140 -82
- package/docs/design/DESIGN.md +44 -35
- package/lib/chat-start.js +2 -2
- package/lib/client.js +668 -412
- package/lib/http-utils.js +81 -0
- package/lib/index.js +342 -384
- package/lib/integrations.js +0 -1
- package/lib/prompt.js +42 -40
- package/lib/runner.js +70 -39
- package/lib/scheduler.js +47 -31
- package/lib/store.js +5 -2
- package/lib/telegram.js +27 -13
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -3,10 +3,14 @@ window.__ModuleLoader__.load({
|
|
|
3
3
|
factory: (require) => {
|
|
4
4
|
var module = { exports: {} }
|
|
5
5
|
var exports = module.exports
|
|
6
|
-
const React = window.React || require('react');
|
|
6
|
+
const React = window.React || require('react');
|
|
7
7
|
const ReactDOM = window.ReactDOM || require('react-dom/client');
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
// Settings namespace, locale namespace and plugin marker share the same
|
|
10
|
+
// canonical identifier — the settings.plugin.item slot key MUST match the
|
|
11
|
+
// namespace registered on the server ('dsh-cron', Issue #85).
|
|
12
|
+
const PLUGIN_ID = '@goodandready/dsh-cron';
|
|
13
|
+
const NS = 'dsh-cron';
|
|
10
14
|
const PANEL = 'cron';
|
|
11
15
|
const ACTIVATE_EVENT = 'dsh-panel-activate';
|
|
12
16
|
const ACTIVE_ATTR = 'data-dsh-cron-active';
|
|
@@ -14,23 +18,173 @@ const React = window.React || require('react');
|
|
|
14
18
|
const VIEW_ATTR = 'data-dsh-cron-view';
|
|
15
19
|
const COLUMN_SELECTOR = '[data-pane=conversation], [class*=centerCol]';
|
|
16
20
|
const SESSION_ROW_SELECTOR = '[class*="sessionRow"], [class*="projectRow"], [class*="searchResultRow"], [class*="newSession"]';
|
|
21
|
+
const SCRIPT_CONFIRM_HEADER = 'x-dsh-cron-confirm';
|
|
17
22
|
|
|
18
23
|
const BACK_ICON = '<svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10 3.5 5.5 8l4.5 4.5"/></svg>';
|
|
19
|
-
const ICON_TIMER = '<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg>';
|
|
20
|
-
const ICON_PLAY = '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="5 3 19 12 5 21 5 21"></polygon></svg>';
|
|
21
|
-
const ICON_PAUSE = '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="6" y="4" width="4" height="16"></rect><rect x="14" y="4" width="4" height="16"></rect></svg>';
|
|
22
|
-
const ICON_TRASH = '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path></svg>';
|
|
23
|
-
const ICON_SEARCH = '<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>';
|
|
24
|
-
const ICON_CHEVRON_DOWN = '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>';
|
|
25
|
-
const ICON_BELL = '<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"></path><path d="M13.73 21a2 2 0 0 1-3.46 0"></path></svg>';
|
|
26
|
-
const ICON_CLIPBOARD = '<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"></path><rect x="8" y="2" width="8" height="4" rx="1" ry="1"></rect></svg>';
|
|
27
|
-
const ICON_ACTIVITY = '<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"></polyline></svg>';
|
|
28
|
-
const ICON_EDIT = '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path></svg>';
|
|
29
|
-
const ICON_SEND = '<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"></line><polygon points="22 2 15 22 11 13 2 9 22 2"></polygon></svg>';
|
|
30
|
-
const
|
|
31
|
-
|
|
24
|
+
const ICON_TIMER = '<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg>';
|
|
25
|
+
const ICON_PLAY = '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polygon points="5 3 19 12 5 21 5 21"></polygon></svg>';
|
|
26
|
+
const ICON_PAUSE = '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="6" y="4" width="4" height="16"></rect><rect x="14" y="4" width="4" height="16"></rect></svg>';
|
|
27
|
+
const ICON_TRASH = '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path></svg>';
|
|
28
|
+
const ICON_SEARCH = '<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>';
|
|
29
|
+
const ICON_CHEVRON_DOWN = '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="6 9 12 15 18 9"></polyline></svg>';
|
|
30
|
+
const ICON_BELL = '<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"></path><path d="M13.73 21a2 2 0 0 1-3.46 0"></path></svg>';
|
|
31
|
+
const ICON_CLIPBOARD = '<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"></path><rect x="8" y="2" width="8" height="4" rx="1" ry="1"></rect></svg>';
|
|
32
|
+
const ICON_ACTIVITY = '<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"></polyline></svg>';
|
|
33
|
+
const ICON_EDIT = '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path></svg>';
|
|
34
|
+
const ICON_SEND = '<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="22" y1="2" x2="11" y2="13"></line><polygon points="22 2 15 22 11 13 2 9 22 2"></polygon></svg>';
|
|
35
|
+
const ICON_TELEGRAM = '<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor" aria-hidden="true"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm4.64 6.8-1.7 8.01c-.13.58-.47.72-.95.45l-2.6-1.92-1.25 1.21c-.14.14-.26.26-.53.26l.19-2.64 4.81-4.35c.21-.19-.05-.29-.32-.11L8.34 13.5 5.78 12.7c-.56-.17-.57-.56.12-.83l10-3.86c.46-.17.87.11.74.79z"/></svg>';
|
|
36
|
+
|
|
37
|
+
// Locale dictionary: English is the canonical source language (Issue #87).
|
|
38
|
+
// Russian and other languages are provided at runtime by the translation
|
|
39
|
+
// plugin; no per-language duplicates are stored here.
|
|
40
|
+
const STRINGS = {
|
|
41
|
+
en: {
|
|
42
|
+
'sidebar.label': 'Scheduled tasks',
|
|
43
|
+
'chip.title': 'Scheduled tasks (Cron)',
|
|
44
|
+
'panel.back': 'Back to chat',
|
|
45
|
+
'panel.title': 'Scheduled tasks',
|
|
46
|
+
'panel.subtitle': 'Ask DSH to plan tasks, set reminders or track updates',
|
|
47
|
+
'actions.create': 'Create',
|
|
48
|
+
'actions.createWithDsh': '💬 Create with DSH',
|
|
49
|
+
'actions.setupManually': '✏️ Set up manually',
|
|
50
|
+
'actions.runNow': 'Run now',
|
|
51
|
+
'actions.runNowShort': 'Run',
|
|
52
|
+
'actions.edit': 'Edit / Details',
|
|
53
|
+
'actions.delete': 'Delete task',
|
|
54
|
+
'actions.pause': 'Pause',
|
|
55
|
+
'actions.resume': 'Resume',
|
|
56
|
+
'actions.settings': 'Telegram & Notifications',
|
|
57
|
+
'search.placeholder': 'Search scheduled tasks',
|
|
58
|
+
'stats.activeTasks': 'Active tasks',
|
|
59
|
+
'stats.totalRuns': 'Total runs',
|
|
60
|
+
'stats.totalTokens': 'Tokens spent',
|
|
61
|
+
'stats.totalCost': 'Estimated cost',
|
|
62
|
+
'tabs.all': 'All',
|
|
63
|
+
'tabs.active': 'Active',
|
|
64
|
+
'tabs.paused': 'Paused',
|
|
65
|
+
'tabs.completed': 'Completed',
|
|
66
|
+
'banner.loadError': 'Failed to load tasks: {error}',
|
|
67
|
+
'banner.retry': 'Retry',
|
|
68
|
+
'list.loading': 'Loading scheduled tasks...',
|
|
69
|
+
'list.empty': 'No scheduled tasks yet',
|
|
70
|
+
'list.typeShell': 'Shell',
|
|
71
|
+
'list.typeLlm': 'LLM',
|
|
72
|
+
'list.oneShot': 'One-shot',
|
|
73
|
+
'list.next': ' · next: {time}',
|
|
74
|
+
'recs.title': 'Recommended tasks',
|
|
75
|
+
'modal.editTitle': 'Edit task',
|
|
76
|
+
'modal.newTitle': 'New scheduled task',
|
|
77
|
+
'modal.statusActive': '● Active',
|
|
78
|
+
'modal.statusPaused': '○ Paused',
|
|
79
|
+
'modal.params': 'Parameters',
|
|
80
|
+
'modal.history': 'Run history ({count})',
|
|
81
|
+
'modal.cancel': 'Cancel',
|
|
82
|
+
'modal.save': 'Save changes',
|
|
83
|
+
'modal.create': 'Create task',
|
|
84
|
+
'modal.close': 'Close',
|
|
85
|
+
'history.loading': 'Loading history...',
|
|
86
|
+
'history.empty': 'No runs recorded yet',
|
|
87
|
+
'history.refresh': 'Refresh history',
|
|
88
|
+
'history.statusSuccess': 'Success',
|
|
89
|
+
'history.statusSkipped': 'Skipped',
|
|
90
|
+
'history.statusMissed': 'Missed',
|
|
91
|
+
'history.statusTimeout': 'Timeout',
|
|
92
|
+
'history.statusFailed': 'Failed',
|
|
93
|
+
'form.nameLabel': 'Task name',
|
|
94
|
+
'form.namePlaceholder': 'For example: dsh-tags-watch',
|
|
95
|
+
'form.typeLabel': 'Execution type',
|
|
96
|
+
'form.typeLlm': 'LLM (AI agent)',
|
|
97
|
+
'form.typeScript': 'NO-LLM (Shell command/script)',
|
|
98
|
+
'form.scheduleLabel': 'Schedule (cron or interval)',
|
|
99
|
+
'form.schedulePlaceholder': '0 */6 * * * or every 2h or in 30m or at: 2026-09-05T15:00:00Z',
|
|
100
|
+
'form.timeoutLabel': 'Execution timeout (seconds)',
|
|
101
|
+
'form.overlapLabel': 'Overlap policy',
|
|
102
|
+
'form.overlapSkip': 'Skip the new run (skip)',
|
|
103
|
+
'form.overlapQueue': 'Queue the next run (queue)',
|
|
104
|
+
'form.overlapReplace': 'Cancel current run and restart (replace)',
|
|
105
|
+
'form.kanbanLabel': 'Kanban card creation (dsh-kanban)',
|
|
106
|
+
'form.kanbanNone': 'Do not create cards (disabled)',
|
|
107
|
+
'form.kanbanOnFailure': 'Create a ticket on failure (into Backlog)',
|
|
108
|
+
'form.kanbanAlways': 'Create a card on every completion (Done / Backlog)',
|
|
109
|
+
'form.providerLabel': 'LLM provider',
|
|
110
|
+
'form.providerDefault': 'Default (DSH)',
|
|
111
|
+
'form.modelLabel': 'LLM model',
|
|
112
|
+
'form.modelDefault': 'Default',
|
|
113
|
+
'form.promptLabelScript': 'Shell command or script',
|
|
114
|
+
'form.promptLabelLlm': 'Instruction / Prompt for the agent',
|
|
115
|
+
'form.promptPlaceholderScript': 'curl -fsSL https://... || exit 1',
|
|
116
|
+
'form.promptPlaceholderLlm': 'Describe exactly what the agent should do...',
|
|
117
|
+
'form.notifyTitle': 'Telegram notifications',
|
|
118
|
+
'form.notifyTelegram': 'Send the run report to Telegram',
|
|
119
|
+
'form.notifyOnlyFailure': 'Only on failures (onlyOnFailure / silent on success)',
|
|
120
|
+
'form.nextRunLabel': 'Next run: ',
|
|
121
|
+
'form.notScheduled': 'not scheduled',
|
|
122
|
+
'settings.sectionLabel': 'Cron tasks',
|
|
123
|
+
'settings.cardTitle': '⏰ Task scheduler (Cron)',
|
|
124
|
+
'settings.cardDesc': 'Scheduled task execution (cron/intervals), background agent scenarios and Telegram/Kanban integration.',
|
|
125
|
+
'settings.openPanel': 'Open task panel',
|
|
126
|
+
'settings.modalTitle': 'Telegram & notification settings',
|
|
127
|
+
'settings.tokenLabel': 'Telegram Bot Token',
|
|
128
|
+
'settings.tokenPlaceholder': '123456789:ABCdefGhIJKlmNoPQRsTUVwxyZ',
|
|
129
|
+
'settings.tokenPlaceholderDefault': 'Using the bot configured in DSH settings (or enter your own)',
|
|
130
|
+
'settings.defaultHint': '✓ A configured bot was found in the DSH profile',
|
|
131
|
+
'settings.chatIdLabel': 'Telegram Chat ID',
|
|
132
|
+
'settings.chatIdPlaceholder': 'For example: 345678901',
|
|
133
|
+
'settings.kanbanUrlLabel': 'Kanban Base URL',
|
|
134
|
+
'settings.globalNotify': 'Deliver reports of all tasks to Telegram globally',
|
|
135
|
+
'settings.globalOnlyFailure': 'Global mode: only on failures (silent on success)',
|
|
136
|
+
'settings.testTelegram': '🔔 Test Telegram delivery',
|
|
137
|
+
'settings.testKanban': '📋 Test Kanban',
|
|
138
|
+
'settings.sending': 'Sending...',
|
|
139
|
+
'settings.checkingKanban': 'Checking Kanban...',
|
|
140
|
+
'settings.testOk': '✅ Test message delivered to Telegram!',
|
|
141
|
+
'settings.testFail': '❌ {error}',
|
|
142
|
+
'settings.kanbanOk': '✅ Kanban card created',
|
|
143
|
+
'settings.kanbanFail': '❌ {error}',
|
|
144
|
+
'settings.save': 'Save settings',
|
|
145
|
+
'settings.saved': '✓ Saved',
|
|
146
|
+
'settings.saveError': 'Save failed: {error}',
|
|
147
|
+
'settings.saveErrorUnknown': 'unknown error',
|
|
148
|
+
'dsh.modalTitle': 'Schedule a task with the DSH agent',
|
|
149
|
+
'dsh.descLabel': 'Describe the task and the desired frequency:',
|
|
150
|
+
'dsh.placeholder': 'For example: check free disk space and failed systemd services every 2 hours, and alert only if something is wrong...',
|
|
151
|
+
'dsh.sendHint': 'Ctrl+Enter to send',
|
|
152
|
+
'dsh.sendTitle': 'Send to agent',
|
|
153
|
+
'dsh.startFailed': 'Failed to start the agent dialog: {error}',
|
|
154
|
+
'dsh.launchError': 'Launch failed: {error}',
|
|
155
|
+
'confirm.delete': 'Delete this task?',
|
|
156
|
+
'list.loadErrorPrefix': 'Failed to load tasks'
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
// Module-level translator. apply() rebinds it to the DSH locale service so
|
|
161
|
+
// the active language and live language switches are honored.
|
|
162
|
+
let translate = (key, vars) => {
|
|
163
|
+
let s = (STRINGS.en && STRINGS.en[key]) || key;
|
|
164
|
+
if (vars) {
|
|
165
|
+
for (const k of Object.keys(vars)) {
|
|
166
|
+
s = s.split('{' + k + '}').join(String(vars[k]));
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return s;
|
|
170
|
+
};
|
|
171
|
+
const T = (key, vars) => translate(key, vars);
|
|
32
172
|
|
|
173
|
+
// Status colors: core semantic tokens first, plugin fallbacks as a single
|
|
174
|
+
// point of change (Issue #91 — no scattered hardcoded hex).
|
|
33
175
|
const STYLES = `
|
|
176
|
+
:root {
|
|
177
|
+
--dsh-cron-success: var(--dsw-alias-label-success, #10b981);
|
|
178
|
+
--dsh-cron-success-bg: var(--dsw-alias-bg-success, rgba(16, 185, 129, 0.15));
|
|
179
|
+
--dsh-cron-danger: var(--dsw-alias-label-danger, #ef4444);
|
|
180
|
+
--dsh-cron-danger-bg: var(--dsw-alias-bg-danger, rgba(239, 68, 68, 0.15));
|
|
181
|
+
--dsh-cron-info: var(--dsw-alias-label-info, #60a5fa);
|
|
182
|
+
--dsh-cron-info-bg: var(--dsw-alias-bg-info, rgba(59, 130, 246, 0.15));
|
|
183
|
+
--dsh-cron-accent: var(--dsw-alias-label-accent, #c084fc);
|
|
184
|
+
--dsh-cron-accent-bg: var(--dsw-alias-bg-accent, rgba(168, 85, 247, 0.15));
|
|
185
|
+
--dsh-cron-warning: var(--dsw-alias-label-warning, #eab308);
|
|
186
|
+
--dsh-cron-warning-bg: var(--dsw-alias-bg-warning, rgba(234, 179, 8, 0.15));
|
|
187
|
+
}
|
|
34
188
|
[data-pane=conversation], [class*=centerCol] {
|
|
35
189
|
position: relative !important;
|
|
36
190
|
}
|
|
@@ -56,7 +210,7 @@ const React = window.React || require('react');
|
|
|
56
210
|
|
|
57
211
|
.dsh-cron-overlay { position: absolute; inset: 0; display: flex; flex-direction: column; background: var(--dsw-alias-bg-base, #171717); color: var(--dsw-alias-label-primary, #ededed); overflow-y: auto; padding: 32px 48px; z-index: 100; box-sizing: border-box; }
|
|
58
212
|
.dsh-cron-container { max-width: 860px; width: 100%; margin: 0 auto; }
|
|
59
|
-
|
|
213
|
+
|
|
60
214
|
.dsh-cron-top-bar { display: flex; align-items: center; gap: 12px; margin-bottom: 20px; }
|
|
61
215
|
.dsh-cron-back-btn { appearance: none; font: inherit; cursor: pointer; color: var(--dsw-alias-label-secondary, #9ca3af); background: 0 0; border: 1px solid var(--dsw-alias-border-l2, #383838); 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; }
|
|
62
216
|
.dsh-cron-back-btn:hover { color: var(--dsw-alias-label-primary, #ededed); background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,0.06)); }
|
|
@@ -64,61 +218,57 @@ const React = window.React || require('react');
|
|
|
64
218
|
.dsh-cron-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 24px; }
|
|
65
219
|
.dsh-cron-title { font-size: 24px; font-weight: 600; letter-spacing: -0.02em; margin-bottom: 6px; }
|
|
66
220
|
.dsh-cron-subtitle { font-size: 14px; color: var(--dsw-alias-label-secondary, #9ca3af); }
|
|
67
|
-
.dsh-cron-create-btn { appearance: none; display: inline-flex; align-items: center; gap: 6px; background: var(--dsw-alias-bg-layer-4, #262626); color: #fff; border: 1px solid var(--dsw-alias-border-l2, #383838); border-radius: 9999px; padding: 8px 16px; font-size: 14px; font-weight: 500; cursor: pointer; position: relative; }
|
|
68
|
-
.dsh-cron-create-btn:hover { background: var(--dsw-alias-bg-layer-hover, #333); }
|
|
221
|
+
.dsh-cron-create-btn { appearance: none; display: inline-flex; align-items: center; gap: 6px; background: var(--dsw-alias-bg-layer-4, #262626); color: var(--dsw-alias-label-primary, #fff); border: 1px solid var(--dsw-alias-border-l2, #383838); border-radius: 9999px; padding: 8px 16px; font-size: 14px; font-weight: 500; cursor: pointer; position: relative; }
|
|
222
|
+
.dsh-cron-create-btn:hover { background: var(--dsw-alias-bg-layer-hover, var(--dsw-alias-interactive-bg-hover, #333)); }
|
|
69
223
|
.dsh-cron-dropdown { position: absolute; top: calc(100% + 6px); right: 0; width: 200px; background: var(--dsw-alias-bg-layer-3, #212121); border: 1px solid var(--dsw-alias-border-l2, #383838); border-radius: 12px; padding: 6px; box-shadow: 0 10px 25px rgba(0,0,0,0.5); z-index: 200; }
|
|
70
|
-
.dsh-cron-dropdown-item { display: flex; align-items: center; gap: 10px; width: 100%; padding: 10px 12px; background: transparent; border: none; border-radius: 8px; color: #ededed; font-size: 13.5px; text-align: left; cursor: pointer; }
|
|
71
|
-
.dsh-cron-dropdown-item:hover { background: var(--dsw-alias-bg-
|
|
224
|
+
.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, #ededed); font-size: 13.5px; text-align: left; cursor: pointer; }
|
|
225
|
+
.dsh-cron-dropdown-item:hover { background: var(--dsw-alias-interactive-bg-hover, #2a2a2a); }
|
|
72
226
|
.dsh-cron-search-bar { position: relative; margin-bottom: 20px; }
|
|
73
|
-
.dsh-cron-search-input { width: 100%; background: var(--dsw-alias-bg-layer-3, #1f1f1f); border: 1px solid var(--dsw-alias-border-l2, #333); border-radius: 10px; padding: 10px 14px 10px 38px; color: inherit; font-size: 14px; outline: none; box-sizing: border-box; }
|
|
74
|
-
.dsh-cron-search-icon { position: absolute; left: 12px; top: 50%; transform: translateY(-50%); color: #666; }
|
|
227
|
+
.dsh-cron-search-input { width: 100%; background: var(--dsw-alias-bg-layer-3, #1f1f1f); border: 1px solid var(--dsw-alias-border-l2, #333); border-radius: 10px; padding: 10px 14px 10px 38px; color: var(--dsw-alias-label-primary, inherit); font-size: 14px; outline: none; box-sizing: border-box; }
|
|
228
|
+
.dsh-cron-search-icon { position: absolute; left: 12px; top: 50%; transform: translateY(-50%); color: var(--dsw-alias-label-tertiary, #666); }
|
|
75
229
|
.dsh-cron-tabs { display: flex; gap: 8px; margin-bottom: 24px; }
|
|
76
|
-
.dsh-cron-tab { appearance: none; background: transparent; border: none; padding: 6px 14px; border-radius: 9999px; color: #9ca3af; font-size: 13.5px; cursor: pointer; }
|
|
77
|
-
.dsh-cron-tab[data-active="true"] { background: var(--dsw-alias-bg-layer-4, #2a2a2a); color: #fff; font-weight: 500; }
|
|
230
|
+
.dsh-cron-tab { appearance: none; background: transparent; border: none; padding: 6px 14px; border-radius: 9999px; color: var(--dsw-alias-label-secondary, #9ca3af); font-size: 13.5px; cursor: pointer; }
|
|
231
|
+
.dsh-cron-tab[data-active="true"] { background: var(--dsw-alias-bg-layer-4, #2a2a2a); color: var(--dsw-alias-label-primary, #fff); font-weight: 500; }
|
|
78
232
|
.dsh-cron-task-list { display: flex; flex-direction: column; gap: 12px; margin-bottom: 40px; }
|
|
79
233
|
.dsh-cron-task-item { display: flex; align-items: center; gap: 16px; padding: 14px 18px; background: var(--dsw-alias-bg-layer-3, #1e1e1e); border: 1px solid var(--dsw-alias-border-l2, #2e2e2e); border-radius: 12px; cursor: pointer; transition: background 0.15s, border-color 0.15s; }
|
|
80
|
-
.dsh-cron-task-item:hover { background: var(--dsw-alias-bg-layer-4, #242424); border-color: #444; }
|
|
81
|
-
.dsh-cron-task-prompt-preview { font-size: 12.5px; color: #888; margin-top: 5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 520px; line-height: 1.3; }
|
|
234
|
+
.dsh-cron-task-item:hover, .dsh-cron-task-item:focus-visible { background: var(--dsw-alias-bg-layer-4, #242424); border-color: var(--dsw-alias-border-l1, #444); outline: none; }
|
|
235
|
+
.dsh-cron-task-prompt-preview { font-size: 12.5px; color: var(--dsw-alias-label-tertiary, #888); margin-top: 5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 520px; line-height: 1.3; }
|
|
82
236
|
.dsh-cron-type-tag { display: inline-block; font-size: 10.5px; padding: 1px 6px; border-radius: 4px; text-transform: uppercase; font-weight: 600; margin-left: 8px; vertical-align: middle; }
|
|
83
|
-
.dsh-cron-task-
|
|
84
|
-
.dsh-cron-task-status-btn { background: none; border: 1px solid #444; border-radius: 50%; width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; color: #888; cursor: pointer; flex-shrink: 0; }
|
|
85
|
-
.dsh-cron-task-status-btn[data-active="true"] { color: #10b981; border-color: #10b981; }
|
|
86
|
-
.dsh-cron-task-info { flex: 1; min-width: 0; }
|
|
87
|
-
.dsh-cron-task-title { font-size: 15px; font-weight: 500; margin-bottom: 3px; }
|
|
88
|
-
.dsh-cron-task-sched { font-size: 13px; color: #9ca3af; }
|
|
89
|
-
.dsh-cron-task-model-tag { display: inline-block; font-size: 11px; padding: 2px 7px; border-radius: 4px; background: rgba(255,255,255,0.06); color: #aaa; margin-left: 8px; vertical-align: middle; }
|
|
237
|
+
.dsh-cron-task-model-tag { display: inline-block; font-size: 11px; padding: 2px 7px; border-radius: 4px; background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,0.06)); color: var(--dsw-alias-label-secondary, #aaa); margin-left: 8px; vertical-align: middle; }
|
|
90
238
|
.dsh-cron-task-actions { display: flex; gap: 8px; }
|
|
91
|
-
.dsh-cron-icon-btn { background: transparent; border: 1px solid #333; border-radius: 8px; padding: 6px 12px; font-size: 12.5px; color: #aaa; cursor: pointer; }
|
|
92
|
-
.dsh-cron-icon-btn:hover { background: #2a2a2a; color: #fff; }
|
|
239
|
+
.dsh-cron-icon-btn { background: transparent; border: 1px solid var(--dsw-alias-border-l2, #333); border-radius: 8px; padding: 6px 12px; font-size: 12.5px; color: var(--dsw-alias-label-secondary, #aaa); cursor: pointer; }
|
|
240
|
+
.dsh-cron-icon-btn:hover { background: var(--dsw-alias-interactive-bg-hover, #2a2a2a); color: var(--dsw-alias-label-primary, #fff); }
|
|
241
|
+
.dsh-cron-task-status-btn { background: none; border: 1px solid var(--dsw-alias-border-l1, #444); border-radius: 50%; width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; color: var(--dsw-alias-label-tertiary, #888); cursor: pointer; flex-shrink: 0; }
|
|
242
|
+
.dsh-cron-task-status-btn[data-active="true"] { color: var(--dsh-cron-success); border-color: var(--dsh-cron-success); }
|
|
93
243
|
.dsh-cron-recs-title { font-size: 17px; font-weight: 600; margin-bottom: 14px; }
|
|
94
244
|
.dsh-cron-recs-list { display: flex; flex-direction: column; gap: 10px; }
|
|
95
245
|
.dsh-cron-rec-card { display: flex; align-items: center; gap: 16px; padding: 16px 20px; background: var(--dsw-alias-bg-layer-3, #1c1c1c); border: 1px solid var(--dsw-alias-border-l1, #282828); border-radius: 12px; cursor: pointer; transition: background 0.15s; }
|
|
96
|
-
.dsh-cron-rec-card:hover { background: var(--dsw-alias-bg-layer-4, #242424); border-color: #383838; }
|
|
97
|
-
.dsh-cron-rec-icon { width: 36px; height: 36px; border-radius: 10px; background: rgba(255,255,255,0.03); display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
|
|
246
|
+
.dsh-cron-rec-card:hover, .dsh-cron-rec-card:focus-visible { background: var(--dsw-alias-bg-layer-4, #242424); border-color: var(--dsw-alias-border-l2, #383838); outline: none; }
|
|
247
|
+
.dsh-cron-rec-icon { width: 36px; height: 36px; border-radius: 10px; background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,0.03)); display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
|
|
98
248
|
.dsh-cron-rec-content { flex: 1; min-width: 0; }
|
|
99
249
|
.dsh-cron-rec-title { font-size: 14.5px; font-weight: 500; margin-bottom: 4px; }
|
|
100
|
-
.dsh-cron-rec-time { font-size: 13px; color: #9ca3af; font-weight: normal; margin-left: 8px; }
|
|
101
|
-
.dsh-cron-rec-desc { font-size: 13px; color: #71717a; }
|
|
102
|
-
|
|
250
|
+
.dsh-cron-rec-time { font-size: 13px; color: var(--dsw-alias-label-secondary, #9ca3af); font-weight: normal; margin-left: 8px; }
|
|
251
|
+
.dsh-cron-rec-desc { font-size: 13px; color: var(--dsw-alias-label-tertiary, #71717a); }
|
|
252
|
+
|
|
103
253
|
.dsh-cron-modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.7); backdrop-filter: blur(4px); display: flex; align-items: center; justify-content: center; z-index: 1000; }
|
|
104
254
|
.dsh-cron-modal { background: var(--dsw-alias-bg-layer-2, #1f1f1f); border: 1px solid var(--dsw-alias-border-l2, #333); border-radius: 16px; width: 100%; max-width: 540px; padding: 24px; box-shadow: 0 20px 40px rgba(0,0,0,0.6); box-sizing: border-box; }
|
|
105
255
|
.dsh-cron-modal-title { font-size: 18px; font-weight: 600; margin-bottom: 18px; }
|
|
106
256
|
.dsh-cron-form-group { margin-bottom: 16px; }
|
|
107
|
-
.dsh-cron-form-group label { display: block; font-size: 13px; color: #aaa; margin-bottom: 6px; }
|
|
108
|
-
.dsh-cron-form-group input, .dsh-cron-form-group textarea, .dsh-cron-form-group select { width: 100%; background: #141414; border: 1px solid #333; border-radius: 8px; padding: 9px 12px; color: #fff; font-size: 13.5px; outline: none; box-sizing: border-box; }
|
|
257
|
+
.dsh-cron-form-group label { display: block; font-size: 13px; color: var(--dsw-alias-label-secondary, #aaa); margin-bottom: 6px; }
|
|
258
|
+
.dsh-cron-form-group input, .dsh-cron-form-group textarea, .dsh-cron-form-group select { width: 100%; background: var(--dsw-alias-bg-base, #141414); border: 1px solid var(--dsw-alias-border-l2, #333); border-radius: 8px; padding: 9px 12px; color: var(--dsw-alias-label-primary, #fff); font-size: 13.5px; outline: none; box-sizing: border-box; }
|
|
109
259
|
.dsh-cron-form-group select { cursor: pointer; }
|
|
110
260
|
.dsh-cron-form-group textarea { min-height: 80px; resize: vertical; }
|
|
111
261
|
.dsh-cron-form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
|
|
112
262
|
.dsh-cron-modal-foot { display: flex; justify-content: flex-end; align-items: center; gap: 10px; margin-top: 20px; }
|
|
113
|
-
.dsh-cron-btn-primary { background: #ededed; color: #111; border: none; border-radius: 8px; padding: 7px 16px; font-size: 13.5px; font-weight: 500; cursor: pointer; display: inline-flex; align-items: center; gap: 6px; }
|
|
263
|
+
.dsh-cron-btn-primary { background: var(--dsw-alias-label-primary, #ededed); color: var(--dsw-alias-bg-base, #111); border: none; border-radius: 8px; padding: 7px 16px; font-size: 13.5px; font-weight: 500; cursor: pointer; display: inline-flex; align-items: center; gap: 6px; }
|
|
114
264
|
.dsh-cron-btn-primary:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
115
|
-
.dsh-cron-btn-secondary { background: transparent; color: #999; border: 1px solid #383838; border-radius: 8px; padding: 7px 16px; font-size: 13.5px; cursor: pointer; }
|
|
116
|
-
.dsh-cron-chat-box { position: relative; background: #141414; border: 1px solid #333; border-radius: 12px; padding: 12px; }
|
|
117
|
-
.dsh-cron-chat-input { width: 100%; background: transparent; border: none; color: #fff; font-size: 14px; min-height: 100px; resize: vertical; outline: none; box-sizing: border-box; line-height: 1.5; }
|
|
118
|
-
.dsh-cron-chat-foot { display: flex; justify-content: space-between; align-items: center; margin-top: 8px; padding-top: 8px; border-top: 1px solid #222; }
|
|
119
|
-
.dsh-cron-chat-hint { font-size: 12px; color: #666; }
|
|
120
|
-
.dsh-cron-send-btn { width: 34px; height: 34px; border-radius: 8px; background: #ededed; color: #111; border: none; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: transform 0.1s, background 0.15s; }
|
|
121
|
-
.dsh-cron-send-btn:hover:not(:disabled) {
|
|
265
|
+
.dsh-cron-btn-secondary { background: transparent; color: var(--dsw-alias-label-secondary, #999); border: 1px solid var(--dsw-alias-border-l2, #383838); border-radius: 8px; padding: 7px 16px; font-size: 13.5px; cursor: pointer; }
|
|
266
|
+
.dsh-cron-chat-box { position: relative; background: var(--dsw-alias-bg-base, #141414); border: 1px solid var(--dsw-alias-border-l2, #333); border-radius: 12px; padding: 12px; }
|
|
267
|
+
.dsh-cron-chat-input { width: 100%; background: transparent; border: none; color: var(--dsw-alias-label-primary, #fff); font-size: 14px; min-height: 100px; resize: vertical; outline: none; box-sizing: border-box; line-height: 1.5; }
|
|
268
|
+
.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, #222); }
|
|
269
|
+
.dsh-cron-chat-hint { font-size: 12px; color: var(--dsw-alias-label-tertiary, #666); }
|
|
270
|
+
.dsh-cron-send-btn { width: 34px; height: 34px; border-radius: 8px; background: var(--dsw-alias-label-primary, #ededed); color: var(--dsw-alias-bg-base, #111); border: none; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: transform 0.1s, background 0.15s; }
|
|
271
|
+
.dsh-cron-send-btn:hover:not(:disabled) { transform: translateY(-1px); }
|
|
122
272
|
.dsh-cron-send-btn:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
123
273
|
|
|
124
274
|
.dsh-cron-card { background: var(--dsw-alias-bg-layer-2, #1f1f1f); border: 1px solid var(--dsw-alias-border-l1, #333); border-radius: 12px; padding: 18px 20px; margin-bottom: 16px; }
|
|
@@ -126,35 +276,35 @@ const React = window.React || require('react');
|
|
|
126
276
|
.dsh-cron-card-title { font-size: 15px; font-weight: 600; color: var(--dsw-alias-label-primary, #fff); }
|
|
127
277
|
.dsh-cron-card-desc { font-size: 13px; color: var(--dsw-alias-label-secondary, #888); margin-bottom: 16px; line-height: 1.4; }
|
|
128
278
|
|
|
129
|
-
.dsh-cron-modal-nav { display: flex; gap: 4px; border-bottom: 1px solid #2e2e2e; margin-bottom: 18px; padding-bottom: 2px; }
|
|
130
|
-
.dsh-cron-modal-tab { appearance: none; background: transparent; border: none; border-bottom: 2px solid transparent; padding: 8px 14px; font-size: 13.5px; color: #888; cursor: pointer; font-weight: 500; transition: color 0.15s, border-color 0.15s; }
|
|
131
|
-
.dsh-cron-modal-tab:hover { color: #ccc; }
|
|
132
|
-
.dsh-cron-modal-tab[data-active="true"] { color: #fff; border-bottom-color: #ededed; }
|
|
279
|
+
.dsh-cron-modal-nav { display: flex; gap: 4px; border-bottom: 1px solid var(--dsw-alias-border-l2, #2e2e2e); margin-bottom: 18px; padding-bottom: 2px; }
|
|
280
|
+
.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, #888); cursor: pointer; font-weight: 500; transition: color 0.15s, border-color 0.15s; }
|
|
281
|
+
.dsh-cron-modal-tab:hover { color: var(--dsw-alias-label-secondary, #ccc); }
|
|
282
|
+
.dsh-cron-modal-tab[data-active="true"] { color: var(--dsw-alias-label-primary, #fff); border-bottom-color: var(--dsw-alias-label-primary, #ededed); }
|
|
133
283
|
.dsh-cron-history-list { display: flex; flex-direction: column; gap: 10px; max-height: 420px; overflow-y: auto; padding-right: 4px; }
|
|
134
|
-
.dsh-cron-history-item { background: #161616; border: 1px solid #2a2a2a; border-radius: 8px; padding: 12px 14px; font-size: 12.5px; }
|
|
284
|
+
.dsh-cron-history-item { background: var(--dsw-alias-bg-base, #161616); border: 1px solid var(--dsw-alias-border-l1, #2a2a2a); border-radius: 8px; padding: 12px 14px; font-size: 12.5px; }
|
|
135
285
|
.dsh-cron-history-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px; }
|
|
136
286
|
.dsh-cron-history-status { font-weight: 600; font-size: 11.5px; text-transform: uppercase; padding: 2px 6px; border-radius: 4px; }
|
|
137
|
-
.dsh-cron-history-status[data-status="success"] { background:
|
|
138
|
-
.dsh-cron-history-status[data-status="error"] { background:
|
|
139
|
-
.dsh-cron-history-status[data-status="skipped"] { background:
|
|
140
|
-
.dsh-cron-history-time { color: #777; font-size: 12px; }
|
|
141
|
-
.dsh-cron-history-output { background: #0c0c0c; border: 1px solid #222; border-radius: 6px; padding: 8px 10px; font-family: monospace; font-size: 12px; color: #bbb; max-height: 140px; overflow-y: auto; white-space: pre-wrap; word-break: break-all; margin-top: 6px; }
|
|
142
|
-
.dsh-cron-history-empty { text-align: center; padding: 36px 0; color: #666; font-size: 13.5px; }
|
|
287
|
+
.dsh-cron-history-status[data-status="success"] { background: var(--dsh-cron-success-bg); color: var(--dsh-cron-success); }
|
|
288
|
+
.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); }
|
|
289
|
+
.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); }
|
|
290
|
+
.dsh-cron-history-time { color: var(--dsw-alias-label-tertiary, #777); font-size: 12px; }
|
|
291
|
+
.dsh-cron-history-output { background: var(--dsw-alias-bg-base, #0c0c0c); border: 1px solid var(--dsw-alias-border-l1, #222); border-radius: 6px; padding: 8px 10px; font-family: monospace; font-size: 12px; color: var(--dsw-alias-label-secondary, #bbb); max-height: 140px; overflow-y: auto; white-space: pre-wrap; word-break: break-all; margin-top: 6px; }
|
|
292
|
+
.dsh-cron-history-empty { text-align: center; padding: 36px 0; color: var(--dsw-alias-label-tertiary, #666); font-size: 13.5px; }
|
|
143
293
|
|
|
144
294
|
.dsh-cron-stats-bar { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; margin-bottom: 18px; }
|
|
145
|
-
.dsh-cron-stat-card { background: rgba(255, 255, 255, 0.02); border: 1px solid #222; border-radius: 8px; padding: 10px 14px; }
|
|
146
|
-
.dsh-cron-stat-val { font-size: 16px; font-weight: 600; color: #fff; }
|
|
147
|
-
.dsh-cron-stat-lbl { font-size: 11.5px; color: #777; margin-top: 2px; }
|
|
148
|
-
|
|
149
|
-
.dsh-cron-cost-tag { font-size: 11px; padding: 2px 6px; border-radius: 4px; background:
|
|
150
|
-
.dsh-cron-tokens-tag { font-size: 11px; padding: 2px 6px; border-radius: 4px; background:
|
|
151
|
-
.dsh-cron-checkbox-row { display: flex; align-items: center; gap: 8px; font-size: 13px; color: #ccc; cursor: pointer; user-select: none; }
|
|
152
|
-
.dsh-cron-checkbox-row input { width: 15px; height: 15px; margin: 0; cursor: pointer; accent-color: #ededed; }
|
|
153
|
-
.dsh-cron-hint { font-size: 12px; color: #888; margin-top: 4px; }
|
|
295
|
+
.dsh-cron-stat-card { background: var(--dsw-alias-bg-layer-3, rgba(255, 255, 255, 0.02)); border: 1px solid var(--dsw-alias-border-l1, #222); border-radius: 8px; padding: 10px 14px; }
|
|
296
|
+
.dsh-cron-stat-val { font-size: 16px; font-weight: 600; color: var(--dsw-alias-label-primary, #fff); }
|
|
297
|
+
.dsh-cron-stat-lbl { font-size: 11.5px; color: var(--dsw-alias-label-tertiary, #777); margin-top: 2px; }
|
|
298
|
+
.dsh-cron-oneshot-tag { font-size: 11px; padding: 2px 6px; border-radius: 4px; background: var(--dsh-cron-accent-bg); color: var(--dsh-cron-accent); font-weight: 500; }
|
|
299
|
+
.dsh-cron-cost-tag { font-size: 11px; padding: 2px 6px; border-radius: 4px; background: var(--dsh-cron-success-bg); color: var(--dsh-cron-success); font-weight: 500; }
|
|
300
|
+
.dsh-cron-tokens-tag { font-size: 11px; padding: 2px 6px; border-radius: 4px; background: var(--dsh-cron-info-bg); color: var(--dsh-cron-info); }
|
|
301
|
+
.dsh-cron-checkbox-row { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--dsw-alias-label-secondary, #ccc); cursor: pointer; user-select: none; }
|
|
302
|
+
.dsh-cron-checkbox-row input { width: 15px; height: 15px; margin: 0; cursor: pointer; accent-color: var(--dsw-alias-label-primary, #ededed); }
|
|
303
|
+
.dsh-cron-hint { font-size: 12px; color: var(--dsw-alias-label-tertiary, #888); margin-top: 4px; }
|
|
154
304
|
.dsh-cron-status-banner { padding: 10px 14px; border-radius: 8px; font-size: 13px; margin-top: 12px; }
|
|
155
|
-
.dsh-cron-status-banner[data-type="success"] { background:
|
|
156
|
-
.dsh-cron-status-banner[data-type="error"] { background:
|
|
157
|
-
.dsh-cron-status-banner[data-type="info"] { background:
|
|
305
|
+
.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); }
|
|
306
|
+
.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); }
|
|
307
|
+
.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); }
|
|
158
308
|
`;
|
|
159
309
|
|
|
160
310
|
function ensureStyles() {
|
|
@@ -163,6 +313,8 @@ const React = window.React || require('react');
|
|
|
163
313
|
if (!el) {
|
|
164
314
|
el = document.createElement('style');
|
|
165
315
|
el.id = 'dsh-cron-styles';
|
|
316
|
+
// Mark ownership before insertion so neighbor cleanups leave it alone (#91)
|
|
317
|
+
el.dataset.dshPlugin = 'dsh-cron';
|
|
166
318
|
document.head.appendChild(el);
|
|
167
319
|
}
|
|
168
320
|
el.textContent = STYLES;
|
|
@@ -194,7 +346,7 @@ const React = window.React || require('react');
|
|
|
194
346
|
const getSessions = () => (ctx && typeof ctx.get === 'function' ? ctx.get('sessions') : (ctx && ctx.sessions));
|
|
195
347
|
let sessions = getSessions();
|
|
196
348
|
|
|
197
|
-
// 1.
|
|
349
|
+
// 1. Try sessions.open(sessionId) immediately
|
|
198
350
|
if (sessions && typeof sessions.open === 'function') {
|
|
199
351
|
try {
|
|
200
352
|
sessions.open(sessionId);
|
|
@@ -202,7 +354,7 @@ const React = window.React || require('react');
|
|
|
202
354
|
} catch (e) {}
|
|
203
355
|
}
|
|
204
356
|
|
|
205
|
-
// 2.
|
|
357
|
+
// 2. If the session is not in manager.summaries yet, refresh from the backend
|
|
206
358
|
if (sessions && typeof sessions.refresh === 'function') {
|
|
207
359
|
try {
|
|
208
360
|
await sessions.refresh();
|
|
@@ -211,7 +363,7 @@ const React = window.React || require('react');
|
|
|
211
363
|
} catch (e) {}
|
|
212
364
|
}
|
|
213
365
|
|
|
214
|
-
// 3.
|
|
366
|
+
// 3. Async retries until the WebSocket/SSE event adds the session to the list
|
|
215
367
|
for (let i = 0; i < 15; i++) {
|
|
216
368
|
await new Promise(resolve => setTimeout(resolve, 120));
|
|
217
369
|
sessions = getSessions();
|
|
@@ -221,7 +373,7 @@ const React = window.React || require('react');
|
|
|
221
373
|
return true;
|
|
222
374
|
} catch (e) {}
|
|
223
375
|
}
|
|
224
|
-
// DOM fallback:
|
|
376
|
+
// DOM fallback: click the session in the sidebar if it has rendered
|
|
225
377
|
if (typeof document !== 'undefined') {
|
|
226
378
|
const domItem = document.querySelector('[data-session-id="' + sessionId + '"], [role="treeitem"][data-id="' + sessionId + '"], [data-id="' + sessionId + '"]');
|
|
227
379
|
if (domItem) {
|
|
@@ -233,8 +385,20 @@ const React = window.React || require('react');
|
|
|
233
385
|
return false;
|
|
234
386
|
}
|
|
235
387
|
|
|
388
|
+
function statusLabel(t, status) {
|
|
389
|
+
if (status === 'success') return T_KEY(t, 'history.statusSuccess');
|
|
390
|
+
if (status === 'skipped') return T_KEY(t, 'history.statusSkipped');
|
|
391
|
+
if (status === 'missed') return T_KEY(t, 'history.statusMissed');
|
|
392
|
+
if (status === 'timeout') return T_KEY(t, 'history.statusTimeout');
|
|
393
|
+
return T_KEY(t, 'history.statusFailed');
|
|
394
|
+
}
|
|
395
|
+
function T_KEY(t, key, vars) {
|
|
396
|
+
return (typeof t === 'function' ? t : translate)(key, vars);
|
|
397
|
+
}
|
|
398
|
+
|
|
236
399
|
function CronScreen(props) {
|
|
237
400
|
const { ctx, toggle, onClose } = props;
|
|
401
|
+
const t = props.t || translate;
|
|
238
402
|
const [tab, setTab] = React.useState('all');
|
|
239
403
|
const [query, setQuery] = React.useState('');
|
|
240
404
|
const [tasks, setTasks] = React.useState([]);
|
|
@@ -311,11 +475,11 @@ const React = window.React || require('react');
|
|
|
311
475
|
setRecs(data.recommendations || []);
|
|
312
476
|
setStats(data.stats || null);
|
|
313
477
|
} else {
|
|
314
|
-
setFetchError(data && data.error ? data.error :
|
|
478
|
+
setFetchError(data && data.error ? data.error : T_KEY(t, 'list.loadErrorPrefix'));
|
|
315
479
|
}
|
|
316
480
|
} catch (err) {
|
|
317
481
|
console.error('[dsh-cron] fetch tasks error:', err);
|
|
318
|
-
setFetchError(err.message ||
|
|
482
|
+
setFetchError(err.message || T_KEY(t, 'list.loadErrorPrefix'));
|
|
319
483
|
} finally {
|
|
320
484
|
setLoading(false);
|
|
321
485
|
}
|
|
@@ -329,7 +493,7 @@ const React = window.React || require('react');
|
|
|
329
493
|
if (data && data.ok) {
|
|
330
494
|
if (!providerName) {
|
|
331
495
|
setProviders(data.providers || []);
|
|
332
|
-
setDefaultModelInfo(data.defaultModel || null);
|
|
496
|
+
setDefaultModelInfo(data.defaultModel || data.current || null);
|
|
333
497
|
if (data.defaultModel?.models) {
|
|
334
498
|
setModels(data.defaultModel.models);
|
|
335
499
|
}
|
|
@@ -342,7 +506,23 @@ const React = window.React || require('react');
|
|
|
342
506
|
}
|
|
343
507
|
};
|
|
344
508
|
|
|
345
|
-
React.useEffect(() => {
|
|
509
|
+
React.useEffect(() => {
|
|
510
|
+
fetchTasks();
|
|
511
|
+
const pollInterval = setInterval(() => {
|
|
512
|
+
// Background polling without resetting loading spinner
|
|
513
|
+
fetch('/dsh-cron/tasks?status=' + encodeURIComponent(tab) + '&query=' + encodeURIComponent(query))
|
|
514
|
+
.then(res => res.json())
|
|
515
|
+
.then(data => {
|
|
516
|
+
if (data && data.ok) {
|
|
517
|
+
setTasks(data.tasks || []);
|
|
518
|
+
setRecs(data.recommendations || []);
|
|
519
|
+
setStats(data.stats || null);
|
|
520
|
+
}
|
|
521
|
+
})
|
|
522
|
+
.catch(() => {});
|
|
523
|
+
}, 8000);
|
|
524
|
+
return () => clearInterval(pollInterval);
|
|
525
|
+
}, [tab, query]);
|
|
346
526
|
React.useEffect(() => { loadModels(); }, []);
|
|
347
527
|
|
|
348
528
|
const handleToggleTask = async (id) => {
|
|
@@ -354,14 +534,16 @@ const React = window.React || require('react');
|
|
|
354
534
|
|
|
355
535
|
const handleRunNow = async (id) => {
|
|
356
536
|
try {
|
|
537
|
+
setTasks(prev => prev.map(t2 => t2.id === id ? { ...t2, lastStatus: 'running' } : t2));
|
|
357
538
|
await fetch('/dsh-cron/tasks/' + encodeURIComponent(id) + '/run', { method: 'POST' });
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
539
|
+
setTimeout(() => fetchTasks(), 1500);
|
|
540
|
+
} catch (err) {
|
|
541
|
+
console.error('[dsh-cron] run now error:', err);
|
|
542
|
+
}
|
|
361
543
|
};
|
|
362
544
|
|
|
363
545
|
const handleDelete = async (id) => {
|
|
364
|
-
if (!confirm(
|
|
546
|
+
if (!confirm(T_KEY(t, 'confirm.delete'))) return;
|
|
365
547
|
try {
|
|
366
548
|
await fetch('/dsh-cron/tasks/' + encodeURIComponent(id), { method: 'DELETE' });
|
|
367
549
|
fetchTasks();
|
|
@@ -404,15 +586,15 @@ const React = window.React || require('react');
|
|
|
404
586
|
if (data && data.ok) {
|
|
405
587
|
setSettingsModalOpen(false);
|
|
406
588
|
} else {
|
|
407
|
-
alert('
|
|
589
|
+
alert(T_KEY(t, 'settings.saveError', { error: (data && data.error) || T_KEY(t, 'settings.saveErrorUnknown') }));
|
|
408
590
|
}
|
|
409
591
|
} catch (err) {
|
|
410
|
-
alert('
|
|
592
|
+
alert(T_KEY(t, 'settings.saveError', { error: err.message }));
|
|
411
593
|
}
|
|
412
594
|
};
|
|
413
595
|
|
|
414
596
|
const handleTestTelegram = async () => {
|
|
415
|
-
setTestStatus({ loading: true, message:
|
|
597
|
+
setTestStatus({ loading: true, message: T_KEY(t, 'settings.sending') });
|
|
416
598
|
try {
|
|
417
599
|
const res = await fetch('/dsh-cron/telegram/test', {
|
|
418
600
|
method: 'POST',
|
|
@@ -424,12 +606,12 @@ const React = window.React || require('react');
|
|
|
424
606
|
});
|
|
425
607
|
const data = await res.json();
|
|
426
608
|
if (data && data.ok) {
|
|
427
|
-
setTestStatus({ loading: false, success: true, message:
|
|
609
|
+
setTestStatus({ loading: false, success: true, message: T_KEY(t, 'settings.testOk') });
|
|
428
610
|
} else {
|
|
429
|
-
setTestStatus({ loading: false, success: false, message:
|
|
611
|
+
setTestStatus({ loading: false, success: false, message: T_KEY(t, 'settings.testFail', { error: (data && data.error) || '' }) });
|
|
430
612
|
}
|
|
431
613
|
} catch (err) {
|
|
432
|
-
setTestStatus({ loading: false, success: false, message:
|
|
614
|
+
setTestStatus({ loading: false, success: false, message: T_KEY(t, 'settings.testFail', { error: err.message }) });
|
|
433
615
|
}
|
|
434
616
|
};
|
|
435
617
|
|
|
@@ -526,13 +708,17 @@ const React = window.React || require('react');
|
|
|
526
708
|
overlapPolicy: formOverlapPolicy,
|
|
527
709
|
kanbanMode: formKanbanMode,
|
|
528
710
|
};
|
|
711
|
+
// Cross-origin hardening: script tasks need the explicit confirm
|
|
712
|
+
// header, which browsers cannot attach to forged cross-site posts (#86)
|
|
713
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
714
|
+
if (formType === 'script') headers[SCRIPT_CONFIRM_HEADER] = 'script';
|
|
529
715
|
const res = await fetch('/dsh-cron/tasks', {
|
|
530
716
|
method: 'POST',
|
|
531
|
-
headers
|
|
717
|
+
headers,
|
|
532
718
|
body: JSON.stringify(payload)
|
|
533
719
|
});
|
|
534
720
|
const data = await res.json();
|
|
535
|
-
if (!data.ok) { alert(data.error || '
|
|
721
|
+
if (!data.ok) { alert(data.error || T_KEY(t, 'settings.saveError', { error: '' })); return; }
|
|
536
722
|
setManualModalOpen(false);
|
|
537
723
|
fetchTasks();
|
|
538
724
|
} catch (err) { alert(err.message); }
|
|
@@ -557,7 +743,7 @@ const React = window.React || require('react');
|
|
|
557
743
|
});
|
|
558
744
|
const data = await res.json();
|
|
559
745
|
if (!data.ok || !data.sessionId) {
|
|
560
|
-
alert(data.error || '
|
|
746
|
+
alert(T_KEY(t, 'dsh.startFailed', { error: data.error || '' }));
|
|
561
747
|
setDshLoading(false);
|
|
562
748
|
return;
|
|
563
749
|
}
|
|
@@ -568,61 +754,63 @@ const React = window.React || require('react');
|
|
|
568
754
|
|
|
569
755
|
await openSession(ctx, data.sessionId);
|
|
570
756
|
} catch (err) {
|
|
571
|
-
alert('
|
|
757
|
+
alert(T_KEY(t, 'dsh.launchError', { error: err.message }));
|
|
572
758
|
} finally {
|
|
573
759
|
setDshLoading(false);
|
|
574
760
|
}
|
|
575
761
|
};
|
|
576
762
|
|
|
763
|
+
const iconHtml = (html) => ({ dangerouslySetInnerHTML: { __html: html } });
|
|
764
|
+
const svgProps = (html) => ({ style: { display: 'flex', alignItems: 'center' }, ...iconHtml(html) });
|
|
765
|
+
|
|
577
766
|
return React.createElement('div', { className: 'dsh-cron-overlay' },
|
|
578
767
|
React.createElement('div', { className: 'dsh-cron-container' },
|
|
579
768
|
React.createElement('div', { className: 'dsh-cron-top-bar' },
|
|
580
769
|
React.createElement('button', {
|
|
581
770
|
type: 'button',
|
|
582
771
|
className: 'dsh-cron-back-btn',
|
|
583
|
-
title:
|
|
772
|
+
title: T_KEY(t, 'panel.back'),
|
|
584
773
|
onClick: () => { if (onClose) onClose(); else toggle.set(false); }
|
|
585
774
|
},
|
|
586
|
-
React.createElement('span',
|
|
587
|
-
|
|
588
|
-
dangerouslySetInnerHTML: { __html: BACK_ICON }
|
|
589
|
-
}),
|
|
590
|
-
React.createElement('span', null, 'Назад к чату')
|
|
775
|
+
React.createElement('span', svgProps(BACK_ICON)),
|
|
776
|
+
React.createElement('span', null, T_KEY(t, 'panel.back'))
|
|
591
777
|
)
|
|
592
778
|
),
|
|
593
779
|
React.createElement('div', { className: 'dsh-cron-header' },
|
|
594
780
|
React.createElement('div', null,
|
|
595
|
-
React.createElement('div', { className: 'dsh-cron-title' },
|
|
596
|
-
React.createElement('div', { className: 'dsh-cron-subtitle' },
|
|
781
|
+
React.createElement('div', { className: 'dsh-cron-title' }, T_KEY(t, 'panel.title')),
|
|
782
|
+
React.createElement('div', { className: 'dsh-cron-subtitle' }, T_KEY(t, 'panel.subtitle'))
|
|
597
783
|
),
|
|
598
784
|
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: '10px' } },
|
|
599
785
|
React.createElement('button', {
|
|
600
786
|
type: 'button',
|
|
601
787
|
className: 'dsh-cron-btn-secondary',
|
|
602
788
|
style: { display: 'flex', alignItems: 'center', gap: '6px', height: '36px', padding: '0 14px' },
|
|
603
|
-
title:
|
|
789
|
+
title: T_KEY(t, 'actions.settings'),
|
|
604
790
|
onClick: openSettingsModal
|
|
605
791
|
},
|
|
606
|
-
React.createElement('span', { style: { display: 'flex', alignItems: 'center', color: '
|
|
607
|
-
|
|
792
|
+
React.createElement('span', { style: { display: 'flex', alignItems: 'center', color: 'var(--dsh-cron-info)' }, ...iconHtml(ICON_TELEGRAM) }),
|
|
793
|
+
T_KEY(t, 'actions.settings')
|
|
608
794
|
),
|
|
609
795
|
React.createElement('div', { style: { position: 'relative' } },
|
|
610
796
|
React.createElement('button', { className: 'dsh-cron-create-btn', onClick: () => setDropdownOpen(!dropdownOpen) },
|
|
611
|
-
'
|
|
612
|
-
React.createElement('span',
|
|
797
|
+
T_KEY(t, 'actions.create'),
|
|
798
|
+
React.createElement('span', iconHtml(ICON_CHEVRON_DOWN))
|
|
613
799
|
),
|
|
614
800
|
dropdownOpen && React.createElement('div', { className: 'dsh-cron-dropdown' },
|
|
615
|
-
React.createElement('button', { className: 'dsh-cron-dropdown-item', onClick: openDshModal },
|
|
616
|
-
React.createElement('button', { className: 'dsh-cron-dropdown-item', onClick: () => openManualModal(null) },
|
|
801
|
+
React.createElement('button', { className: 'dsh-cron-dropdown-item', onClick: openDshModal }, T_KEY(t, 'actions.createWithDsh')),
|
|
802
|
+
React.createElement('button', { className: 'dsh-cron-dropdown-item', onClick: () => openManualModal(null) }, T_KEY(t, 'actions.setupManually'))
|
|
617
803
|
)
|
|
618
804
|
)
|
|
619
805
|
)
|
|
620
806
|
),
|
|
621
807
|
React.createElement('div', { className: 'dsh-cron-search-bar' },
|
|
622
|
-
React.createElement('span', { className: 'dsh-cron-search-icon',
|
|
808
|
+
React.createElement('span', { className: 'dsh-cron-search-icon', ...iconHtml(ICON_SEARCH) }),
|
|
623
809
|
React.createElement('input', {
|
|
624
810
|
className: 'dsh-cron-search-input',
|
|
625
|
-
|
|
811
|
+
type: 'search',
|
|
812
|
+
'aria-label': T_KEY(t, 'search.placeholder'),
|
|
813
|
+
placeholder: T_KEY(t, 'search.placeholder'),
|
|
626
814
|
value: query,
|
|
627
815
|
onChange: (e) => setQuery(e.target.value)
|
|
628
816
|
})
|
|
@@ -630,29 +818,33 @@ const React = window.React || require('react');
|
|
|
630
818
|
stats && React.createElement('div', { className: 'dsh-cron-stats-bar' },
|
|
631
819
|
React.createElement('div', { className: 'dsh-cron-stat-card' },
|
|
632
820
|
React.createElement('div', { className: 'dsh-cron-stat-val' }, stats.activeTasks || 0),
|
|
633
|
-
React.createElement('div', { className: 'dsh-cron-stat-lbl' },
|
|
821
|
+
React.createElement('div', { className: 'dsh-cron-stat-lbl' }, T_KEY(t, 'stats.activeTasks'))
|
|
634
822
|
),
|
|
635
823
|
React.createElement('div', { className: 'dsh-cron-stat-card' },
|
|
636
824
|
React.createElement('div', { className: 'dsh-cron-stat-val' }, stats.totalRuns || 0),
|
|
637
|
-
React.createElement('div', { className: 'dsh-cron-stat-lbl' },
|
|
825
|
+
React.createElement('div', { className: 'dsh-cron-stat-lbl' }, T_KEY(t, 'stats.totalRuns'))
|
|
638
826
|
),
|
|
639
827
|
React.createElement('div', { className: 'dsh-cron-stat-card' },
|
|
640
828
|
React.createElement('div', { className: 'dsh-cron-stat-val' }, (stats.totalTokens || 0).toLocaleString()),
|
|
641
|
-
React.createElement('div', { className: 'dsh-cron-stat-lbl' },
|
|
829
|
+
React.createElement('div', { className: 'dsh-cron-stat-lbl' }, T_KEY(t, 'stats.totalTokens'))
|
|
642
830
|
),
|
|
643
831
|
React.createElement('div', { className: 'dsh-cron-stat-card' },
|
|
644
|
-
React.createElement('div', { className: 'dsh-cron-stat-val', style: { color: '
|
|
645
|
-
React.createElement('div', { className: 'dsh-cron-stat-lbl' },
|
|
832
|
+
React.createElement('div', { className: 'dsh-cron-stat-val', style: { color: 'var(--dsh-cron-success)' } }, '$' + (stats.totalCostUsd || 0).toFixed(4)),
|
|
833
|
+
React.createElement('div', { className: 'dsh-cron-stat-lbl' }, T_KEY(t, 'stats.totalCost'))
|
|
646
834
|
)
|
|
647
835
|
),
|
|
648
836
|
React.createElement('div', { className: 'dsh-cron-tabs' },
|
|
649
|
-
['all', 'active', 'paused', 'completed'].map((
|
|
837
|
+
['all', 'active', 'paused', 'completed'].map((tabName) =>
|
|
650
838
|
React.createElement('button', {
|
|
651
|
-
key:
|
|
839
|
+
key: tabName,
|
|
652
840
|
className: 'dsh-cron-tab',
|
|
653
|
-
'data-active': tab ===
|
|
654
|
-
onClick: () => setTab(
|
|
655
|
-
},
|
|
841
|
+
'data-active': tab === tabName ? 'true' : undefined,
|
|
842
|
+
onClick: () => setTab(tabName)
|
|
843
|
+
},
|
|
844
|
+
tabName === 'all' ? T_KEY(t, 'tabs.all')
|
|
845
|
+
: tabName === 'active' ? T_KEY(t, 'tabs.active')
|
|
846
|
+
: tabName === 'paused' ? T_KEY(t, 'tabs.paused')
|
|
847
|
+
: T_KEY(t, 'tabs.completed'))
|
|
656
848
|
)
|
|
657
849
|
),
|
|
658
850
|
fetchError ? React.createElement('div', {
|
|
@@ -660,50 +852,59 @@ const React = window.React || require('react');
|
|
|
660
852
|
'data-type': 'error',
|
|
661
853
|
style: { margin: '8px 0', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }
|
|
662
854
|
},
|
|
663
|
-
React.createElement('span', null, '
|
|
855
|
+
React.createElement('span', null, T_KEY(t, 'banner.loadError', { error: fetchError })),
|
|
664
856
|
React.createElement('button', {
|
|
665
857
|
type: 'button',
|
|
666
|
-
className: 'dsh-cron-btn',
|
|
858
|
+
className: 'dsh-cron-btn-secondary',
|
|
667
859
|
style: { marginLeft: '8px', padding: '2px 8px', fontSize: '12px' },
|
|
668
860
|
onClick: () => fetchTasks()
|
|
669
|
-
}, '
|
|
861
|
+
}, T_KEY(t, 'banner.retry'))
|
|
670
862
|
) : null,
|
|
671
863
|
React.createElement('div', { className: 'dsh-cron-task-list' },
|
|
672
864
|
loading && tasks.length === 0
|
|
673
|
-
? React.createElement('div', { style: { color: '#888', padding: '16px 0', fontSize: '14px' } },
|
|
865
|
+
? React.createElement('div', { style: { color: 'var(--dsw-alias-label-tertiary, #888)', padding: '16px 0', fontSize: '14px' } }, T_KEY(t, 'list.loading'))
|
|
674
866
|
: tasks.length === 0
|
|
675
|
-
? React.createElement('div', { style: { color: '#777', padding: '16px 0', fontSize: '14px' } },
|
|
867
|
+
? React.createElement('div', { style: { color: 'var(--dsw-alias-label-tertiary, #777)', padding: '16px 0', fontSize: '14px' } }, T_KEY(t, 'list.empty'))
|
|
676
868
|
: tasks.map((task) =>
|
|
677
869
|
React.createElement('div', {
|
|
678
870
|
key: task.id,
|
|
679
871
|
className: 'dsh-cron-task-item',
|
|
680
|
-
|
|
872
|
+
role: 'button',
|
|
873
|
+
tabIndex: 0,
|
|
874
|
+
'aria-label': task.title,
|
|
875
|
+
onClick: () => openEditModal(task),
|
|
876
|
+
onKeyDown: (e) => {
|
|
877
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
878
|
+
e.preventDefault();
|
|
879
|
+
openEditModal(task);
|
|
880
|
+
}
|
|
881
|
+
}
|
|
681
882
|
},
|
|
682
883
|
React.createElement('button', {
|
|
683
884
|
type: 'button',
|
|
684
885
|
className: 'dsh-cron-task-status-btn',
|
|
685
886
|
'data-active': task.status === 'active' ? 'true' : undefined,
|
|
686
|
-
title: task.status === 'active' ? '
|
|
887
|
+
title: task.status === 'active' ? T_KEY(t, 'actions.pause') : T_KEY(t, 'actions.resume'),
|
|
888
|
+
'aria-label': task.status === 'active' ? T_KEY(t, 'actions.pause') : T_KEY(t, 'actions.resume'),
|
|
687
889
|
onClick: (e) => { e.stopPropagation(); handleToggleTask(task.id); }
|
|
688
|
-
}, React.createElement('span',
|
|
890
|
+
}, React.createElement('span', svgProps(task.status === 'active' ? ICON_PAUSE : ICON_PLAY))),
|
|
689
891
|
React.createElement('div', { className: 'dsh-cron-task-info' },
|
|
690
892
|
React.createElement('div', { className: 'dsh-cron-task-title' },
|
|
691
893
|
task.title,
|
|
692
894
|
React.createElement('span', {
|
|
693
895
|
className: 'dsh-cron-type-tag',
|
|
694
|
-
style:
|
|
695
|
-
background:
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
}, task.type === 'script' ? 'Shell' : 'LLM'),
|
|
896
|
+
style: task.type === 'script'
|
|
897
|
+
? { background: 'var(--dsh-cron-info-bg)', color: 'var(--dsh-cron-info)' }
|
|
898
|
+
: { background: 'var(--dsh-cron-success-bg)', color: 'var(--dsh-cron-success)' }
|
|
899
|
+
}, task.type === 'script' ? T_KEY(t, 'list.typeShell') : T_KEY(t, 'list.typeLlm')),
|
|
699
900
|
task.model ? React.createElement('span', { className: 'dsh-cron-task-model-tag' }, task.model.split('/').pop()) : null,
|
|
700
|
-
task.oneShot ? React.createElement('span', { className: 'dsh-cron-oneshot-tag' }, '
|
|
901
|
+
task.oneShot ? React.createElement('span', { className: 'dsh-cron-oneshot-tag' }, T_KEY(t, 'list.oneShot')) : null,
|
|
701
902
|
task.totalCostUsd > 0 ? React.createElement('span', { className: 'dsh-cron-cost-tag' }, '$' + task.totalCostUsd.toFixed(4)) : null,
|
|
702
903
|
task.totalTokens > 0 ? React.createElement('span', { className: 'dsh-cron-tokens-tag' }, (task.totalTokens > 1000 ? Math.round(task.totalTokens / 1000) + 'k' : task.totalTokens) + ' tok') : null
|
|
703
904
|
),
|
|
704
905
|
React.createElement('div', { className: 'dsh-cron-task-sched' },
|
|
705
906
|
task.scheduleText || task.schedule,
|
|
706
|
-
task.nextRunAt ?
|
|
907
|
+
task.nextRunAt ? T_KEY(t, 'list.next', { time: new Date(task.nextRunAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) }) : ''
|
|
707
908
|
),
|
|
708
909
|
task.prompt ? React.createElement('div', {
|
|
709
910
|
className: 'dsh-cron-task-prompt-preview',
|
|
@@ -717,37 +918,45 @@ const React = window.React || require('react');
|
|
|
717
918
|
React.createElement('button', {
|
|
718
919
|
type: 'button',
|
|
719
920
|
className: 'dsh-cron-icon-btn',
|
|
720
|
-
title:
|
|
921
|
+
title: T_KEY(t, 'actions.runNow'),
|
|
721
922
|
onClick: () => handleRunNow(task.id)
|
|
722
|
-
}, '
|
|
923
|
+
}, T_KEY(t, 'actions.runNowShort')),
|
|
723
924
|
React.createElement('button', {
|
|
724
925
|
type: 'button',
|
|
725
926
|
className: 'dsh-cron-icon-btn',
|
|
726
|
-
title:
|
|
927
|
+
title: T_KEY(t, 'actions.edit'),
|
|
928
|
+
'aria-label': T_KEY(t, 'actions.edit'),
|
|
727
929
|
onClick: () => openEditModal(task)
|
|
728
|
-
}, React.createElement('span',
|
|
930
|
+
}, React.createElement('span', svgProps(ICON_EDIT))),
|
|
729
931
|
React.createElement('button', {
|
|
730
932
|
type: 'button',
|
|
731
933
|
className: 'dsh-cron-icon-btn',
|
|
732
|
-
title:
|
|
934
|
+
title: T_KEY(t, 'actions.delete'),
|
|
935
|
+
'aria-label': T_KEY(t, 'actions.delete'),
|
|
733
936
|
onClick: () => handleDelete(task.id)
|
|
734
|
-
}, React.createElement('span',
|
|
937
|
+
}, React.createElement('span', svgProps(ICON_TRASH)))
|
|
735
938
|
)
|
|
736
939
|
)
|
|
737
940
|
)
|
|
738
941
|
),
|
|
739
|
-
React.createElement('div', { className: 'dsh-cron-recs-title' },
|
|
942
|
+
React.createElement('div', { className: 'dsh-cron-recs-title' }, T_KEY(t, 'recs.title')),
|
|
740
943
|
React.createElement('div', { className: 'dsh-cron-recs-list' },
|
|
741
944
|
recs.map((rec) =>
|
|
742
945
|
React.createElement('div', {
|
|
743
946
|
key: rec.id,
|
|
744
947
|
className: 'dsh-cron-rec-card',
|
|
745
|
-
|
|
948
|
+
role: 'button',
|
|
949
|
+
tabIndex: 0,
|
|
950
|
+
onClick: () => openManualModal(rec),
|
|
951
|
+
onKeyDown: (e) => {
|
|
952
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
953
|
+
e.preventDefault();
|
|
954
|
+
openManualModal(rec);
|
|
955
|
+
}
|
|
956
|
+
}
|
|
746
957
|
},
|
|
747
958
|
React.createElement('div', { className: 'dsh-cron-rec-icon' },
|
|
748
|
-
React.createElement('span',
|
|
749
|
-
__html: rec.icon === 'bell' ? ICON_BELL : rec.icon === 'clipboard' ? ICON_CLIPBOARD : ICON_ACTIVITY
|
|
750
|
-
} })
|
|
959
|
+
React.createElement('span', iconHtml(rec.icon === 'bell' ? ICON_BELL : rec.icon === 'clipboard' ? ICON_CLIPBOARD : ICON_ACTIVITY))
|
|
751
960
|
),
|
|
752
961
|
React.createElement('div', { className: 'dsh-cron-rec-content' },
|
|
753
962
|
React.createElement('div', { className: 'dsh-cron-rec-title' },
|
|
@@ -766,285 +975,288 @@ const React = window.React || require('react');
|
|
|
766
975
|
React.createElement('div', {
|
|
767
976
|
className: 'dsh-cron-modal',
|
|
768
977
|
style: { maxWidth: '640px' },
|
|
978
|
+
role: 'dialog',
|
|
979
|
+
'aria-modal': 'true',
|
|
769
980
|
onClick: (e) => e.stopPropagation()
|
|
770
981
|
},
|
|
771
982
|
React.createElement('div', { className: 'dsh-cron-modal-head', style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: formId ? '12px' : '18px' } },
|
|
772
|
-
React.createElement('div', { className: 'dsh-cron-modal-title', style: { margin: 0 } }, formId ?
|
|
983
|
+
React.createElement('div', { className: 'dsh-cron-modal-title', style: { margin: 0 } }, formId ? T_KEY(t, 'modal.editTitle') : T_KEY(t, 'modal.newTitle')),
|
|
773
984
|
selectedTaskMeta && React.createElement('span', {
|
|
774
985
|
style: {
|
|
775
986
|
fontSize: '12px',
|
|
776
987
|
padding: '3px 8px',
|
|
777
988
|
borderRadius: '6px',
|
|
778
|
-
background: selectedTaskMeta.status === 'active' ? '
|
|
779
|
-
color: selectedTaskMeta.status === 'active' ? '
|
|
989
|
+
background: selectedTaskMeta.status === 'active' ? 'var(--dsh-cron-success-bg)' : 'var(--dsh-cron-danger-bg)',
|
|
990
|
+
color: selectedTaskMeta.status === 'active' ? 'var(--dsh-cron-success)' : 'var(--dsh-cron-danger)'
|
|
780
991
|
}
|
|
781
|
-
}, selectedTaskMeta.status === 'active' ?
|
|
992
|
+
}, selectedTaskMeta.status === 'active' ? T_KEY(t, 'modal.statusActive') : T_KEY(t, 'modal.statusPaused'))
|
|
782
993
|
),
|
|
783
994
|
|
|
784
995
|
formId && React.createElement('div', { className: 'dsh-cron-modal-nav' },
|
|
785
996
|
React.createElement('button', {
|
|
786
997
|
type: 'button',
|
|
787
998
|
className: 'dsh-cron-modal-tab',
|
|
788
|
-
'data-active': modalTab === 'params',
|
|
999
|
+
'data-active': modalTab === 'params' ? 'true' : undefined,
|
|
789
1000
|
onClick: () => setModalTab('params')
|
|
790
|
-
}, '
|
|
1001
|
+
}, T_KEY(t, 'modal.params')),
|
|
791
1002
|
React.createElement('button', {
|
|
792
1003
|
type: 'button',
|
|
793
1004
|
className: 'dsh-cron-modal-tab',
|
|
794
|
-
'data-active': modalTab === 'history',
|
|
1005
|
+
'data-active': modalTab === 'history' ? 'true' : undefined,
|
|
795
1006
|
onClick: () => { setModalTab('history'); if (formId) loadTaskHistory(formId); }
|
|
796
|
-
}, '
|
|
1007
|
+
}, T_KEY(t, 'modal.history', { count: taskHistory.length || 0 }))
|
|
797
1008
|
),
|
|
798
1009
|
|
|
799
1010
|
modalTab === 'history' && formId ? React.createElement('div', null,
|
|
800
|
-
historyLoading ? React.createElement('div', { className: 'dsh-cron-history-empty' },
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
1011
|
+
historyLoading ? React.createElement('div', { className: 'dsh-cron-history-empty' }, T_KEY(t, 'history.loading')) :
|
|
1012
|
+
taskHistory.length === 0 ? React.createElement('div', { className: 'dsh-cron-history-empty' }, T_KEY(t, 'history.empty')) :
|
|
1013
|
+
React.createElement('div', { className: 'dsh-cron-history-list' },
|
|
1014
|
+
taskHistory.map((run) => React.createElement('div', { key: run.id, className: 'dsh-cron-history-item' },
|
|
1015
|
+
React.createElement('div', { className: 'dsh-cron-history-head' },
|
|
1016
|
+
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: '8px' } },
|
|
1017
|
+
React.createElement('span', {
|
|
1018
|
+
className: 'dsh-cron-history-status',
|
|
1019
|
+
'data-status': run.status
|
|
1020
|
+
}, statusLabel(t, run.status)),
|
|
1021
|
+
React.createElement('span', { className: 'dsh-cron-history-time' }, run.at ? new Date(run.at).toLocaleString() : '')
|
|
1022
|
+
),
|
|
1023
|
+
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: '8px' } },
|
|
1024
|
+
run.costUsd > 0 && React.createElement('span', { className: 'dsh-cron-cost-tag' }, '$' + run.costUsd.toFixed(5)),
|
|
1025
|
+
run.usage && (run.usage.inputTokens > 0 || run.usage.outputTokens > 0) && React.createElement('span', { className: 'dsh-cron-tokens-tag' }, (run.usage.inputTokens + run.usage.outputTokens) + ' tok'),
|
|
1026
|
+
React.createElement('span', { style: { color: 'var(--dsw-alias-label-tertiary, #888)', fontSize: '11.5px' } }, (run.durationMs || 0) + ' ms')
|
|
1027
|
+
)
|
|
1028
|
+
),
|
|
1029
|
+
run.output && React.createElement('div', { className: 'dsh-cron-history-output' }, run.output),
|
|
1030
|
+
run.error && React.createElement('div', { className: 'dsh-cron-history-output', style: { color: 'var(--dsh-cron-danger)', borderColor: 'var(--dsh-cron-danger-bg)' } }, run.error)
|
|
1031
|
+
))
|
|
817
1032
|
),
|
|
818
|
-
run.output && React.createElement('div', { className: 'dsh-cron-history-output' }, run.output),
|
|
819
|
-
run.error && React.createElement('div', { className: 'dsh-cron-history-output', style: { color: '#ef4444', borderColor: '#552222' } }, run.error)
|
|
820
|
-
))
|
|
821
|
-
),
|
|
822
1033
|
React.createElement('div', { className: 'dsh-cron-modal-foot', style: { marginTop: '18px' } },
|
|
823
1034
|
React.createElement('button', {
|
|
824
1035
|
type: 'button',
|
|
825
1036
|
className: 'dsh-cron-btn-secondary',
|
|
826
1037
|
onClick: () => { if (formId) loadTaskHistory(formId); }
|
|
827
|
-
},
|
|
1038
|
+
}, T_KEY(t, 'history.refresh')),
|
|
828
1039
|
React.createElement('button', {
|
|
829
1040
|
type: 'button',
|
|
830
1041
|
className: 'dsh-cron-btn-primary',
|
|
831
1042
|
onClick: () => setManualModalOpen(false)
|
|
832
|
-
}, '
|
|
1043
|
+
}, T_KEY(t, 'modal.close'))
|
|
833
1044
|
)
|
|
834
1045
|
) :
|
|
835
1046
|
|
|
836
|
-
|
|
837
|
-
React.createElement('div', { className: 'dsh-cron-form-group' },
|
|
838
|
-
React.createElement('label', null, 'Название задачи'),
|
|
839
|
-
React.createElement('input', {
|
|
840
|
-
required: true,
|
|
841
|
-
value: formTitle,
|
|
842
|
-
placeholder: 'Например: dsh-tags-watch',
|
|
843
|
-
onChange: (e) => setFormTitle(e.target.value)
|
|
844
|
-
})
|
|
845
|
-
),
|
|
846
|
-
React.createElement('div', { className: 'dsh-cron-form-row', style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '12px' } },
|
|
847
|
-
React.createElement('div', { className: 'dsh-cron-form-group' },
|
|
848
|
-
React.createElement('label', null, 'Тип выполнения'),
|
|
849
|
-
React.createElement('select', {
|
|
850
|
-
value: formType,
|
|
851
|
-
onChange: (e) => setFormType(e.target.value)
|
|
852
|
-
},
|
|
853
|
-
React.createElement('option', { value: 'llm' }, 'LLM (Агент с нейросетью)'),
|
|
854
|
-
React.createElement('option', { value: 'script' }, 'NO-LLM (Shell-команда/Скрипт)')
|
|
855
|
-
)
|
|
856
|
-
),
|
|
1047
|
+
React.createElement('form', { onSubmit: handleSaveManual },
|
|
857
1048
|
React.createElement('div', { className: 'dsh-cron-form-group' },
|
|
858
|
-
React.createElement('label', null,
|
|
1049
|
+
React.createElement('label', null, T_KEY(t, 'form.nameLabel')),
|
|
859
1050
|
React.createElement('input', {
|
|
860
1051
|
required: true,
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
)
|
|
866
|
-
),
|
|
867
|
-
React.createElement('div', { className: 'dsh-cron-form-row', style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '12px' } },
|
|
868
|
-
React.createElement('div', { className: 'dsh-cron-form-group' },
|
|
869
|
-
React.createElement('label', null, 'Таймаут выполнения (секунды)'),
|
|
870
|
-
React.createElement('input', {
|
|
871
|
-
type: 'number',
|
|
872
|
-
min: 1,
|
|
873
|
-
value: formTimeoutSeconds,
|
|
874
|
-
placeholder: '1800',
|
|
875
|
-
onChange: (e) => setFormTimeoutSeconds(e.target.value)
|
|
1052
|
+
autoFocus: true,
|
|
1053
|
+
value: formTitle,
|
|
1054
|
+
placeholder: T_KEY(t, 'form.namePlaceholder'),
|
|
1055
|
+
onChange: (e) => setFormTitle(e.target.value)
|
|
876
1056
|
})
|
|
877
1057
|
),
|
|
878
|
-
React.createElement('div', { className: 'dsh-cron-form-
|
|
879
|
-
React.createElement('
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
1058
|
+
React.createElement('div', { className: 'dsh-cron-form-row' },
|
|
1059
|
+
React.createElement('div', { className: 'dsh-cron-form-group' },
|
|
1060
|
+
React.createElement('label', null, T_KEY(t, 'form.typeLabel')),
|
|
1061
|
+
React.createElement('select', {
|
|
1062
|
+
value: formType,
|
|
1063
|
+
onChange: (e) => setFormType(e.target.value)
|
|
1064
|
+
},
|
|
1065
|
+
React.createElement('option', { value: 'llm' }, T_KEY(t, 'form.typeLlm')),
|
|
1066
|
+
React.createElement('option', { value: 'script' }, T_KEY(t, 'form.typeScript'))
|
|
1067
|
+
)
|
|
1068
|
+
),
|
|
1069
|
+
React.createElement('div', { className: 'dsh-cron-form-group' },
|
|
1070
|
+
React.createElement('label', null, T_KEY(t, 'form.scheduleLabel')),
|
|
1071
|
+
React.createElement('input', {
|
|
1072
|
+
required: true,
|
|
1073
|
+
value: formSchedule,
|
|
1074
|
+
placeholder: T_KEY(t, 'form.schedulePlaceholder'),
|
|
1075
|
+
onChange: (e) => setFormSchedule(e.target.value)
|
|
1076
|
+
})
|
|
887
1077
|
)
|
|
888
|
-
)
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
1078
|
+
),
|
|
1079
|
+
React.createElement('div', { className: 'dsh-cron-form-row' },
|
|
1080
|
+
React.createElement('div', { className: 'dsh-cron-form-group' },
|
|
1081
|
+
React.createElement('label', null, T_KEY(t, 'form.timeoutLabel')),
|
|
1082
|
+
React.createElement('input', {
|
|
1083
|
+
type: 'number',
|
|
1084
|
+
min: 1,
|
|
1085
|
+
value: formTimeoutSeconds,
|
|
1086
|
+
placeholder: '1800',
|
|
1087
|
+
onChange: (e) => setFormTimeoutSeconds(e.target.value)
|
|
1088
|
+
})
|
|
1089
|
+
),
|
|
1090
|
+
React.createElement('div', { className: 'dsh-cron-form-group' },
|
|
1091
|
+
React.createElement('label', null, T_KEY(t, 'form.overlapLabel')),
|
|
1092
|
+
React.createElement('select', {
|
|
1093
|
+
value: formOverlapPolicy,
|
|
1094
|
+
onChange: (e) => setFormOverlapPolicy(e.target.value)
|
|
1095
|
+
},
|
|
1096
|
+
React.createElement('option', { value: 'skip' }, T_KEY(t, 'form.overlapSkip')),
|
|
1097
|
+
React.createElement('option', { value: 'queue' }, T_KEY(t, 'form.overlapQueue')),
|
|
1098
|
+
React.createElement('option', { value: 'replace' }, T_KEY(t, 'form.overlapReplace'))
|
|
1099
|
+
)
|
|
910
1100
|
)
|
|
911
1101
|
),
|
|
912
1102
|
React.createElement('div', { className: 'dsh-cron-form-group' },
|
|
913
|
-
React.createElement('label', null,
|
|
1103
|
+
React.createElement('label', null, T_KEY(t, 'form.kanbanLabel')),
|
|
914
1104
|
React.createElement('select', {
|
|
915
|
-
value:
|
|
916
|
-
onChange: (e) =>
|
|
1105
|
+
value: formKanbanMode,
|
|
1106
|
+
onChange: (e) => setFormKanbanMode(e.target.value)
|
|
917
1107
|
},
|
|
918
|
-
React.createElement('option', { value: '' },
|
|
919
|
-
|
|
1108
|
+
React.createElement('option', { value: 'none' }, T_KEY(t, 'form.kanbanNone')),
|
|
1109
|
+
React.createElement('option', { value: 'on_failure' }, T_KEY(t, 'form.kanbanOnFailure')),
|
|
1110
|
+
React.createElement('option', { value: 'always' }, T_KEY(t, 'form.kanbanAlways'))
|
|
920
1111
|
)
|
|
921
|
-
)
|
|
922
|
-
),
|
|
923
|
-
React.createElement('div', { className: 'dsh-cron-form-group' },
|
|
924
|
-
React.createElement('label', null, formType === 'script' ? 'Shell-команда или скрипт' : 'Инструкция / Промпт для агента'),
|
|
925
|
-
React.createElement('textarea', {
|
|
926
|
-
required: true,
|
|
927
|
-
rows: 9,
|
|
928
|
-
style: { fontFamily: 'monospace', fontSize: '12.5px', lineHeight: '1.45' },
|
|
929
|
-
value: formPrompt,
|
|
930
|
-
placeholder: formType === 'script' ? 'curl -fsSL https://... || exit 1' : 'Опишите, что именно агент должен сделать...',
|
|
931
|
-
onChange: (e) => setFormPrompt(e.target.value)
|
|
932
|
-
})
|
|
933
|
-
),
|
|
934
|
-
React.createElement('div', {
|
|
935
|
-
style: {
|
|
936
|
-
background: 'rgba(255,255,255,0.02)',
|
|
937
|
-
border: '1px solid #2e2e2e',
|
|
938
|
-
borderRadius: '8px',
|
|
939
|
-
padding: '12px 14px',
|
|
940
|
-
marginBottom: '16px'
|
|
941
|
-
}
|
|
942
|
-
},
|
|
943
|
-
React.createElement('div', { style: { fontWeight: 500, fontSize: '13px', color: '#eee', marginBottom: '8px', display: 'flex', alignItems: 'center', gap: '6px' } },
|
|
944
|
-
React.createElement('span', { style: { display: 'flex', alignItems: 'center', color: '#60a5fa' }, dangerouslySetInnerHTML: { __html: ICON_TELEGRAM } }),
|
|
945
|
-
'Уведомления в Telegram'
|
|
946
1112
|
),
|
|
947
|
-
React.createElement('
|
|
948
|
-
React.createElement('
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
1113
|
+
formType !== 'script' && React.createElement('div', { className: 'dsh-cron-form-row' },
|
|
1114
|
+
React.createElement('div', { className: 'dsh-cron-form-group' },
|
|
1115
|
+
React.createElement('label', null, T_KEY(t, 'form.providerLabel')),
|
|
1116
|
+
React.createElement('select', {
|
|
1117
|
+
value: formProvider,
|
|
1118
|
+
onChange: handleProviderChange
|
|
1119
|
+
},
|
|
1120
|
+
React.createElement('option', { value: '' }, T_KEY(t, 'form.providerDefault')),
|
|
1121
|
+
providers.map((p) => React.createElement('option', { key: p.id, value: p.id }, p.name || p.id))
|
|
1122
|
+
)
|
|
1123
|
+
),
|
|
1124
|
+
React.createElement('div', { className: 'dsh-cron-form-group' },
|
|
1125
|
+
React.createElement('label', null, T_KEY(t, 'form.modelLabel')),
|
|
1126
|
+
React.createElement('select', {
|
|
1127
|
+
value: formModel,
|
|
1128
|
+
onChange: (e) => setFormModel(e.target.value)
|
|
1129
|
+
},
|
|
1130
|
+
React.createElement('option', { value: '' }, T_KEY(t, 'form.modelDefault')),
|
|
1131
|
+
models.map((m) => React.createElement('option', { key: m.id, value: m.id }, m.name || m.id))
|
|
1132
|
+
)
|
|
1133
|
+
)
|
|
954
1134
|
),
|
|
955
|
-
|
|
956
|
-
React.createElement('
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
style: {
|
|
966
|
-
background: 'rgba(255,255,255,0.03)',
|
|
967
|
-
border: '1px solid #2e2e2e',
|
|
968
|
-
borderRadius: '8px',
|
|
969
|
-
padding: '10px 14px',
|
|
970
|
-
fontSize: '12.5px',
|
|
971
|
-
color: '#999',
|
|
972
|
-
marginBottom: '16px',
|
|
973
|
-
display: 'flex',
|
|
974
|
-
justifyContent: 'space-between',
|
|
975
|
-
alignItems: 'center'
|
|
976
|
-
}
|
|
977
|
-
},
|
|
978
|
-
React.createElement('div', null,
|
|
979
|
-
React.createElement('span', null, 'Следующий запуск: '),
|
|
980
|
-
React.createElement('strong', { style: { color: '#eee' } }, selectedTaskMeta.nextRunAt ? new Date(selectedTaskMeta.nextRunAt).toLocaleString() : 'не запланирован')
|
|
1135
|
+
React.createElement('div', { className: 'dsh-cron-form-group' },
|
|
1136
|
+
React.createElement('label', null, formType === 'script' ? T_KEY(t, 'form.promptLabelScript') : T_KEY(t, 'form.promptLabelLlm')),
|
|
1137
|
+
React.createElement('textarea', {
|
|
1138
|
+
required: true,
|
|
1139
|
+
rows: 9,
|
|
1140
|
+
style: { fontFamily: 'monospace', fontSize: '12.5px', lineHeight: '1.45' },
|
|
1141
|
+
value: formPrompt,
|
|
1142
|
+
placeholder: formType === 'script' ? T_KEY(t, 'form.promptPlaceholderScript') : T_KEY(t, 'form.promptPlaceholderLlm'),
|
|
1143
|
+
onChange: (e) => setFormPrompt(e.target.value)
|
|
1144
|
+
})
|
|
981
1145
|
),
|
|
982
|
-
React.createElement('
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
1146
|
+
React.createElement('div', {
|
|
1147
|
+
style: {
|
|
1148
|
+
background: 'var(--dsw-alias-bg-layer-3, rgba(255,255,255,0.02))',
|
|
1149
|
+
border: '1px solid var(--dsw-alias-border-l2, #2e2e2e)',
|
|
1150
|
+
borderRadius: '8px',
|
|
1151
|
+
padding: '12px 14px',
|
|
1152
|
+
marginBottom: '16px'
|
|
989
1153
|
}
|
|
990
|
-
},
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
className: 'dsh-cron-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1154
|
+
},
|
|
1155
|
+
React.createElement('div', { style: { fontWeight: 500, fontSize: '13px', color: 'var(--dsw-alias-label-primary, #eee)', marginBottom: '8px', display: 'flex', alignItems: 'center', gap: '6px' } },
|
|
1156
|
+
React.createElement('span', { style: { display: 'flex', alignItems: 'center', color: 'var(--dsh-cron-info)' }, ...iconHtml(ICON_TELEGRAM) }),
|
|
1157
|
+
T_KEY(t, 'form.notifyTitle')
|
|
1158
|
+
),
|
|
1159
|
+
React.createElement('label', { className: 'dsh-cron-checkbox-row', style: { marginBottom: formNotifyTelegram ? '8px' : '0' } },
|
|
1160
|
+
React.createElement('input', {
|
|
1161
|
+
type: 'checkbox',
|
|
1162
|
+
checked: formNotifyTelegram,
|
|
1163
|
+
onChange: (e) => setFormNotifyTelegram(e.target.checked)
|
|
1164
|
+
}),
|
|
1165
|
+
React.createElement('span', null, T_KEY(t, 'form.notifyTelegram'))
|
|
1166
|
+
),
|
|
1167
|
+
formNotifyTelegram && React.createElement('label', { className: 'dsh-cron-checkbox-row', style: { marginLeft: '24px', marginTop: '6px' } },
|
|
1168
|
+
React.createElement('input', {
|
|
1169
|
+
type: 'checkbox',
|
|
1170
|
+
checked: formOnlyOnFailure,
|
|
1171
|
+
onChange: (e) => setFormOnlyOnFailure(e.target.checked)
|
|
1172
|
+
}),
|
|
1173
|
+
React.createElement('span', null, T_KEY(t, 'form.notifyOnlyFailure'))
|
|
1174
|
+
)
|
|
1175
|
+
),
|
|
1176
|
+
selectedTaskMeta && React.createElement('div', {
|
|
1177
|
+
style: {
|
|
1178
|
+
background: 'var(--dsw-alias-bg-layer-3, rgba(255,255,255,0.03))',
|
|
1179
|
+
border: '1px solid var(--dsw-alias-border-l2, #2e2e2e)',
|
|
1180
|
+
borderRadius: '8px',
|
|
1181
|
+
padding: '10px 14px',
|
|
1182
|
+
fontSize: '12.5px',
|
|
1183
|
+
color: 'var(--dsw-alias-label-secondary, #999)',
|
|
1184
|
+
marginBottom: '16px',
|
|
1185
|
+
display: 'flex',
|
|
1186
|
+
justifyContent: 'space-between',
|
|
1187
|
+
alignItems: 'center'
|
|
1188
|
+
}
|
|
1189
|
+
},
|
|
1190
|
+
React.createElement('div', null,
|
|
1191
|
+
React.createElement('span', null, T_KEY(t, 'form.nextRunLabel')),
|
|
1192
|
+
React.createElement('strong', { style: { color: 'var(--dsw-alias-label-primary, #eee)' } }, selectedTaskMeta.nextRunAt ? new Date(selectedTaskMeta.nextRunAt).toLocaleString() : T_KEY(t, 'form.notScheduled'))
|
|
1193
|
+
),
|
|
1194
|
+
React.createElement('button', {
|
|
1195
|
+
type: 'button',
|
|
1196
|
+
className: 'dsh-cron-btn-secondary',
|
|
1197
|
+
style: { padding: '4px 10px', fontSize: '12px' },
|
|
1198
|
+
onClick: async () => {
|
|
1199
|
+
await handleRunNow(selectedTaskMeta.id);
|
|
1200
|
+
if (formId) loadTaskHistory(formId);
|
|
1201
|
+
}
|
|
1202
|
+
}, T_KEY(t, 'actions.runNow'))
|
|
1203
|
+
),
|
|
1204
|
+
React.createElement('div', { className: 'dsh-cron-modal-foot' },
|
|
1205
|
+
React.createElement('button', {
|
|
1206
|
+
type: 'button',
|
|
1207
|
+
className: 'dsh-cron-btn-secondary',
|
|
1208
|
+
onClick: () => setManualModalOpen(false)
|
|
1209
|
+
}, T_KEY(t, 'modal.cancel')),
|
|
1210
|
+
React.createElement('button', {
|
|
1211
|
+
type: 'submit',
|
|
1212
|
+
className: 'dsh-cron-btn-primary'
|
|
1213
|
+
}, formId ? T_KEY(t, 'modal.save') : T_KEY(t, 'modal.create'))
|
|
1214
|
+
)
|
|
1002
1215
|
)
|
|
1003
|
-
)
|
|
1004
1216
|
)
|
|
1005
1217
|
),
|
|
1006
1218
|
|
|
1007
|
-
//
|
|
1219
|
+
// 2. Settings modal
|
|
1008
1220
|
settingsModalOpen && React.createElement('div', { className: 'dsh-cron-modal-overlay', onClick: () => setSettingsModalOpen(false) },
|
|
1009
|
-
React.createElement('div', { className: 'dsh-cron-modal', onClick: (e) => e.stopPropagation() },
|
|
1221
|
+
React.createElement('div', { className: 'dsh-cron-modal', role: 'dialog', 'aria-modal': 'true', onClick: (e) => e.stopPropagation() },
|
|
1010
1222
|
React.createElement('div', { className: 'dsh-cron-modal-head', style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '18px' } },
|
|
1011
1223
|
React.createElement('div', { className: 'dsh-cron-modal-title', style: { margin: 0, display: 'flex', alignItems: 'center', gap: '8px' } },
|
|
1012
|
-
React.createElement('span', { style: { display: 'flex', alignItems: 'center', color: '
|
|
1013
|
-
|
|
1224
|
+
React.createElement('span', { style: { display: 'flex', alignItems: 'center', color: 'var(--dsh-cron-info)' }, ...iconHtml(ICON_TELEGRAM) }),
|
|
1225
|
+
T_KEY(t, 'settings.modalTitle')
|
|
1014
1226
|
),
|
|
1015
1227
|
React.createElement('button', {
|
|
1016
1228
|
type: 'button',
|
|
1017
|
-
style: { background: 'none', border: 'none', color: '#888', cursor: 'pointer', fontSize: '18px' },
|
|
1229
|
+
style: { background: 'none', border: 'none', color: 'var(--dsw-alias-label-tertiary, #888)', cursor: 'pointer', fontSize: '18px' },
|
|
1018
1230
|
onClick: () => setSettingsModalOpen(false)
|
|
1019
1231
|
}, '✕')
|
|
1020
1232
|
),
|
|
1021
1233
|
React.createElement('form', { onSubmit: handleSaveSettings },
|
|
1022
1234
|
React.createElement('div', { className: 'dsh-cron-form-group' },
|
|
1023
|
-
React.createElement('label', null,
|
|
1235
|
+
React.createElement('label', null, T_KEY(t, 'settings.tokenLabel')),
|
|
1024
1236
|
React.createElement('input', {
|
|
1025
1237
|
type: 'password',
|
|
1026
1238
|
value: settingsBotToken,
|
|
1027
|
-
placeholder: settingsHasDefault ?
|
|
1239
|
+
placeholder: settingsHasDefault ? T_KEY(t, 'settings.tokenPlaceholderDefault') : T_KEY(t, 'settings.tokenPlaceholder'),
|
|
1028
1240
|
onChange: (e) => setSettingsBotToken(e.target.value)
|
|
1029
1241
|
}),
|
|
1030
|
-
settingsHasDefault && React.createElement('div', { className: 'dsh-cron-hint' },
|
|
1242
|
+
settingsHasDefault && React.createElement('div', { className: 'dsh-cron-hint' }, T_KEY(t, 'settings.defaultHint'))
|
|
1031
1243
|
),
|
|
1032
1244
|
React.createElement('div', { className: 'dsh-cron-form-group' },
|
|
1033
|
-
React.createElement('label', null,
|
|
1245
|
+
React.createElement('label', null, T_KEY(t, 'settings.chatIdLabel')),
|
|
1034
1246
|
React.createElement('input', {
|
|
1035
1247
|
value: settingsChatId,
|
|
1036
|
-
placeholder:
|
|
1248
|
+
placeholder: T_KEY(t, 'settings.chatIdPlaceholder'),
|
|
1037
1249
|
onChange: (e) => setSettingsChatId(e.target.value)
|
|
1038
1250
|
})
|
|
1039
1251
|
),
|
|
1040
|
-
React.createElement('div', { style: { background: 'rgba(255,255,255,0.02)', border: '1px solid #2e2e2e', borderRadius: '8px', padding: '12px 14px', marginBottom: '16px' } },
|
|
1252
|
+
React.createElement('div', { style: { background: 'var(--dsw-alias-bg-layer-3, rgba(255,255,255,0.02))', border: '1px solid var(--dsw-alias-border-l2, #2e2e2e)', borderRadius: '8px', padding: '12px 14px', marginBottom: '16px' } },
|
|
1041
1253
|
React.createElement('label', { className: 'dsh-cron-checkbox-row', style: { marginBottom: '8px' } },
|
|
1042
1254
|
React.createElement('input', {
|
|
1043
1255
|
type: 'checkbox',
|
|
1044
1256
|
checked: settingsNotifyGlobal,
|
|
1045
1257
|
onChange: (e) => setSettingsNotifyGlobal(e.target.checked)
|
|
1046
1258
|
}),
|
|
1047
|
-
React.createElement('span', null,
|
|
1259
|
+
React.createElement('span', null, T_KEY(t, 'settings.globalNotify'))
|
|
1048
1260
|
),
|
|
1049
1261
|
React.createElement('label', { className: 'dsh-cron-checkbox-row' },
|
|
1050
1262
|
React.createElement('input', {
|
|
@@ -1052,7 +1264,7 @@ const React = window.React || require('react');
|
|
|
1052
1264
|
checked: settingsOnlyOnFailureGlobal,
|
|
1053
1265
|
onChange: (e) => setSettingsOnlyOnFailureGlobal(e.target.checked)
|
|
1054
1266
|
}),
|
|
1055
|
-
React.createElement('span', null,
|
|
1267
|
+
React.createElement('span', null, T_KEY(t, 'settings.globalOnlyFailure'))
|
|
1056
1268
|
)
|
|
1057
1269
|
),
|
|
1058
1270
|
React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '14px' } },
|
|
@@ -1062,7 +1274,7 @@ const React = window.React || require('react');
|
|
|
1062
1274
|
style: { display: 'inline-flex', alignItems: 'center', gap: '6px' },
|
|
1063
1275
|
disabled: testStatus?.loading,
|
|
1064
1276
|
onClick: handleTestTelegram
|
|
1065
|
-
}, testStatus?.loading ? '
|
|
1277
|
+
}, testStatus?.loading ? T_KEY(t, 'settings.sending') : T_KEY(t, 'settings.testTelegram'))
|
|
1066
1278
|
),
|
|
1067
1279
|
testStatus && testStatus.message && React.createElement('div', {
|
|
1068
1280
|
className: 'dsh-cron-status-banner',
|
|
@@ -1073,28 +1285,29 @@ const React = window.React || require('react');
|
|
|
1073
1285
|
type: 'button',
|
|
1074
1286
|
className: 'dsh-cron-btn-secondary',
|
|
1075
1287
|
onClick: () => setSettingsModalOpen(false)
|
|
1076
|
-
}, '
|
|
1288
|
+
}, T_KEY(t, 'modal.cancel')),
|
|
1077
1289
|
React.createElement('button', {
|
|
1078
1290
|
type: 'submit',
|
|
1079
1291
|
className: 'dsh-cron-btn-primary'
|
|
1080
|
-
},
|
|
1292
|
+
}, T_KEY(t, 'settings.save'))
|
|
1081
1293
|
)
|
|
1082
1294
|
)
|
|
1083
1295
|
)
|
|
1084
1296
|
),
|
|
1085
|
-
|
|
1297
|
+
|
|
1298
|
+
// 3. "Create with DSH" modal
|
|
1086
1299
|
dshModalOpen && React.createElement('div', { className: 'dsh-cron-modal-overlay', onClick: () => !dshLoading && setDshModalOpen(false) },
|
|
1087
|
-
React.createElement('div', { className: 'dsh-cron-modal', onClick: (e) => e.stopPropagation() },
|
|
1088
|
-
React.createElement('div', { className: 'dsh-cron-modal-title' },
|
|
1300
|
+
React.createElement('div', { className: 'dsh-cron-modal', role: 'dialog', 'aria-modal': 'true', onClick: (e) => e.stopPropagation() },
|
|
1301
|
+
React.createElement('div', { className: 'dsh-cron-modal-title' }, T_KEY(t, 'dsh.modalTitle')),
|
|
1089
1302
|
React.createElement('div', { className: 'dsh-cron-form-group' },
|
|
1090
|
-
React.createElement('label', null,
|
|
1303
|
+
React.createElement('label', null, T_KEY(t, 'dsh.descLabel')),
|
|
1091
1304
|
React.createElement('div', { className: 'dsh-cron-chat-box' },
|
|
1092
1305
|
React.createElement('textarea', {
|
|
1093
1306
|
className: 'dsh-cron-chat-input',
|
|
1094
1307
|
rows: 4,
|
|
1095
1308
|
autoFocus: true,
|
|
1096
1309
|
disabled: dshLoading,
|
|
1097
|
-
placeholder:
|
|
1310
|
+
placeholder: T_KEY(t, 'dsh.placeholder'),
|
|
1098
1311
|
value: dshPrompt,
|
|
1099
1312
|
onKeyDown: (e) => {
|
|
1100
1313
|
if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
|
|
@@ -1105,18 +1318,15 @@ const React = window.React || require('react');
|
|
|
1105
1318
|
onChange: (e) => setDshPrompt(e.target.value)
|
|
1106
1319
|
}),
|
|
1107
1320
|
React.createElement('div', { className: 'dsh-cron-chat-foot' },
|
|
1108
|
-
React.createElement('span', { className: 'dsh-cron-chat-hint' },
|
|
1321
|
+
React.createElement('span', { className: 'dsh-cron-chat-hint' }, T_KEY(t, 'dsh.sendHint')),
|
|
1109
1322
|
React.createElement('button', {
|
|
1110
1323
|
type: 'button',
|
|
1111
1324
|
className: 'dsh-cron-send-btn',
|
|
1112
1325
|
disabled: dshLoading || !dshPrompt.trim(),
|
|
1113
|
-
title:
|
|
1326
|
+
title: T_KEY(t, 'dsh.sendTitle'),
|
|
1114
1327
|
onClick: handleStartWithDSH
|
|
1115
1328
|
},
|
|
1116
|
-
React.createElement('span',
|
|
1117
|
-
style: { display: 'flex', alignItems: 'center' },
|
|
1118
|
-
dangerouslySetInnerHTML: { __html: ICON_SEND }
|
|
1119
|
-
})
|
|
1329
|
+
React.createElement('span', svgProps(ICON_SEND))
|
|
1120
1330
|
)
|
|
1121
1331
|
)
|
|
1122
1332
|
)
|
|
@@ -1127,7 +1337,7 @@ const React = window.React || require('react');
|
|
|
1127
1337
|
className: 'dsh-cron-btn-secondary',
|
|
1128
1338
|
disabled: dshLoading,
|
|
1129
1339
|
onClick: () => setDshModalOpen(false)
|
|
1130
|
-
}, '
|
|
1340
|
+
}, T_KEY(t, 'modal.cancel'))
|
|
1131
1341
|
)
|
|
1132
1342
|
)
|
|
1133
1343
|
)
|
|
@@ -1136,6 +1346,7 @@ const React = window.React || require('react');
|
|
|
1136
1346
|
|
|
1137
1347
|
function CronSettingsCard(props) {
|
|
1138
1348
|
const { ctx, toggle } = props;
|
|
1349
|
+
const t = props.t || translate;
|
|
1139
1350
|
const [token, setToken] = React.useState('');
|
|
1140
1351
|
const [chatId, setChatId] = React.useState('');
|
|
1141
1352
|
const [notify, setNotify] = React.useState(false);
|
|
@@ -1186,7 +1397,7 @@ const React = window.React || require('react');
|
|
|
1186
1397
|
};
|
|
1187
1398
|
|
|
1188
1399
|
const testTelegram = async () => {
|
|
1189
|
-
setTestInfo({ loading: true, message: '
|
|
1400
|
+
setTestInfo({ loading: true, message: T_KEY(t, 'settings.sending') });
|
|
1190
1401
|
try {
|
|
1191
1402
|
const res = await fetch('/dsh-cron/telegram/test', {
|
|
1192
1403
|
method: 'POST',
|
|
@@ -1195,17 +1406,17 @@ const React = window.React || require('react');
|
|
|
1195
1406
|
});
|
|
1196
1407
|
const data = await res.json();
|
|
1197
1408
|
if (data && data.ok) {
|
|
1198
|
-
setTestInfo({ success: true, message:
|
|
1409
|
+
setTestInfo({ success: true, message: T_KEY(t, 'settings.testOk') });
|
|
1199
1410
|
} else {
|
|
1200
|
-
setTestInfo({ success: false, message:
|
|
1411
|
+
setTestInfo({ success: false, message: T_KEY(t, 'settings.testFail', { error: (data && data.error) || '' }) });
|
|
1201
1412
|
}
|
|
1202
1413
|
} catch (err) {
|
|
1203
|
-
setTestInfo({ success: false, message:
|
|
1414
|
+
setTestInfo({ success: false, message: T_KEY(t, 'settings.testFail', { error: err.message }) });
|
|
1204
1415
|
}
|
|
1205
1416
|
};
|
|
1206
1417
|
|
|
1207
1418
|
const testKanban = async () => {
|
|
1208
|
-
setTestInfo({ loading: true, message:
|
|
1419
|
+
setTestInfo({ loading: true, message: T_KEY(t, 'settings.checkingKanban') });
|
|
1209
1420
|
try {
|
|
1210
1421
|
const res = await fetch('/dsh-cron/kanban/test', {
|
|
1211
1422
|
method: 'POST',
|
|
@@ -1214,62 +1425,62 @@ const React = window.React || require('react');
|
|
|
1214
1425
|
});
|
|
1215
1426
|
const data = await res.json();
|
|
1216
1427
|
if (data && data.ok) {
|
|
1217
|
-
setTestInfo({ success: true, message:
|
|
1428
|
+
setTestInfo({ success: true, message: T_KEY(t, 'settings.kanbanOk') });
|
|
1218
1429
|
} else {
|
|
1219
|
-
setTestInfo({ success: false, message:
|
|
1430
|
+
setTestInfo({ success: false, message: T_KEY(t, 'settings.kanbanFail', { error: (data && data.error) || '' }) });
|
|
1220
1431
|
}
|
|
1221
1432
|
} catch (err) {
|
|
1222
|
-
setTestInfo({ success: false, message:
|
|
1433
|
+
setTestInfo({ success: false, message: T_KEY(t, 'settings.kanbanFail', { error: err.message }) });
|
|
1223
1434
|
}
|
|
1224
1435
|
};
|
|
1225
1436
|
|
|
1226
1437
|
return React.createElement('div', { className: 'dsh-cron-card' },
|
|
1227
1438
|
React.createElement('div', { className: 'dsh-cron-card-head' },
|
|
1228
|
-
React.createElement('span', { className: 'dsh-cron-card-title' },
|
|
1439
|
+
React.createElement('span', { className: 'dsh-cron-card-title' }, T_KEY(t, 'settings.cardTitle')),
|
|
1229
1440
|
React.createElement('button', {
|
|
1230
1441
|
type: 'button',
|
|
1231
1442
|
className: 'dsh-cron-btn-primary',
|
|
1232
1443
|
onClick: () => { if (toggle) toggle.set(true); }
|
|
1233
|
-
},
|
|
1444
|
+
}, T_KEY(t, 'settings.openPanel'))
|
|
1234
1445
|
),
|
|
1235
1446
|
React.createElement('div', { className: 'dsh-cron-card-desc', style: { marginBottom: '16px' } },
|
|
1236
|
-
|
|
1447
|
+
T_KEY(t, 'settings.cardDesc')
|
|
1237
1448
|
),
|
|
1238
1449
|
React.createElement('form', { onSubmit: saveSettings, style: { display: 'flex', flexDirection: 'column', gap: '12px' } },
|
|
1239
1450
|
React.createElement('div', { className: 'dsh-cron-form-group', style: { marginBottom: 0 } },
|
|
1240
|
-
React.createElement('label', null,
|
|
1451
|
+
React.createElement('label', null, T_KEY(t, 'settings.tokenLabel')),
|
|
1241
1452
|
React.createElement('input', {
|
|
1242
1453
|
type: 'password',
|
|
1243
1454
|
value: token,
|
|
1244
|
-
placeholder: hasDefault ?
|
|
1455
|
+
placeholder: hasDefault ? T_KEY(t, 'settings.tokenPlaceholderDefault') : T_KEY(t, 'settings.tokenPlaceholder'),
|
|
1245
1456
|
onChange: (e) => setToken(e.target.value)
|
|
1246
1457
|
}),
|
|
1247
|
-
hasDefault && React.createElement('div', { className: 'dsh-cron-hint' },
|
|
1458
|
+
hasDefault && React.createElement('div', { className: 'dsh-cron-hint' }, T_KEY(t, 'settings.defaultHint'))
|
|
1248
1459
|
),
|
|
1249
1460
|
React.createElement('div', { className: 'dsh-cron-form-group', style: { marginBottom: 0 } },
|
|
1250
|
-
React.createElement('label', null,
|
|
1461
|
+
React.createElement('label', null, T_KEY(t, 'settings.chatIdLabel')),
|
|
1251
1462
|
React.createElement('input', {
|
|
1252
1463
|
value: chatId,
|
|
1253
|
-
placeholder:
|
|
1464
|
+
placeholder: T_KEY(t, 'settings.chatIdPlaceholder'),
|
|
1254
1465
|
onChange: (e) => setChatId(e.target.value)
|
|
1255
1466
|
})
|
|
1256
1467
|
),
|
|
1257
1468
|
React.createElement('div', { className: 'dsh-cron-form-group', style: { marginBottom: 0 } },
|
|
1258
|
-
React.createElement('label', null,
|
|
1469
|
+
React.createElement('label', null, T_KEY(t, 'settings.kanbanUrlLabel')),
|
|
1259
1470
|
React.createElement('input', {
|
|
1260
1471
|
value: kanbanUrl,
|
|
1261
1472
|
placeholder: 'http://127.0.0.1:3000',
|
|
1262
1473
|
onChange: (e) => setKanbanUrl(e.target.value)
|
|
1263
1474
|
})
|
|
1264
1475
|
),
|
|
1265
|
-
React.createElement('div', { style: { background: 'rgba(255,255,255,0.02)', border: '1px solid #2e2e2e', borderRadius: '6px', padding: '10px 12px', display: 'flex', flexDirection: 'column', gap: '8px' } },
|
|
1476
|
+
React.createElement('div', { style: { background: 'var(--dsw-alias-bg-layer-3, rgba(255,255,255,0.02))', border: '1px solid var(--dsw-alias-border-l2, #2e2e2e)', borderRadius: '6px', padding: '10px 12px', display: 'flex', flexDirection: 'column', gap: '8px' } },
|
|
1266
1477
|
React.createElement('label', { className: 'dsh-cron-checkbox-row' },
|
|
1267
1478
|
React.createElement('input', {
|
|
1268
1479
|
type: 'checkbox',
|
|
1269
1480
|
checked: notify,
|
|
1270
1481
|
onChange: (e) => setNotify(e.target.checked)
|
|
1271
1482
|
}),
|
|
1272
|
-
React.createElement('span', null,
|
|
1483
|
+
React.createElement('span', null, T_KEY(t, 'settings.globalNotify'))
|
|
1273
1484
|
),
|
|
1274
1485
|
React.createElement('label', { className: 'dsh-cron-checkbox-row' },
|
|
1275
1486
|
React.createElement('input', {
|
|
@@ -1277,7 +1488,7 @@ const React = window.React || require('react');
|
|
|
1277
1488
|
checked: onlyFailure,
|
|
1278
1489
|
onChange: (e) => setOnlyFailure(e.target.checked)
|
|
1279
1490
|
}),
|
|
1280
|
-
React.createElement('span', null,
|
|
1491
|
+
React.createElement('span', null, T_KEY(t, 'settings.globalOnlyFailure'))
|
|
1281
1492
|
)
|
|
1282
1493
|
),
|
|
1283
1494
|
testInfo && testInfo.message && React.createElement('div', {
|
|
@@ -1290,19 +1501,19 @@ const React = window.React || require('react');
|
|
|
1290
1501
|
type: 'submit',
|
|
1291
1502
|
className: 'dsh-cron-btn-primary',
|
|
1292
1503
|
style: { minWidth: '140px' }
|
|
1293
|
-
}, saved ?
|
|
1504
|
+
}, saved ? T_KEY(t, 'settings.saved') : T_KEY(t, 'settings.save')),
|
|
1294
1505
|
React.createElement('button', {
|
|
1295
1506
|
type: 'button',
|
|
1296
1507
|
className: 'dsh-cron-btn-secondary',
|
|
1297
1508
|
onClick: testTelegram,
|
|
1298
1509
|
disabled: testInfo?.loading
|
|
1299
|
-
},
|
|
1510
|
+
}, T_KEY(t, 'settings.testTelegram')),
|
|
1300
1511
|
React.createElement('button', {
|
|
1301
1512
|
type: 'button',
|
|
1302
1513
|
className: 'dsh-cron-btn-secondary',
|
|
1303
1514
|
onClick: testKanban,
|
|
1304
1515
|
disabled: testInfo?.loading
|
|
1305
|
-
},
|
|
1516
|
+
}, T_KEY(t, 'settings.testKanban'))
|
|
1306
1517
|
)
|
|
1307
1518
|
)
|
|
1308
1519
|
);
|
|
@@ -1320,7 +1531,7 @@ const React = window.React || require('react');
|
|
|
1320
1531
|
const entry = target.cloneNode(true);
|
|
1321
1532
|
entry.className = String(target.className) + ' dsh-cron-entry-clone';
|
|
1322
1533
|
entry.setAttribute(ENTRY_ATTR, '');
|
|
1323
|
-
entry.setAttribute('data-dsh-plugin', 'cron');
|
|
1534
|
+
entry.setAttribute('data-dsh-plugin', 'dsh-cron');
|
|
1324
1535
|
entry.setAttribute('data-dsh-part', 'sidebar-entry');
|
|
1325
1536
|
entry.setAttribute('aria-label', label);
|
|
1326
1537
|
entry.removeAttribute('id');
|
|
@@ -1398,13 +1609,13 @@ const React = window.React || require('react');
|
|
|
1398
1609
|
if (column === null) return;
|
|
1399
1610
|
container = document.createElement('div');
|
|
1400
1611
|
container.setAttribute(VIEW_ATTR, '');
|
|
1401
|
-
container.setAttribute('data-dsh-plugin', 'cron');
|
|
1612
|
+
container.setAttribute('data-dsh-plugin', 'dsh-cron');
|
|
1402
1613
|
column.appendChild(container);
|
|
1403
1614
|
if (ReactDOM && typeof ReactDOM.createRoot === 'function') {
|
|
1404
1615
|
root = ReactDOM.createRoot(container);
|
|
1405
|
-
root.render(React.createElement(CronScreen, { ctx, toggle, onClose: () => toggle.set(false) }));
|
|
1616
|
+
root.render(React.createElement(CronScreen, { ctx, toggle, onClose: () => toggle.set(false), t: translate }));
|
|
1406
1617
|
} else if (ReactDOM && typeof ReactDOM.render === 'function') {
|
|
1407
|
-
ReactDOM.render(React.createElement(CronScreen, { ctx, toggle, onClose: () => toggle.set(false) }), container);
|
|
1618
|
+
ReactDOM.render(React.createElement(CronScreen, { ctx, toggle, onClose: () => toggle.set(false), t: translate }), container);
|
|
1408
1619
|
}
|
|
1409
1620
|
};
|
|
1410
1621
|
|
|
@@ -1447,43 +1658,88 @@ const React = window.React || require('react');
|
|
|
1447
1658
|
};
|
|
1448
1659
|
}
|
|
1449
1660
|
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1661
|
+
/**
|
|
1662
|
+
* Register a card into a DSH mount point. Mount points are populated via
|
|
1663
|
+
* ctx.slots.inject(mount, cb) — a direct slots.register does not throw but
|
|
1664
|
+
* also never appears in the target surface (settings plugins tab, header),
|
|
1665
|
+
* so the inject contract is mandatory (#85).
|
|
1666
|
+
*/
|
|
1667
|
+
function registerIntoMount(ctx, mountName, entry, component) {
|
|
1668
|
+
if (!ctx || !ctx.slots || typeof ctx.slots.inject !== 'function') return false;
|
|
1669
|
+
try {
|
|
1670
|
+
return !!ctx.slots.inject(mountName, () => {
|
|
1671
|
+
ctx.slots.register(entry, component);
|
|
1672
|
+
});
|
|
1673
|
+
} catch (err) {
|
|
1674
|
+
console.warn('[dsh-cron] slot injection skipped:', mountName, '-', err && err.message);
|
|
1675
|
+
return false;
|
|
1461
1676
|
}
|
|
1462
|
-
return undefined;
|
|
1463
1677
|
}
|
|
1464
1678
|
|
|
1465
1679
|
function apply(ctx) {
|
|
1466
1680
|
ensureStyles();
|
|
1681
|
+
|
|
1682
|
+
// Register the English canonical strings; rebinding the translator lets
|
|
1683
|
+
// the translation plugin drive the active language at runtime (#87).
|
|
1684
|
+
try {
|
|
1685
|
+
if (ctx.locale && typeof ctx.locale.register === 'function') {
|
|
1686
|
+
ctx.locale.register(NS, STRINGS);
|
|
1687
|
+
}
|
|
1688
|
+
} catch (e) {
|
|
1689
|
+
// Re-registration in the same session throws — the dictionary is live already.
|
|
1690
|
+
}
|
|
1691
|
+
try {
|
|
1692
|
+
if (ctx.locale && typeof ctx.locale.bind === 'function') {
|
|
1693
|
+
translate = ctx.locale.bind(NS);
|
|
1694
|
+
}
|
|
1695
|
+
} catch (e) {}
|
|
1696
|
+
|
|
1467
1697
|
const toggle = createToggle();
|
|
1468
1698
|
|
|
1469
|
-
const
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1699
|
+
const cardOk = registerIntoMount(ctx, 'settings.plugin.item', {
|
|
1700
|
+
name: 'settings.plugin.item',
|
|
1701
|
+
key: NS,
|
|
1702
|
+
locale: NS,
|
|
1703
|
+
inject: () => ({ ctx, toggle }),
|
|
1704
|
+
}, CronSettingsCard);
|
|
1705
|
+
if (!cardOk) {
|
|
1706
|
+
// Fallback for builds without the settings.plugin.item mount: a
|
|
1707
|
+
// dedicated section so the settings are never lost (#85).
|
|
1708
|
+
registerIntoMount(ctx, 'settings.section', {
|
|
1709
|
+
name: 'settings.section',
|
|
1710
|
+
id: PLUGIN_ID,
|
|
1711
|
+
order: 34,
|
|
1712
|
+
locale: NS,
|
|
1713
|
+
label: () => translate('settings.sectionLabel'),
|
|
1714
|
+
inject: () => ({ ctx, toggle }),
|
|
1715
|
+
}, CronSettingsCard);
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
const chipOk = registerIntoMount(ctx, 'conversation.session.header.utilities', {
|
|
1719
|
+
name: 'conversation.session.header.utilities',
|
|
1720
|
+
id: PLUGIN_ID + '.chip',
|
|
1721
|
+
order: 27,
|
|
1722
|
+
locale: NS,
|
|
1723
|
+
inject: () => ({ ctx, toggle }),
|
|
1724
|
+
}, CronChip);
|
|
1725
|
+
const cardSlot = cardOk ? 'settings.plugin.item' : 'settings.section';
|
|
1726
|
+
const chipSlot = chipOk ? 'conversation.session.header.utilities' : undefined;
|
|
1727
|
+
|
|
1728
|
+
function CronChip(props) {
|
|
1729
|
+
const chipT = props.t || translate;
|
|
1730
|
+
return React.createElement('button', {
|
|
1731
|
+
type: 'button',
|
|
1732
|
+
style: { background: 'none', border: 'none', color: 'var(--dsw-alias-label-secondary, #999)', cursor: 'pointer', padding: '4px', display: 'inline-flex', alignItems: 'center' },
|
|
1733
|
+
title: T_KEY(chipT, 'chip.title'),
|
|
1734
|
+
'aria-label': T_KEY(chipT, 'chip.title'),
|
|
1735
|
+
onClick: () => toggle.toggle(),
|
|
1736
|
+
dangerouslySetInnerHTML: { __html: ICON_TIMER }
|
|
1737
|
+
});
|
|
1738
|
+
}
|
|
1483
1739
|
|
|
1484
1740
|
ctx.effect(() => {
|
|
1485
1741
|
const off = [
|
|
1486
|
-
mountSidebarEntry(toggle, '
|
|
1742
|
+
mountSidebarEntry(toggle, translate('sidebar.label')),
|
|
1487
1743
|
mountCronScreen(ctx, toggle)
|
|
1488
1744
|
];
|
|
1489
1745
|
return () => { for (const dispose of off) dispose(); };
|