@goodandready/dsh-cron 0.1.24 → 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 -36
- package/lib/chat-start.js +2 -2
- package/lib/client.js +647 -409
- 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 +48 -34
- package/lib/scheduler.js +33 -32
- package/lib/store.js +1 -1
- 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
|
}
|
|
@@ -370,7 +534,7 @@ const React = window.React || require('react');
|
|
|
370
534
|
|
|
371
535
|
const handleRunNow = async (id) => {
|
|
372
536
|
try {
|
|
373
|
-
setTasks(prev => prev.map(
|
|
537
|
+
setTasks(prev => prev.map(t2 => t2.id === id ? { ...t2, lastStatus: 'running' } : t2));
|
|
374
538
|
await fetch('/dsh-cron/tasks/' + encodeURIComponent(id) + '/run', { method: 'POST' });
|
|
375
539
|
setTimeout(() => fetchTasks(), 1500);
|
|
376
540
|
} catch (err) {
|
|
@@ -379,7 +543,7 @@ const React = window.React || require('react');
|
|
|
379
543
|
};
|
|
380
544
|
|
|
381
545
|
const handleDelete = async (id) => {
|
|
382
|
-
if (!confirm(
|
|
546
|
+
if (!confirm(T_KEY(t, 'confirm.delete'))) return;
|
|
383
547
|
try {
|
|
384
548
|
await fetch('/dsh-cron/tasks/' + encodeURIComponent(id), { method: 'DELETE' });
|
|
385
549
|
fetchTasks();
|
|
@@ -422,15 +586,15 @@ const React = window.React || require('react');
|
|
|
422
586
|
if (data && data.ok) {
|
|
423
587
|
setSettingsModalOpen(false);
|
|
424
588
|
} else {
|
|
425
|
-
alert('
|
|
589
|
+
alert(T_KEY(t, 'settings.saveError', { error: (data && data.error) || T_KEY(t, 'settings.saveErrorUnknown') }));
|
|
426
590
|
}
|
|
427
591
|
} catch (err) {
|
|
428
|
-
alert('
|
|
592
|
+
alert(T_KEY(t, 'settings.saveError', { error: err.message }));
|
|
429
593
|
}
|
|
430
594
|
};
|
|
431
595
|
|
|
432
596
|
const handleTestTelegram = async () => {
|
|
433
|
-
setTestStatus({ loading: true, message:
|
|
597
|
+
setTestStatus({ loading: true, message: T_KEY(t, 'settings.sending') });
|
|
434
598
|
try {
|
|
435
599
|
const res = await fetch('/dsh-cron/telegram/test', {
|
|
436
600
|
method: 'POST',
|
|
@@ -442,12 +606,12 @@ const React = window.React || require('react');
|
|
|
442
606
|
});
|
|
443
607
|
const data = await res.json();
|
|
444
608
|
if (data && data.ok) {
|
|
445
|
-
setTestStatus({ loading: false, success: true, message:
|
|
609
|
+
setTestStatus({ loading: false, success: true, message: T_KEY(t, 'settings.testOk') });
|
|
446
610
|
} else {
|
|
447
|
-
setTestStatus({ loading: false, success: false, message:
|
|
611
|
+
setTestStatus({ loading: false, success: false, message: T_KEY(t, 'settings.testFail', { error: (data && data.error) || '' }) });
|
|
448
612
|
}
|
|
449
613
|
} catch (err) {
|
|
450
|
-
setTestStatus({ loading: false, success: false, message:
|
|
614
|
+
setTestStatus({ loading: false, success: false, message: T_KEY(t, 'settings.testFail', { error: err.message }) });
|
|
451
615
|
}
|
|
452
616
|
};
|
|
453
617
|
|
|
@@ -544,13 +708,17 @@ const React = window.React || require('react');
|
|
|
544
708
|
overlapPolicy: formOverlapPolicy,
|
|
545
709
|
kanbanMode: formKanbanMode,
|
|
546
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';
|
|
547
715
|
const res = await fetch('/dsh-cron/tasks', {
|
|
548
716
|
method: 'POST',
|
|
549
|
-
headers
|
|
717
|
+
headers,
|
|
550
718
|
body: JSON.stringify(payload)
|
|
551
719
|
});
|
|
552
720
|
const data = await res.json();
|
|
553
|
-
if (!data.ok) { alert(data.error || '
|
|
721
|
+
if (!data.ok) { alert(data.error || T_KEY(t, 'settings.saveError', { error: '' })); return; }
|
|
554
722
|
setManualModalOpen(false);
|
|
555
723
|
fetchTasks();
|
|
556
724
|
} catch (err) { alert(err.message); }
|
|
@@ -575,7 +743,7 @@ const React = window.React || require('react');
|
|
|
575
743
|
});
|
|
576
744
|
const data = await res.json();
|
|
577
745
|
if (!data.ok || !data.sessionId) {
|
|
578
|
-
alert(data.error || '
|
|
746
|
+
alert(T_KEY(t, 'dsh.startFailed', { error: data.error || '' }));
|
|
579
747
|
setDshLoading(false);
|
|
580
748
|
return;
|
|
581
749
|
}
|
|
@@ -586,61 +754,63 @@ const React = window.React || require('react');
|
|
|
586
754
|
|
|
587
755
|
await openSession(ctx, data.sessionId);
|
|
588
756
|
} catch (err) {
|
|
589
|
-
alert('
|
|
757
|
+
alert(T_KEY(t, 'dsh.launchError', { error: err.message }));
|
|
590
758
|
} finally {
|
|
591
759
|
setDshLoading(false);
|
|
592
760
|
}
|
|
593
761
|
};
|
|
594
762
|
|
|
763
|
+
const iconHtml = (html) => ({ dangerouslySetInnerHTML: { __html: html } });
|
|
764
|
+
const svgProps = (html) => ({ style: { display: 'flex', alignItems: 'center' }, ...iconHtml(html) });
|
|
765
|
+
|
|
595
766
|
return React.createElement('div', { className: 'dsh-cron-overlay' },
|
|
596
767
|
React.createElement('div', { className: 'dsh-cron-container' },
|
|
597
768
|
React.createElement('div', { className: 'dsh-cron-top-bar' },
|
|
598
769
|
React.createElement('button', {
|
|
599
770
|
type: 'button',
|
|
600
771
|
className: 'dsh-cron-back-btn',
|
|
601
|
-
title:
|
|
772
|
+
title: T_KEY(t, 'panel.back'),
|
|
602
773
|
onClick: () => { if (onClose) onClose(); else toggle.set(false); }
|
|
603
774
|
},
|
|
604
|
-
React.createElement('span',
|
|
605
|
-
|
|
606
|
-
dangerouslySetInnerHTML: { __html: BACK_ICON }
|
|
607
|
-
}),
|
|
608
|
-
React.createElement('span', null, 'Назад к чату')
|
|
775
|
+
React.createElement('span', svgProps(BACK_ICON)),
|
|
776
|
+
React.createElement('span', null, T_KEY(t, 'panel.back'))
|
|
609
777
|
)
|
|
610
778
|
),
|
|
611
779
|
React.createElement('div', { className: 'dsh-cron-header' },
|
|
612
780
|
React.createElement('div', null,
|
|
613
|
-
React.createElement('div', { className: 'dsh-cron-title' },
|
|
614
|
-
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'))
|
|
615
783
|
),
|
|
616
784
|
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: '10px' } },
|
|
617
785
|
React.createElement('button', {
|
|
618
786
|
type: 'button',
|
|
619
787
|
className: 'dsh-cron-btn-secondary',
|
|
620
788
|
style: { display: 'flex', alignItems: 'center', gap: '6px', height: '36px', padding: '0 14px' },
|
|
621
|
-
title:
|
|
789
|
+
title: T_KEY(t, 'actions.settings'),
|
|
622
790
|
onClick: openSettingsModal
|
|
623
791
|
},
|
|
624
|
-
React.createElement('span', { style: { display: 'flex', alignItems: 'center', color: '
|
|
625
|
-
|
|
792
|
+
React.createElement('span', { style: { display: 'flex', alignItems: 'center', color: 'var(--dsh-cron-info)' }, ...iconHtml(ICON_TELEGRAM) }),
|
|
793
|
+
T_KEY(t, 'actions.settings')
|
|
626
794
|
),
|
|
627
795
|
React.createElement('div', { style: { position: 'relative' } },
|
|
628
796
|
React.createElement('button', { className: 'dsh-cron-create-btn', onClick: () => setDropdownOpen(!dropdownOpen) },
|
|
629
|
-
'
|
|
630
|
-
React.createElement('span',
|
|
797
|
+
T_KEY(t, 'actions.create'),
|
|
798
|
+
React.createElement('span', iconHtml(ICON_CHEVRON_DOWN))
|
|
631
799
|
),
|
|
632
800
|
dropdownOpen && React.createElement('div', { className: 'dsh-cron-dropdown' },
|
|
633
|
-
React.createElement('button', { className: 'dsh-cron-dropdown-item', onClick: openDshModal },
|
|
634
|
-
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'))
|
|
635
803
|
)
|
|
636
804
|
)
|
|
637
805
|
)
|
|
638
806
|
),
|
|
639
807
|
React.createElement('div', { className: 'dsh-cron-search-bar' },
|
|
640
|
-
React.createElement('span', { className: 'dsh-cron-search-icon',
|
|
808
|
+
React.createElement('span', { className: 'dsh-cron-search-icon', ...iconHtml(ICON_SEARCH) }),
|
|
641
809
|
React.createElement('input', {
|
|
642
810
|
className: 'dsh-cron-search-input',
|
|
643
|
-
|
|
811
|
+
type: 'search',
|
|
812
|
+
'aria-label': T_KEY(t, 'search.placeholder'),
|
|
813
|
+
placeholder: T_KEY(t, 'search.placeholder'),
|
|
644
814
|
value: query,
|
|
645
815
|
onChange: (e) => setQuery(e.target.value)
|
|
646
816
|
})
|
|
@@ -648,29 +818,33 @@ const React = window.React || require('react');
|
|
|
648
818
|
stats && React.createElement('div', { className: 'dsh-cron-stats-bar' },
|
|
649
819
|
React.createElement('div', { className: 'dsh-cron-stat-card' },
|
|
650
820
|
React.createElement('div', { className: 'dsh-cron-stat-val' }, stats.activeTasks || 0),
|
|
651
|
-
React.createElement('div', { className: 'dsh-cron-stat-lbl' },
|
|
821
|
+
React.createElement('div', { className: 'dsh-cron-stat-lbl' }, T_KEY(t, 'stats.activeTasks'))
|
|
652
822
|
),
|
|
653
823
|
React.createElement('div', { className: 'dsh-cron-stat-card' },
|
|
654
824
|
React.createElement('div', { className: 'dsh-cron-stat-val' }, stats.totalRuns || 0),
|
|
655
|
-
React.createElement('div', { className: 'dsh-cron-stat-lbl' },
|
|
825
|
+
React.createElement('div', { className: 'dsh-cron-stat-lbl' }, T_KEY(t, 'stats.totalRuns'))
|
|
656
826
|
),
|
|
657
827
|
React.createElement('div', { className: 'dsh-cron-stat-card' },
|
|
658
828
|
React.createElement('div', { className: 'dsh-cron-stat-val' }, (stats.totalTokens || 0).toLocaleString()),
|
|
659
|
-
React.createElement('div', { className: 'dsh-cron-stat-lbl' },
|
|
829
|
+
React.createElement('div', { className: 'dsh-cron-stat-lbl' }, T_KEY(t, 'stats.totalTokens'))
|
|
660
830
|
),
|
|
661
831
|
React.createElement('div', { className: 'dsh-cron-stat-card' },
|
|
662
|
-
React.createElement('div', { className: 'dsh-cron-stat-val', style: { color: '
|
|
663
|
-
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'))
|
|
664
834
|
)
|
|
665
835
|
),
|
|
666
836
|
React.createElement('div', { className: 'dsh-cron-tabs' },
|
|
667
|
-
['all', 'active', 'paused', 'completed'].map((
|
|
837
|
+
['all', 'active', 'paused', 'completed'].map((tabName) =>
|
|
668
838
|
React.createElement('button', {
|
|
669
|
-
key:
|
|
839
|
+
key: tabName,
|
|
670
840
|
className: 'dsh-cron-tab',
|
|
671
|
-
'data-active': tab ===
|
|
672
|
-
onClick: () => setTab(
|
|
673
|
-
},
|
|
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'))
|
|
674
848
|
)
|
|
675
849
|
),
|
|
676
850
|
fetchError ? React.createElement('div', {
|
|
@@ -678,50 +852,59 @@ const React = window.React || require('react');
|
|
|
678
852
|
'data-type': 'error',
|
|
679
853
|
style: { margin: '8px 0', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }
|
|
680
854
|
},
|
|
681
|
-
React.createElement('span', null, '
|
|
855
|
+
React.createElement('span', null, T_KEY(t, 'banner.loadError', { error: fetchError })),
|
|
682
856
|
React.createElement('button', {
|
|
683
857
|
type: 'button',
|
|
684
|
-
className: 'dsh-cron-btn',
|
|
858
|
+
className: 'dsh-cron-btn-secondary',
|
|
685
859
|
style: { marginLeft: '8px', padding: '2px 8px', fontSize: '12px' },
|
|
686
860
|
onClick: () => fetchTasks()
|
|
687
|
-
}, '
|
|
861
|
+
}, T_KEY(t, 'banner.retry'))
|
|
688
862
|
) : null,
|
|
689
863
|
React.createElement('div', { className: 'dsh-cron-task-list' },
|
|
690
864
|
loading && tasks.length === 0
|
|
691
|
-
? 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'))
|
|
692
866
|
: tasks.length === 0
|
|
693
|
-
? 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'))
|
|
694
868
|
: tasks.map((task) =>
|
|
695
869
|
React.createElement('div', {
|
|
696
870
|
key: task.id,
|
|
697
871
|
className: 'dsh-cron-task-item',
|
|
698
|
-
|
|
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
|
+
}
|
|
699
882
|
},
|
|
700
883
|
React.createElement('button', {
|
|
701
884
|
type: 'button',
|
|
702
885
|
className: 'dsh-cron-task-status-btn',
|
|
703
886
|
'data-active': task.status === 'active' ? 'true' : undefined,
|
|
704
|
-
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'),
|
|
705
889
|
onClick: (e) => { e.stopPropagation(); handleToggleTask(task.id); }
|
|
706
|
-
}, React.createElement('span',
|
|
890
|
+
}, React.createElement('span', svgProps(task.status === 'active' ? ICON_PAUSE : ICON_PLAY))),
|
|
707
891
|
React.createElement('div', { className: 'dsh-cron-task-info' },
|
|
708
892
|
React.createElement('div', { className: 'dsh-cron-task-title' },
|
|
709
893
|
task.title,
|
|
710
894
|
React.createElement('span', {
|
|
711
895
|
className: 'dsh-cron-type-tag',
|
|
712
|
-
style:
|
|
713
|
-
background:
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
}, 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')),
|
|
717
900
|
task.model ? React.createElement('span', { className: 'dsh-cron-task-model-tag' }, task.model.split('/').pop()) : null,
|
|
718
|
-
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,
|
|
719
902
|
task.totalCostUsd > 0 ? React.createElement('span', { className: 'dsh-cron-cost-tag' }, '$' + task.totalCostUsd.toFixed(4)) : null,
|
|
720
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
|
|
721
904
|
),
|
|
722
905
|
React.createElement('div', { className: 'dsh-cron-task-sched' },
|
|
723
906
|
task.scheduleText || task.schedule,
|
|
724
|
-
task.nextRunAt ?
|
|
907
|
+
task.nextRunAt ? T_KEY(t, 'list.next', { time: new Date(task.nextRunAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) }) : ''
|
|
725
908
|
),
|
|
726
909
|
task.prompt ? React.createElement('div', {
|
|
727
910
|
className: 'dsh-cron-task-prompt-preview',
|
|
@@ -735,37 +918,45 @@ const React = window.React || require('react');
|
|
|
735
918
|
React.createElement('button', {
|
|
736
919
|
type: 'button',
|
|
737
920
|
className: 'dsh-cron-icon-btn',
|
|
738
|
-
title:
|
|
921
|
+
title: T_KEY(t, 'actions.runNow'),
|
|
739
922
|
onClick: () => handleRunNow(task.id)
|
|
740
|
-
}, '
|
|
923
|
+
}, T_KEY(t, 'actions.runNowShort')),
|
|
741
924
|
React.createElement('button', {
|
|
742
925
|
type: 'button',
|
|
743
926
|
className: 'dsh-cron-icon-btn',
|
|
744
|
-
title:
|
|
927
|
+
title: T_KEY(t, 'actions.edit'),
|
|
928
|
+
'aria-label': T_KEY(t, 'actions.edit'),
|
|
745
929
|
onClick: () => openEditModal(task)
|
|
746
|
-
}, React.createElement('span',
|
|
930
|
+
}, React.createElement('span', svgProps(ICON_EDIT))),
|
|
747
931
|
React.createElement('button', {
|
|
748
932
|
type: 'button',
|
|
749
933
|
className: 'dsh-cron-icon-btn',
|
|
750
|
-
title:
|
|
934
|
+
title: T_KEY(t, 'actions.delete'),
|
|
935
|
+
'aria-label': T_KEY(t, 'actions.delete'),
|
|
751
936
|
onClick: () => handleDelete(task.id)
|
|
752
|
-
}, React.createElement('span',
|
|
937
|
+
}, React.createElement('span', svgProps(ICON_TRASH)))
|
|
753
938
|
)
|
|
754
939
|
)
|
|
755
940
|
)
|
|
756
941
|
),
|
|
757
|
-
React.createElement('div', { className: 'dsh-cron-recs-title' },
|
|
942
|
+
React.createElement('div', { className: 'dsh-cron-recs-title' }, T_KEY(t, 'recs.title')),
|
|
758
943
|
React.createElement('div', { className: 'dsh-cron-recs-list' },
|
|
759
944
|
recs.map((rec) =>
|
|
760
945
|
React.createElement('div', {
|
|
761
946
|
key: rec.id,
|
|
762
947
|
className: 'dsh-cron-rec-card',
|
|
763
|
-
|
|
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
|
+
}
|
|
764
957
|
},
|
|
765
958
|
React.createElement('div', { className: 'dsh-cron-rec-icon' },
|
|
766
|
-
React.createElement('span',
|
|
767
|
-
__html: rec.icon === 'bell' ? ICON_BELL : rec.icon === 'clipboard' ? ICON_CLIPBOARD : ICON_ACTIVITY
|
|
768
|
-
} })
|
|
959
|
+
React.createElement('span', iconHtml(rec.icon === 'bell' ? ICON_BELL : rec.icon === 'clipboard' ? ICON_CLIPBOARD : ICON_ACTIVITY))
|
|
769
960
|
),
|
|
770
961
|
React.createElement('div', { className: 'dsh-cron-rec-content' },
|
|
771
962
|
React.createElement('div', { className: 'dsh-cron-rec-title' },
|
|
@@ -784,285 +975,288 @@ const React = window.React || require('react');
|
|
|
784
975
|
React.createElement('div', {
|
|
785
976
|
className: 'dsh-cron-modal',
|
|
786
977
|
style: { maxWidth: '640px' },
|
|
978
|
+
role: 'dialog',
|
|
979
|
+
'aria-modal': 'true',
|
|
787
980
|
onClick: (e) => e.stopPropagation()
|
|
788
981
|
},
|
|
789
982
|
React.createElement('div', { className: 'dsh-cron-modal-head', style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: formId ? '12px' : '18px' } },
|
|
790
|
-
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')),
|
|
791
984
|
selectedTaskMeta && React.createElement('span', {
|
|
792
985
|
style: {
|
|
793
986
|
fontSize: '12px',
|
|
794
987
|
padding: '3px 8px',
|
|
795
988
|
borderRadius: '6px',
|
|
796
|
-
background: selectedTaskMeta.status === 'active' ? '
|
|
797
|
-
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)'
|
|
798
991
|
}
|
|
799
|
-
}, selectedTaskMeta.status === 'active' ?
|
|
992
|
+
}, selectedTaskMeta.status === 'active' ? T_KEY(t, 'modal.statusActive') : T_KEY(t, 'modal.statusPaused'))
|
|
800
993
|
),
|
|
801
994
|
|
|
802
995
|
formId && React.createElement('div', { className: 'dsh-cron-modal-nav' },
|
|
803
996
|
React.createElement('button', {
|
|
804
997
|
type: 'button',
|
|
805
998
|
className: 'dsh-cron-modal-tab',
|
|
806
|
-
'data-active': modalTab === 'params',
|
|
999
|
+
'data-active': modalTab === 'params' ? 'true' : undefined,
|
|
807
1000
|
onClick: () => setModalTab('params')
|
|
808
|
-
}, '
|
|
1001
|
+
}, T_KEY(t, 'modal.params')),
|
|
809
1002
|
React.createElement('button', {
|
|
810
1003
|
type: 'button',
|
|
811
1004
|
className: 'dsh-cron-modal-tab',
|
|
812
|
-
'data-active': modalTab === 'history',
|
|
1005
|
+
'data-active': modalTab === 'history' ? 'true' : undefined,
|
|
813
1006
|
onClick: () => { setModalTab('history'); if (formId) loadTaskHistory(formId); }
|
|
814
|
-
}, '
|
|
1007
|
+
}, T_KEY(t, 'modal.history', { count: taskHistory.length || 0 }))
|
|
815
1008
|
),
|
|
816
1009
|
|
|
817
1010
|
modalTab === 'history' && formId ? React.createElement('div', null,
|
|
818
|
-
historyLoading ? React.createElement('div', { className: 'dsh-cron-history-empty' },
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
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
|
+
))
|
|
835
1032
|
),
|
|
836
|
-
run.output && React.createElement('div', { className: 'dsh-cron-history-output' }, run.output),
|
|
837
|
-
run.error && React.createElement('div', { className: 'dsh-cron-history-output', style: { color: '#ef4444', borderColor: '#552222' } }, run.error)
|
|
838
|
-
))
|
|
839
|
-
),
|
|
840
1033
|
React.createElement('div', { className: 'dsh-cron-modal-foot', style: { marginTop: '18px' } },
|
|
841
1034
|
React.createElement('button', {
|
|
842
1035
|
type: 'button',
|
|
843
1036
|
className: 'dsh-cron-btn-secondary',
|
|
844
1037
|
onClick: () => { if (formId) loadTaskHistory(formId); }
|
|
845
|
-
},
|
|
1038
|
+
}, T_KEY(t, 'history.refresh')),
|
|
846
1039
|
React.createElement('button', {
|
|
847
1040
|
type: 'button',
|
|
848
1041
|
className: 'dsh-cron-btn-primary',
|
|
849
1042
|
onClick: () => setManualModalOpen(false)
|
|
850
|
-
}, '
|
|
1043
|
+
}, T_KEY(t, 'modal.close'))
|
|
851
1044
|
)
|
|
852
1045
|
) :
|
|
853
1046
|
|
|
854
|
-
|
|
855
|
-
React.createElement('div', { className: 'dsh-cron-form-group' },
|
|
856
|
-
React.createElement('label', null, 'Название задачи'),
|
|
857
|
-
React.createElement('input', {
|
|
858
|
-
required: true,
|
|
859
|
-
value: formTitle,
|
|
860
|
-
placeholder: 'Например: dsh-tags-watch',
|
|
861
|
-
onChange: (e) => setFormTitle(e.target.value)
|
|
862
|
-
})
|
|
863
|
-
),
|
|
864
|
-
React.createElement('div', { className: 'dsh-cron-form-row', style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '12px' } },
|
|
1047
|
+
React.createElement('form', { onSubmit: handleSaveManual },
|
|
865
1048
|
React.createElement('div', { className: 'dsh-cron-form-group' },
|
|
866
|
-
React.createElement('label', null,
|
|
867
|
-
React.createElement('select', {
|
|
868
|
-
value: formType,
|
|
869
|
-
onChange: (e) => setFormType(e.target.value)
|
|
870
|
-
},
|
|
871
|
-
React.createElement('option', { value: 'llm' }, 'LLM (Агент с нейросетью)'),
|
|
872
|
-
React.createElement('option', { value: 'script' }, 'NO-LLM (Shell-команда/Скрипт)')
|
|
873
|
-
)
|
|
874
|
-
),
|
|
875
|
-
React.createElement('div', { className: 'dsh-cron-form-group' },
|
|
876
|
-
React.createElement('label', null, 'Расписание (cron или интервал)'),
|
|
1049
|
+
React.createElement('label', null, T_KEY(t, 'form.nameLabel')),
|
|
877
1050
|
React.createElement('input', {
|
|
878
1051
|
required: true,
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
)
|
|
884
|
-
),
|
|
885
|
-
React.createElement('div', { className: 'dsh-cron-form-row', style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '12px' } },
|
|
886
|
-
React.createElement('div', { className: 'dsh-cron-form-group' },
|
|
887
|
-
React.createElement('label', null, 'Таймаут выполнения (секунды)'),
|
|
888
|
-
React.createElement('input', {
|
|
889
|
-
type: 'number',
|
|
890
|
-
min: 1,
|
|
891
|
-
value: formTimeoutSeconds,
|
|
892
|
-
placeholder: '1800',
|
|
893
|
-
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)
|
|
894
1056
|
})
|
|
895
1057
|
),
|
|
896
|
-
React.createElement('div', { className: 'dsh-cron-form-
|
|
897
|
-
React.createElement('
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
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
|
+
})
|
|
905
1077
|
)
|
|
906
|
-
)
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
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
|
+
)
|
|
928
1100
|
)
|
|
929
1101
|
),
|
|
930
1102
|
React.createElement('div', { className: 'dsh-cron-form-group' },
|
|
931
|
-
React.createElement('label', null,
|
|
1103
|
+
React.createElement('label', null, T_KEY(t, 'form.kanbanLabel')),
|
|
932
1104
|
React.createElement('select', {
|
|
933
|
-
value:
|
|
934
|
-
onChange: (e) =>
|
|
1105
|
+
value: formKanbanMode,
|
|
1106
|
+
onChange: (e) => setFormKanbanMode(e.target.value)
|
|
935
1107
|
},
|
|
936
|
-
React.createElement('option', { value: '' },
|
|
937
|
-
|
|
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'))
|
|
938
1111
|
)
|
|
939
|
-
)
|
|
940
|
-
),
|
|
941
|
-
React.createElement('div', { className: 'dsh-cron-form-group' },
|
|
942
|
-
React.createElement('label', null, formType === 'script' ? 'Shell-команда или скрипт' : 'Инструкция / Промпт для агента'),
|
|
943
|
-
React.createElement('textarea', {
|
|
944
|
-
required: true,
|
|
945
|
-
rows: 9,
|
|
946
|
-
style: { fontFamily: 'monospace', fontSize: '12.5px', lineHeight: '1.45' },
|
|
947
|
-
value: formPrompt,
|
|
948
|
-
placeholder: formType === 'script' ? 'curl -fsSL https://... || exit 1' : 'Опишите, что именно агент должен сделать...',
|
|
949
|
-
onChange: (e) => setFormPrompt(e.target.value)
|
|
950
|
-
})
|
|
951
|
-
),
|
|
952
|
-
React.createElement('div', {
|
|
953
|
-
style: {
|
|
954
|
-
background: 'rgba(255,255,255,0.02)',
|
|
955
|
-
border: '1px solid #2e2e2e',
|
|
956
|
-
borderRadius: '8px',
|
|
957
|
-
padding: '12px 14px',
|
|
958
|
-
marginBottom: '16px'
|
|
959
|
-
}
|
|
960
|
-
},
|
|
961
|
-
React.createElement('div', { style: { fontWeight: 500, fontSize: '13px', color: '#eee', marginBottom: '8px', display: 'flex', alignItems: 'center', gap: '6px' } },
|
|
962
|
-
React.createElement('span', { style: { display: 'flex', alignItems: 'center', color: '#60a5fa' }, dangerouslySetInnerHTML: { __html: ICON_TELEGRAM } }),
|
|
963
|
-
'Уведомления в Telegram'
|
|
964
1112
|
),
|
|
965
|
-
React.createElement('
|
|
966
|
-
React.createElement('
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
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
|
+
)
|
|
972
1134
|
),
|
|
973
|
-
|
|
974
|
-
React.createElement('
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
style: {
|
|
984
|
-
background: 'rgba(255,255,255,0.03)',
|
|
985
|
-
border: '1px solid #2e2e2e',
|
|
986
|
-
borderRadius: '8px',
|
|
987
|
-
padding: '10px 14px',
|
|
988
|
-
fontSize: '12.5px',
|
|
989
|
-
color: '#999',
|
|
990
|
-
marginBottom: '16px',
|
|
991
|
-
display: 'flex',
|
|
992
|
-
justifyContent: 'space-between',
|
|
993
|
-
alignItems: 'center'
|
|
994
|
-
}
|
|
995
|
-
},
|
|
996
|
-
React.createElement('div', null,
|
|
997
|
-
React.createElement('span', null, 'Следующий запуск: '),
|
|
998
|
-
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
|
+
})
|
|
999
1145
|
),
|
|
1000
|
-
React.createElement('
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
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'
|
|
1007
1153
|
}
|
|
1008
|
-
},
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
className: 'dsh-cron-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
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
|
+
)
|
|
1020
1215
|
)
|
|
1021
|
-
)
|
|
1022
1216
|
)
|
|
1023
1217
|
),
|
|
1024
1218
|
|
|
1025
|
-
//
|
|
1219
|
+
// 2. Settings modal
|
|
1026
1220
|
settingsModalOpen && React.createElement('div', { className: 'dsh-cron-modal-overlay', onClick: () => setSettingsModalOpen(false) },
|
|
1027
|
-
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() },
|
|
1028
1222
|
React.createElement('div', { className: 'dsh-cron-modal-head', style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '18px' } },
|
|
1029
1223
|
React.createElement('div', { className: 'dsh-cron-modal-title', style: { margin: 0, display: 'flex', alignItems: 'center', gap: '8px' } },
|
|
1030
|
-
React.createElement('span', { style: { display: 'flex', alignItems: 'center', color: '
|
|
1031
|
-
|
|
1224
|
+
React.createElement('span', { style: { display: 'flex', alignItems: 'center', color: 'var(--dsh-cron-info)' }, ...iconHtml(ICON_TELEGRAM) }),
|
|
1225
|
+
T_KEY(t, 'settings.modalTitle')
|
|
1032
1226
|
),
|
|
1033
1227
|
React.createElement('button', {
|
|
1034
1228
|
type: 'button',
|
|
1035
|
-
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' },
|
|
1036
1230
|
onClick: () => setSettingsModalOpen(false)
|
|
1037
1231
|
}, '✕')
|
|
1038
1232
|
),
|
|
1039
1233
|
React.createElement('form', { onSubmit: handleSaveSettings },
|
|
1040
1234
|
React.createElement('div', { className: 'dsh-cron-form-group' },
|
|
1041
|
-
React.createElement('label', null,
|
|
1235
|
+
React.createElement('label', null, T_KEY(t, 'settings.tokenLabel')),
|
|
1042
1236
|
React.createElement('input', {
|
|
1043
1237
|
type: 'password',
|
|
1044
1238
|
value: settingsBotToken,
|
|
1045
|
-
placeholder: settingsHasDefault ?
|
|
1239
|
+
placeholder: settingsHasDefault ? T_KEY(t, 'settings.tokenPlaceholderDefault') : T_KEY(t, 'settings.tokenPlaceholder'),
|
|
1046
1240
|
onChange: (e) => setSettingsBotToken(e.target.value)
|
|
1047
1241
|
}),
|
|
1048
|
-
settingsHasDefault && React.createElement('div', { className: 'dsh-cron-hint' },
|
|
1242
|
+
settingsHasDefault && React.createElement('div', { className: 'dsh-cron-hint' }, T_KEY(t, 'settings.defaultHint'))
|
|
1049
1243
|
),
|
|
1050
1244
|
React.createElement('div', { className: 'dsh-cron-form-group' },
|
|
1051
|
-
React.createElement('label', null,
|
|
1245
|
+
React.createElement('label', null, T_KEY(t, 'settings.chatIdLabel')),
|
|
1052
1246
|
React.createElement('input', {
|
|
1053
1247
|
value: settingsChatId,
|
|
1054
|
-
placeholder:
|
|
1248
|
+
placeholder: T_KEY(t, 'settings.chatIdPlaceholder'),
|
|
1055
1249
|
onChange: (e) => setSettingsChatId(e.target.value)
|
|
1056
1250
|
})
|
|
1057
1251
|
),
|
|
1058
|
-
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' } },
|
|
1059
1253
|
React.createElement('label', { className: 'dsh-cron-checkbox-row', style: { marginBottom: '8px' } },
|
|
1060
1254
|
React.createElement('input', {
|
|
1061
1255
|
type: 'checkbox',
|
|
1062
1256
|
checked: settingsNotifyGlobal,
|
|
1063
1257
|
onChange: (e) => setSettingsNotifyGlobal(e.target.checked)
|
|
1064
1258
|
}),
|
|
1065
|
-
React.createElement('span', null,
|
|
1259
|
+
React.createElement('span', null, T_KEY(t, 'settings.globalNotify'))
|
|
1066
1260
|
),
|
|
1067
1261
|
React.createElement('label', { className: 'dsh-cron-checkbox-row' },
|
|
1068
1262
|
React.createElement('input', {
|
|
@@ -1070,7 +1264,7 @@ const React = window.React || require('react');
|
|
|
1070
1264
|
checked: settingsOnlyOnFailureGlobal,
|
|
1071
1265
|
onChange: (e) => setSettingsOnlyOnFailureGlobal(e.target.checked)
|
|
1072
1266
|
}),
|
|
1073
|
-
React.createElement('span', null,
|
|
1267
|
+
React.createElement('span', null, T_KEY(t, 'settings.globalOnlyFailure'))
|
|
1074
1268
|
)
|
|
1075
1269
|
),
|
|
1076
1270
|
React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '14px' } },
|
|
@@ -1080,7 +1274,7 @@ const React = window.React || require('react');
|
|
|
1080
1274
|
style: { display: 'inline-flex', alignItems: 'center', gap: '6px' },
|
|
1081
1275
|
disabled: testStatus?.loading,
|
|
1082
1276
|
onClick: handleTestTelegram
|
|
1083
|
-
}, testStatus?.loading ? '
|
|
1277
|
+
}, testStatus?.loading ? T_KEY(t, 'settings.sending') : T_KEY(t, 'settings.testTelegram'))
|
|
1084
1278
|
),
|
|
1085
1279
|
testStatus && testStatus.message && React.createElement('div', {
|
|
1086
1280
|
className: 'dsh-cron-status-banner',
|
|
@@ -1091,28 +1285,29 @@ const React = window.React || require('react');
|
|
|
1091
1285
|
type: 'button',
|
|
1092
1286
|
className: 'dsh-cron-btn-secondary',
|
|
1093
1287
|
onClick: () => setSettingsModalOpen(false)
|
|
1094
|
-
}, '
|
|
1288
|
+
}, T_KEY(t, 'modal.cancel')),
|
|
1095
1289
|
React.createElement('button', {
|
|
1096
1290
|
type: 'submit',
|
|
1097
1291
|
className: 'dsh-cron-btn-primary'
|
|
1098
|
-
},
|
|
1292
|
+
}, T_KEY(t, 'settings.save'))
|
|
1099
1293
|
)
|
|
1100
1294
|
)
|
|
1101
1295
|
)
|
|
1102
1296
|
),
|
|
1103
|
-
|
|
1297
|
+
|
|
1298
|
+
// 3. "Create with DSH" modal
|
|
1104
1299
|
dshModalOpen && React.createElement('div', { className: 'dsh-cron-modal-overlay', onClick: () => !dshLoading && setDshModalOpen(false) },
|
|
1105
|
-
React.createElement('div', { className: 'dsh-cron-modal', onClick: (e) => e.stopPropagation() },
|
|
1106
|
-
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')),
|
|
1107
1302
|
React.createElement('div', { className: 'dsh-cron-form-group' },
|
|
1108
|
-
React.createElement('label', null,
|
|
1303
|
+
React.createElement('label', null, T_KEY(t, 'dsh.descLabel')),
|
|
1109
1304
|
React.createElement('div', { className: 'dsh-cron-chat-box' },
|
|
1110
1305
|
React.createElement('textarea', {
|
|
1111
1306
|
className: 'dsh-cron-chat-input',
|
|
1112
1307
|
rows: 4,
|
|
1113
1308
|
autoFocus: true,
|
|
1114
1309
|
disabled: dshLoading,
|
|
1115
|
-
placeholder:
|
|
1310
|
+
placeholder: T_KEY(t, 'dsh.placeholder'),
|
|
1116
1311
|
value: dshPrompt,
|
|
1117
1312
|
onKeyDown: (e) => {
|
|
1118
1313
|
if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
|
|
@@ -1123,18 +1318,15 @@ const React = window.React || require('react');
|
|
|
1123
1318
|
onChange: (e) => setDshPrompt(e.target.value)
|
|
1124
1319
|
}),
|
|
1125
1320
|
React.createElement('div', { className: 'dsh-cron-chat-foot' },
|
|
1126
|
-
React.createElement('span', { className: 'dsh-cron-chat-hint' },
|
|
1321
|
+
React.createElement('span', { className: 'dsh-cron-chat-hint' }, T_KEY(t, 'dsh.sendHint')),
|
|
1127
1322
|
React.createElement('button', {
|
|
1128
1323
|
type: 'button',
|
|
1129
1324
|
className: 'dsh-cron-send-btn',
|
|
1130
1325
|
disabled: dshLoading || !dshPrompt.trim(),
|
|
1131
|
-
title:
|
|
1326
|
+
title: T_KEY(t, 'dsh.sendTitle'),
|
|
1132
1327
|
onClick: handleStartWithDSH
|
|
1133
1328
|
},
|
|
1134
|
-
React.createElement('span',
|
|
1135
|
-
style: { display: 'flex', alignItems: 'center' },
|
|
1136
|
-
dangerouslySetInnerHTML: { __html: ICON_SEND }
|
|
1137
|
-
})
|
|
1329
|
+
React.createElement('span', svgProps(ICON_SEND))
|
|
1138
1330
|
)
|
|
1139
1331
|
)
|
|
1140
1332
|
)
|
|
@@ -1145,7 +1337,7 @@ const React = window.React || require('react');
|
|
|
1145
1337
|
className: 'dsh-cron-btn-secondary',
|
|
1146
1338
|
disabled: dshLoading,
|
|
1147
1339
|
onClick: () => setDshModalOpen(false)
|
|
1148
|
-
}, '
|
|
1340
|
+
}, T_KEY(t, 'modal.cancel'))
|
|
1149
1341
|
)
|
|
1150
1342
|
)
|
|
1151
1343
|
)
|
|
@@ -1154,6 +1346,7 @@ const React = window.React || require('react');
|
|
|
1154
1346
|
|
|
1155
1347
|
function CronSettingsCard(props) {
|
|
1156
1348
|
const { ctx, toggle } = props;
|
|
1349
|
+
const t = props.t || translate;
|
|
1157
1350
|
const [token, setToken] = React.useState('');
|
|
1158
1351
|
const [chatId, setChatId] = React.useState('');
|
|
1159
1352
|
const [notify, setNotify] = React.useState(false);
|
|
@@ -1204,7 +1397,7 @@ const React = window.React || require('react');
|
|
|
1204
1397
|
};
|
|
1205
1398
|
|
|
1206
1399
|
const testTelegram = async () => {
|
|
1207
|
-
setTestInfo({ loading: true, message: '
|
|
1400
|
+
setTestInfo({ loading: true, message: T_KEY(t, 'settings.sending') });
|
|
1208
1401
|
try {
|
|
1209
1402
|
const res = await fetch('/dsh-cron/telegram/test', {
|
|
1210
1403
|
method: 'POST',
|
|
@@ -1213,17 +1406,17 @@ const React = window.React || require('react');
|
|
|
1213
1406
|
});
|
|
1214
1407
|
const data = await res.json();
|
|
1215
1408
|
if (data && data.ok) {
|
|
1216
|
-
setTestInfo({ success: true, message:
|
|
1409
|
+
setTestInfo({ success: true, message: T_KEY(t, 'settings.testOk') });
|
|
1217
1410
|
} else {
|
|
1218
|
-
setTestInfo({ success: false, message:
|
|
1411
|
+
setTestInfo({ success: false, message: T_KEY(t, 'settings.testFail', { error: (data && data.error) || '' }) });
|
|
1219
1412
|
}
|
|
1220
1413
|
} catch (err) {
|
|
1221
|
-
setTestInfo({ success: false, message:
|
|
1414
|
+
setTestInfo({ success: false, message: T_KEY(t, 'settings.testFail', { error: err.message }) });
|
|
1222
1415
|
}
|
|
1223
1416
|
};
|
|
1224
1417
|
|
|
1225
1418
|
const testKanban = async () => {
|
|
1226
|
-
setTestInfo({ loading: true, message:
|
|
1419
|
+
setTestInfo({ loading: true, message: T_KEY(t, 'settings.checkingKanban') });
|
|
1227
1420
|
try {
|
|
1228
1421
|
const res = await fetch('/dsh-cron/kanban/test', {
|
|
1229
1422
|
method: 'POST',
|
|
@@ -1232,62 +1425,62 @@ const React = window.React || require('react');
|
|
|
1232
1425
|
});
|
|
1233
1426
|
const data = await res.json();
|
|
1234
1427
|
if (data && data.ok) {
|
|
1235
|
-
setTestInfo({ success: true, message:
|
|
1428
|
+
setTestInfo({ success: true, message: T_KEY(t, 'settings.kanbanOk') });
|
|
1236
1429
|
} else {
|
|
1237
|
-
setTestInfo({ success: false, message:
|
|
1430
|
+
setTestInfo({ success: false, message: T_KEY(t, 'settings.kanbanFail', { error: (data && data.error) || '' }) });
|
|
1238
1431
|
}
|
|
1239
1432
|
} catch (err) {
|
|
1240
|
-
setTestInfo({ success: false, message:
|
|
1433
|
+
setTestInfo({ success: false, message: T_KEY(t, 'settings.kanbanFail', { error: err.message }) });
|
|
1241
1434
|
}
|
|
1242
1435
|
};
|
|
1243
1436
|
|
|
1244
1437
|
return React.createElement('div', { className: 'dsh-cron-card' },
|
|
1245
1438
|
React.createElement('div', { className: 'dsh-cron-card-head' },
|
|
1246
|
-
React.createElement('span', { className: 'dsh-cron-card-title' },
|
|
1439
|
+
React.createElement('span', { className: 'dsh-cron-card-title' }, T_KEY(t, 'settings.cardTitle')),
|
|
1247
1440
|
React.createElement('button', {
|
|
1248
1441
|
type: 'button',
|
|
1249
1442
|
className: 'dsh-cron-btn-primary',
|
|
1250
1443
|
onClick: () => { if (toggle) toggle.set(true); }
|
|
1251
|
-
},
|
|
1444
|
+
}, T_KEY(t, 'settings.openPanel'))
|
|
1252
1445
|
),
|
|
1253
1446
|
React.createElement('div', { className: 'dsh-cron-card-desc', style: { marginBottom: '16px' } },
|
|
1254
|
-
|
|
1447
|
+
T_KEY(t, 'settings.cardDesc')
|
|
1255
1448
|
),
|
|
1256
1449
|
React.createElement('form', { onSubmit: saveSettings, style: { display: 'flex', flexDirection: 'column', gap: '12px' } },
|
|
1257
1450
|
React.createElement('div', { className: 'dsh-cron-form-group', style: { marginBottom: 0 } },
|
|
1258
|
-
React.createElement('label', null,
|
|
1451
|
+
React.createElement('label', null, T_KEY(t, 'settings.tokenLabel')),
|
|
1259
1452
|
React.createElement('input', {
|
|
1260
1453
|
type: 'password',
|
|
1261
1454
|
value: token,
|
|
1262
|
-
placeholder: hasDefault ?
|
|
1455
|
+
placeholder: hasDefault ? T_KEY(t, 'settings.tokenPlaceholderDefault') : T_KEY(t, 'settings.tokenPlaceholder'),
|
|
1263
1456
|
onChange: (e) => setToken(e.target.value)
|
|
1264
1457
|
}),
|
|
1265
|
-
hasDefault && React.createElement('div', { className: 'dsh-cron-hint' },
|
|
1458
|
+
hasDefault && React.createElement('div', { className: 'dsh-cron-hint' }, T_KEY(t, 'settings.defaultHint'))
|
|
1266
1459
|
),
|
|
1267
1460
|
React.createElement('div', { className: 'dsh-cron-form-group', style: { marginBottom: 0 } },
|
|
1268
|
-
React.createElement('label', null,
|
|
1461
|
+
React.createElement('label', null, T_KEY(t, 'settings.chatIdLabel')),
|
|
1269
1462
|
React.createElement('input', {
|
|
1270
1463
|
value: chatId,
|
|
1271
|
-
placeholder:
|
|
1464
|
+
placeholder: T_KEY(t, 'settings.chatIdPlaceholder'),
|
|
1272
1465
|
onChange: (e) => setChatId(e.target.value)
|
|
1273
1466
|
})
|
|
1274
1467
|
),
|
|
1275
1468
|
React.createElement('div', { className: 'dsh-cron-form-group', style: { marginBottom: 0 } },
|
|
1276
|
-
React.createElement('label', null,
|
|
1469
|
+
React.createElement('label', null, T_KEY(t, 'settings.kanbanUrlLabel')),
|
|
1277
1470
|
React.createElement('input', {
|
|
1278
1471
|
value: kanbanUrl,
|
|
1279
1472
|
placeholder: 'http://127.0.0.1:3000',
|
|
1280
1473
|
onChange: (e) => setKanbanUrl(e.target.value)
|
|
1281
1474
|
})
|
|
1282
1475
|
),
|
|
1283
|
-
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' } },
|
|
1284
1477
|
React.createElement('label', { className: 'dsh-cron-checkbox-row' },
|
|
1285
1478
|
React.createElement('input', {
|
|
1286
1479
|
type: 'checkbox',
|
|
1287
1480
|
checked: notify,
|
|
1288
1481
|
onChange: (e) => setNotify(e.target.checked)
|
|
1289
1482
|
}),
|
|
1290
|
-
React.createElement('span', null,
|
|
1483
|
+
React.createElement('span', null, T_KEY(t, 'settings.globalNotify'))
|
|
1291
1484
|
),
|
|
1292
1485
|
React.createElement('label', { className: 'dsh-cron-checkbox-row' },
|
|
1293
1486
|
React.createElement('input', {
|
|
@@ -1295,7 +1488,7 @@ const React = window.React || require('react');
|
|
|
1295
1488
|
checked: onlyFailure,
|
|
1296
1489
|
onChange: (e) => setOnlyFailure(e.target.checked)
|
|
1297
1490
|
}),
|
|
1298
|
-
React.createElement('span', null,
|
|
1491
|
+
React.createElement('span', null, T_KEY(t, 'settings.globalOnlyFailure'))
|
|
1299
1492
|
)
|
|
1300
1493
|
),
|
|
1301
1494
|
testInfo && testInfo.message && React.createElement('div', {
|
|
@@ -1308,19 +1501,19 @@ const React = window.React || require('react');
|
|
|
1308
1501
|
type: 'submit',
|
|
1309
1502
|
className: 'dsh-cron-btn-primary',
|
|
1310
1503
|
style: { minWidth: '140px' }
|
|
1311
|
-
}, saved ?
|
|
1504
|
+
}, saved ? T_KEY(t, 'settings.saved') : T_KEY(t, 'settings.save')),
|
|
1312
1505
|
React.createElement('button', {
|
|
1313
1506
|
type: 'button',
|
|
1314
1507
|
className: 'dsh-cron-btn-secondary',
|
|
1315
1508
|
onClick: testTelegram,
|
|
1316
1509
|
disabled: testInfo?.loading
|
|
1317
|
-
},
|
|
1510
|
+
}, T_KEY(t, 'settings.testTelegram')),
|
|
1318
1511
|
React.createElement('button', {
|
|
1319
1512
|
type: 'button',
|
|
1320
1513
|
className: 'dsh-cron-btn-secondary',
|
|
1321
1514
|
onClick: testKanban,
|
|
1322
1515
|
disabled: testInfo?.loading
|
|
1323
|
-
},
|
|
1516
|
+
}, T_KEY(t, 'settings.testKanban'))
|
|
1324
1517
|
)
|
|
1325
1518
|
)
|
|
1326
1519
|
);
|
|
@@ -1338,7 +1531,7 @@ const React = window.React || require('react');
|
|
|
1338
1531
|
const entry = target.cloneNode(true);
|
|
1339
1532
|
entry.className = String(target.className) + ' dsh-cron-entry-clone';
|
|
1340
1533
|
entry.setAttribute(ENTRY_ATTR, '');
|
|
1341
|
-
entry.setAttribute('data-dsh-plugin', 'cron');
|
|
1534
|
+
entry.setAttribute('data-dsh-plugin', 'dsh-cron');
|
|
1342
1535
|
entry.setAttribute('data-dsh-part', 'sidebar-entry');
|
|
1343
1536
|
entry.setAttribute('aria-label', label);
|
|
1344
1537
|
entry.removeAttribute('id');
|
|
@@ -1416,13 +1609,13 @@ const React = window.React || require('react');
|
|
|
1416
1609
|
if (column === null) return;
|
|
1417
1610
|
container = document.createElement('div');
|
|
1418
1611
|
container.setAttribute(VIEW_ATTR, '');
|
|
1419
|
-
container.setAttribute('data-dsh-plugin', 'cron');
|
|
1612
|
+
container.setAttribute('data-dsh-plugin', 'dsh-cron');
|
|
1420
1613
|
column.appendChild(container);
|
|
1421
1614
|
if (ReactDOM && typeof ReactDOM.createRoot === 'function') {
|
|
1422
1615
|
root = ReactDOM.createRoot(container);
|
|
1423
|
-
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 }));
|
|
1424
1617
|
} else if (ReactDOM && typeof ReactDOM.render === 'function') {
|
|
1425
|
-
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);
|
|
1426
1619
|
}
|
|
1427
1620
|
};
|
|
1428
1621
|
|
|
@@ -1465,43 +1658,88 @@ const React = window.React || require('react');
|
|
|
1465
1658
|
};
|
|
1466
1659
|
}
|
|
1467
1660
|
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
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;
|
|
1479
1676
|
}
|
|
1480
|
-
return undefined;
|
|
1481
1677
|
}
|
|
1482
1678
|
|
|
1483
1679
|
function apply(ctx) {
|
|
1484
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
|
+
|
|
1485
1697
|
const toggle = createToggle();
|
|
1486
1698
|
|
|
1487
|
-
const
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
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
|
+
}
|
|
1501
1739
|
|
|
1502
1740
|
ctx.effect(() => {
|
|
1503
1741
|
const off = [
|
|
1504
|
-
mountSidebarEntry(toggle, '
|
|
1742
|
+
mountSidebarEntry(toggle, translate('sidebar.label')),
|
|
1505
1743
|
mountCronScreen(ctx, toggle)
|
|
1506
1744
|
];
|
|
1507
1745
|
return () => { for (const dispose of off) dispose(); };
|