@goodandready/dsh-cron 0.1.24 → 0.2.1
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 +46 -37
- package/lib/chat-start.js +2 -2
- package/lib/client.js +709 -424
- package/lib/http-utils.js +81 -0
- package/lib/index.js +379 -389
- 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,183 @@ 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
|
-
|
|
32
|
-
|
|
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.loading': 'Loading settings...',
|
|
123
|
+
'settings.unavailable': 'Settings are unavailable right now.',
|
|
124
|
+
'settings.retry': 'Retry',
|
|
125
|
+
'settings.cardTitle': '⏰ Task scheduler (Cron)',
|
|
126
|
+
'settings.cardDesc': 'Scheduled task execution (cron/intervals), background agent scenarios and Telegram/Kanban integration.',
|
|
127
|
+
'settings.openPanel': 'Open task panel',
|
|
128
|
+
'settings.modalTitle': 'Telegram & notification settings',
|
|
129
|
+
'settings.tokenLabel': 'Telegram Bot Token',
|
|
130
|
+
'settings.tokenPlaceholder': '123456789:ABCdefGhIJKlmNoPQRsTUVwxyZ',
|
|
131
|
+
'settings.tokenPlaceholderDefault': 'Using the bot configured in DSH settings (or enter your own)',
|
|
132
|
+
'settings.defaultHint': '✓ A configured bot was found in the DSH profile',
|
|
133
|
+
'settings.chatIdLabel': 'Telegram Chat ID',
|
|
134
|
+
'settings.chatIdPlaceholder': 'For example: 345678901',
|
|
135
|
+
'settings.kanbanUrlLabel': 'Kanban Base URL',
|
|
136
|
+
'settings.globalNotify': 'Deliver reports of all tasks to Telegram globally',
|
|
137
|
+
'settings.globalOnlyFailure': 'Global mode: only on failures (silent on success)',
|
|
138
|
+
'settings.testTelegram': '🔔 Test Telegram delivery',
|
|
139
|
+
'settings.testKanban': '📋 Test Kanban',
|
|
140
|
+
'settings.sending': 'Sending...',
|
|
141
|
+
'settings.checkingKanban': 'Checking Kanban...',
|
|
142
|
+
'settings.testOk': '✅ Test message delivered to Telegram!',
|
|
143
|
+
'settings.testFail': '❌ {error}',
|
|
144
|
+
'settings.kanbanOk': '✅ Kanban card created',
|
|
145
|
+
'settings.kanbanFail': '❌ {error}',
|
|
146
|
+
'settings.save': 'Save settings',
|
|
147
|
+
'settings.saved': '✓ Saved',
|
|
148
|
+
'settings.saveError': 'Save failed: {error}',
|
|
149
|
+
'settings.saveErrorUnknown': 'unknown error',
|
|
150
|
+
'dsh.modalTitle': 'Schedule a task with the DSH agent',
|
|
151
|
+
'dsh.descLabel': 'Describe the task and the desired frequency:',
|
|
152
|
+
'dsh.placeholder': 'For example: check free disk space and failed systemd services every 2 hours, and alert only if something is wrong...',
|
|
153
|
+
'dsh.sendHint': 'Ctrl+Enter to send',
|
|
154
|
+
'dsh.sendTitle': 'Send to agent',
|
|
155
|
+
'dsh.startFailed': 'Failed to start the agent dialog: {error}',
|
|
156
|
+
'dsh.launchError': 'Launch failed: {error}',
|
|
157
|
+
'confirm.delete': 'Delete this task?',
|
|
158
|
+
'list.loadErrorPrefix': 'Failed to load tasks'
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
// Module-level translator. apply() rebinds it to the DSH locale service so
|
|
163
|
+
// the active language and live language switches are honored.
|
|
164
|
+
let translate = (key, vars) => {
|
|
165
|
+
let s = (STRINGS.en && STRINGS.en[key]) || key;
|
|
166
|
+
if (vars) {
|
|
167
|
+
for (const k of Object.keys(vars)) {
|
|
168
|
+
s = s.split('{' + k + '}').join(String(vars[k]));
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return s;
|
|
172
|
+
};
|
|
173
|
+
const T = (key, vars) => translate(key, vars);
|
|
174
|
+
|
|
175
|
+
// Core chevron primitive when the build ships it; own SVG fallback otherwise.
|
|
176
|
+
let CoreChevron = null;
|
|
177
|
+
try {
|
|
178
|
+
const primitives = require('@deepseek-ai/dsh-client-ui-primitives');
|
|
179
|
+
CoreChevron = primitives && primitives.IconChevronDownOutline14;
|
|
180
|
+
} catch (e) {}
|
|
181
|
+
const chevronNode = () => (CoreChevron ? React.createElement(CoreChevron) : React.createElement('span', { style: { display: 'flex', alignItems: 'center' }, dangerouslySetInnerHTML: { __html: ICON_CHEVRON_DOWN } }));
|
|
182
|
+
|
|
183
|
+
// Status colors: core semantic tokens first, plugin fallbacks as a single
|
|
184
|
+
// point of change (Issue #91 — no scattered hardcoded hex).
|
|
33
185
|
const STYLES = `
|
|
186
|
+
:root {
|
|
187
|
+
--dsh-cron-success: var(--dsw-alias-label-success, #10b981);
|
|
188
|
+
--dsh-cron-success-bg: var(--dsw-alias-bg-success, rgba(16, 185, 129, 0.15));
|
|
189
|
+
--dsh-cron-danger: var(--dsw-alias-label-danger, #ef4444);
|
|
190
|
+
--dsh-cron-danger-bg: var(--dsw-alias-bg-danger, rgba(239, 68, 68, 0.15));
|
|
191
|
+
--dsh-cron-info: var(--dsw-alias-label-info, #60a5fa);
|
|
192
|
+
--dsh-cron-info-bg: var(--dsw-alias-bg-info, rgba(59, 130, 246, 0.15));
|
|
193
|
+
--dsh-cron-accent: var(--dsw-alias-label-accent, #c084fc);
|
|
194
|
+
--dsh-cron-accent-bg: var(--dsw-alias-bg-accent, rgba(168, 85, 247, 0.15));
|
|
195
|
+
--dsh-cron-warning: var(--dsw-alias-label-warning, #eab308);
|
|
196
|
+
--dsh-cron-warning-bg: var(--dsw-alias-bg-warning, rgba(234, 179, 8, 0.15));
|
|
197
|
+
}
|
|
34
198
|
[data-pane=conversation], [class*=centerCol] {
|
|
35
199
|
position: relative !important;
|
|
36
200
|
}
|
|
@@ -56,7 +220,7 @@ const React = window.React || require('react');
|
|
|
56
220
|
|
|
57
221
|
.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
222
|
.dsh-cron-container { max-width: 860px; width: 100%; margin: 0 auto; }
|
|
59
|
-
|
|
223
|
+
|
|
60
224
|
.dsh-cron-top-bar { display: flex; align-items: center; gap: 12px; margin-bottom: 20px; }
|
|
61
225
|
.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
226
|
.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,97 +228,98 @@ const React = window.React || require('react');
|
|
|
64
228
|
.dsh-cron-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 24px; }
|
|
65
229
|
.dsh-cron-title { font-size: 24px; font-weight: 600; letter-spacing: -0.02em; margin-bottom: 6px; }
|
|
66
230
|
.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); }
|
|
231
|
+
.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; }
|
|
232
|
+
.dsh-cron-create-btn:hover { background: var(--dsw-alias-bg-layer-hover, var(--dsw-alias-interactive-bg-hover, #333)); }
|
|
69
233
|
.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-
|
|
234
|
+
.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; }
|
|
235
|
+
.dsh-cron-dropdown-item:hover { background: var(--dsw-alias-interactive-bg-hover, #2a2a2a); }
|
|
72
236
|
.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; }
|
|
237
|
+
.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; }
|
|
238
|
+
.dsh-cron-search-icon { position: absolute; left: 12px; top: 50%; transform: translateY(-50%); color: var(--dsw-alias-label-tertiary, #666); }
|
|
75
239
|
.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; }
|
|
240
|
+
.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; }
|
|
241
|
+
.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
242
|
.dsh-cron-task-list { display: flex; flex-direction: column; gap: 12px; margin-bottom: 40px; }
|
|
79
243
|
.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; }
|
|
244
|
+
.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; }
|
|
245
|
+
.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
246
|
.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; }
|
|
247
|
+
.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
248
|
.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; }
|
|
249
|
+
.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; }
|
|
250
|
+
.dsh-cron-icon-btn:hover { background: var(--dsw-alias-interactive-bg-hover, #2a2a2a); color: var(--dsw-alias-label-primary, #fff); }
|
|
251
|
+
.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; }
|
|
252
|
+
.dsh-cron-task-status-btn[data-active="true"] { color: var(--dsh-cron-success); border-color: var(--dsh-cron-success); }
|
|
93
253
|
.dsh-cron-recs-title { font-size: 17px; font-weight: 600; margin-bottom: 14px; }
|
|
94
254
|
.dsh-cron-recs-list { display: flex; flex-direction: column; gap: 10px; }
|
|
95
255
|
.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; }
|
|
256
|
+
.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; }
|
|
257
|
+
.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
258
|
.dsh-cron-rec-content { flex: 1; min-width: 0; }
|
|
99
259
|
.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
|
-
|
|
260
|
+
.dsh-cron-rec-time { font-size: 13px; color: var(--dsw-alias-label-secondary, #9ca3af); font-weight: normal; margin-left: 8px; }
|
|
261
|
+
.dsh-cron-rec-desc { font-size: 13px; color: var(--dsw-alias-label-tertiary, #71717a); }
|
|
262
|
+
|
|
103
263
|
.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
264
|
.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
265
|
.dsh-cron-modal-title { font-size: 18px; font-weight: 600; margin-bottom: 18px; }
|
|
106
266
|
.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; }
|
|
267
|
+
.dsh-cron-form-group label { display: block; font-size: 13px; color: var(--dsw-alias-label-secondary, #aaa); margin-bottom: 6px; }
|
|
268
|
+
.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
269
|
.dsh-cron-form-group select { cursor: pointer; }
|
|
110
270
|
.dsh-cron-form-group textarea { min-height: 80px; resize: vertical; }
|
|
111
271
|
.dsh-cron-form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
|
|
112
272
|
.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; }
|
|
273
|
+
.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
274
|
.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) {
|
|
275
|
+
.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; }
|
|
276
|
+
.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; }
|
|
277
|
+
.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; }
|
|
278
|
+
.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); }
|
|
279
|
+
.dsh-cron-chat-hint { font-size: 12px; color: var(--dsw-alias-label-tertiary, #666); }
|
|
280
|
+
.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; }
|
|
281
|
+
.dsh-cron-send-btn:hover:not(:disabled) { transform: translateY(-1px); }
|
|
122
282
|
.dsh-cron-send-btn:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
123
283
|
|
|
124
|
-
.dsh-cron-card { background: var(--dsw-alias-bg-layer-2, #1f1f1f); border: 1px solid var(--dsw-alias-border-l1, #333); border-radius: 12px; padding:
|
|
125
|
-
.dsh-cron-card-head {
|
|
284
|
+
.dsh-cron-card { background: var(--dsw-alias-bg-layer-2, #1f1f1f); border: 1px solid var(--dsw-alias-border-l1, #333); border-radius: 12px; padding: 14px 20px; margin-bottom: 16px; }
|
|
285
|
+
.dsh-cron-card-head-btn { appearance: none; width: 100%; font: inherit; color: inherit; text-align: left; cursor: pointer; background: 0 0; border: 0; border-radius: 12px; display: flex; align-items: center; gap: 12px; padding: 4px 0; }
|
|
286
|
+
.dsh-cron-card-head-btn .dsh-cron-card-title { flex: 1; }
|
|
126
287
|
.dsh-cron-card-title { font-size: 15px; font-weight: 600; color: var(--dsw-alias-label-primary, #fff); }
|
|
288
|
+
.dsh-cron-chev { margin-left: auto; flex: none; color: var(--dsw-alias-label-tertiary, #888); transition: transform 0.16s; display: flex; align-items: center; }
|
|
289
|
+
.dsh-cron-chev-open { transform: rotate(180deg); }
|
|
290
|
+
.dsh-cron-card-body { border-top: 1px solid var(--dsw-alias-border-l1, #333); margin-top: 12px; padding-top: 14px; }
|
|
291
|
+
.dsh-cron-card-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; }
|
|
127
292
|
.dsh-cron-card-desc { font-size: 13px; color: var(--dsw-alias-label-secondary, #888); margin-bottom: 16px; line-height: 1.4; }
|
|
128
293
|
|
|
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; }
|
|
294
|
+
.dsh-cron-modal-nav { display: flex; gap: 4px; border-bottom: 1px solid var(--dsw-alias-border-l2, #2e2e2e); margin-bottom: 18px; padding-bottom: 2px; }
|
|
295
|
+
.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; }
|
|
296
|
+
.dsh-cron-modal-tab:hover { color: var(--dsw-alias-label-secondary, #ccc); }
|
|
297
|
+
.dsh-cron-modal-tab[data-active="true"] { color: var(--dsw-alias-label-primary, #fff); border-bottom-color: var(--dsw-alias-label-primary, #ededed); }
|
|
133
298
|
.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; }
|
|
299
|
+
.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
300
|
.dsh-cron-history-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px; }
|
|
136
301
|
.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; }
|
|
302
|
+
.dsh-cron-history-status[data-status="success"] { background: var(--dsh-cron-success-bg); color: var(--dsh-cron-success); }
|
|
303
|
+
.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); }
|
|
304
|
+
.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); }
|
|
305
|
+
.dsh-cron-history-time { color: var(--dsw-alias-label-tertiary, #777); font-size: 12px; }
|
|
306
|
+
.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; }
|
|
307
|
+
.dsh-cron-history-empty { text-align: center; padding: 36px 0; color: var(--dsw-alias-label-tertiary, #666); font-size: 13.5px; }
|
|
143
308
|
|
|
144
309
|
.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; }
|
|
310
|
+
.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; }
|
|
311
|
+
.dsh-cron-stat-val { font-size: 16px; font-weight: 600; color: var(--dsw-alias-label-primary, #fff); }
|
|
312
|
+
.dsh-cron-stat-lbl { font-size: 11.5px; color: var(--dsw-alias-label-tertiary, #777); margin-top: 2px; }
|
|
313
|
+
.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; }
|
|
314
|
+
.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; }
|
|
315
|
+
.dsh-cron-tokens-tag { font-size: 11px; padding: 2px 6px; border-radius: 4px; background: var(--dsh-cron-info-bg); color: var(--dsh-cron-info); }
|
|
316
|
+
.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; }
|
|
317
|
+
.dsh-cron-checkbox-row input { width: 15px; height: 15px; margin: 0; cursor: pointer; accent-color: var(--dsw-alias-label-primary, #ededed); }
|
|
318
|
+
.dsh-cron-hint { font-size: 12px; color: var(--dsw-alias-label-tertiary, #888); margin-top: 4px; }
|
|
154
319
|
.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:
|
|
320
|
+
.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); }
|
|
321
|
+
.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); }
|
|
322
|
+
.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
323
|
`;
|
|
159
324
|
|
|
160
325
|
function ensureStyles() {
|
|
@@ -163,6 +328,8 @@ const React = window.React || require('react');
|
|
|
163
328
|
if (!el) {
|
|
164
329
|
el = document.createElement('style');
|
|
165
330
|
el.id = 'dsh-cron-styles';
|
|
331
|
+
// Mark ownership before insertion so neighbor cleanups leave it alone (#91)
|
|
332
|
+
el.dataset.dshPlugin = 'dsh-cron';
|
|
166
333
|
document.head.appendChild(el);
|
|
167
334
|
}
|
|
168
335
|
el.textContent = STYLES;
|
|
@@ -194,7 +361,7 @@ const React = window.React || require('react');
|
|
|
194
361
|
const getSessions = () => (ctx && typeof ctx.get === 'function' ? ctx.get('sessions') : (ctx && ctx.sessions));
|
|
195
362
|
let sessions = getSessions();
|
|
196
363
|
|
|
197
|
-
// 1.
|
|
364
|
+
// 1. Try sessions.open(sessionId) immediately
|
|
198
365
|
if (sessions && typeof sessions.open === 'function') {
|
|
199
366
|
try {
|
|
200
367
|
sessions.open(sessionId);
|
|
@@ -202,7 +369,7 @@ const React = window.React || require('react');
|
|
|
202
369
|
} catch (e) {}
|
|
203
370
|
}
|
|
204
371
|
|
|
205
|
-
// 2.
|
|
372
|
+
// 2. If the session is not in manager.summaries yet, refresh from the backend
|
|
206
373
|
if (sessions && typeof sessions.refresh === 'function') {
|
|
207
374
|
try {
|
|
208
375
|
await sessions.refresh();
|
|
@@ -211,7 +378,7 @@ const React = window.React || require('react');
|
|
|
211
378
|
} catch (e) {}
|
|
212
379
|
}
|
|
213
380
|
|
|
214
|
-
// 3.
|
|
381
|
+
// 3. Async retries until the WebSocket/SSE event adds the session to the list
|
|
215
382
|
for (let i = 0; i < 15; i++) {
|
|
216
383
|
await new Promise(resolve => setTimeout(resolve, 120));
|
|
217
384
|
sessions = getSessions();
|
|
@@ -221,7 +388,7 @@ const React = window.React || require('react');
|
|
|
221
388
|
return true;
|
|
222
389
|
} catch (e) {}
|
|
223
390
|
}
|
|
224
|
-
// DOM fallback:
|
|
391
|
+
// DOM fallback: click the session in the sidebar if it has rendered
|
|
225
392
|
if (typeof document !== 'undefined') {
|
|
226
393
|
const domItem = document.querySelector('[data-session-id="' + sessionId + '"], [role="treeitem"][data-id="' + sessionId + '"], [data-id="' + sessionId + '"]');
|
|
227
394
|
if (domItem) {
|
|
@@ -233,8 +400,20 @@ const React = window.React || require('react');
|
|
|
233
400
|
return false;
|
|
234
401
|
}
|
|
235
402
|
|
|
403
|
+
function statusLabel(t, status) {
|
|
404
|
+
if (status === 'success') return T_KEY(t, 'history.statusSuccess');
|
|
405
|
+
if (status === 'skipped') return T_KEY(t, 'history.statusSkipped');
|
|
406
|
+
if (status === 'missed') return T_KEY(t, 'history.statusMissed');
|
|
407
|
+
if (status === 'timeout') return T_KEY(t, 'history.statusTimeout');
|
|
408
|
+
return T_KEY(t, 'history.statusFailed');
|
|
409
|
+
}
|
|
410
|
+
function T_KEY(t, key, vars) {
|
|
411
|
+
return (typeof t === 'function' ? t : translate)(key, vars);
|
|
412
|
+
}
|
|
413
|
+
|
|
236
414
|
function CronScreen(props) {
|
|
237
415
|
const { ctx, toggle, onClose } = props;
|
|
416
|
+
const t = props.t || translate;
|
|
238
417
|
const [tab, setTab] = React.useState('all');
|
|
239
418
|
const [query, setQuery] = React.useState('');
|
|
240
419
|
const [tasks, setTasks] = React.useState([]);
|
|
@@ -311,11 +490,11 @@ const React = window.React || require('react');
|
|
|
311
490
|
setRecs(data.recommendations || []);
|
|
312
491
|
setStats(data.stats || null);
|
|
313
492
|
} else {
|
|
314
|
-
setFetchError(data && data.error ? data.error :
|
|
493
|
+
setFetchError(data && data.error ? data.error : T_KEY(t, 'list.loadErrorPrefix'));
|
|
315
494
|
}
|
|
316
495
|
} catch (err) {
|
|
317
496
|
console.error('[dsh-cron] fetch tasks error:', err);
|
|
318
|
-
setFetchError(err.message ||
|
|
497
|
+
setFetchError(err.message || T_KEY(t, 'list.loadErrorPrefix'));
|
|
319
498
|
} finally {
|
|
320
499
|
setLoading(false);
|
|
321
500
|
}
|
|
@@ -329,7 +508,7 @@ const React = window.React || require('react');
|
|
|
329
508
|
if (data && data.ok) {
|
|
330
509
|
if (!providerName) {
|
|
331
510
|
setProviders(data.providers || []);
|
|
332
|
-
setDefaultModelInfo(data.defaultModel || null);
|
|
511
|
+
setDefaultModelInfo(data.defaultModel || data.current || null);
|
|
333
512
|
if (data.defaultModel?.models) {
|
|
334
513
|
setModels(data.defaultModel.models);
|
|
335
514
|
}
|
|
@@ -370,7 +549,7 @@ const React = window.React || require('react');
|
|
|
370
549
|
|
|
371
550
|
const handleRunNow = async (id) => {
|
|
372
551
|
try {
|
|
373
|
-
setTasks(prev => prev.map(
|
|
552
|
+
setTasks(prev => prev.map(t2 => t2.id === id ? { ...t2, lastStatus: 'running' } : t2));
|
|
374
553
|
await fetch('/dsh-cron/tasks/' + encodeURIComponent(id) + '/run', { method: 'POST' });
|
|
375
554
|
setTimeout(() => fetchTasks(), 1500);
|
|
376
555
|
} catch (err) {
|
|
@@ -379,7 +558,7 @@ const React = window.React || require('react');
|
|
|
379
558
|
};
|
|
380
559
|
|
|
381
560
|
const handleDelete = async (id) => {
|
|
382
|
-
if (!confirm(
|
|
561
|
+
if (!confirm(T_KEY(t, 'confirm.delete'))) return;
|
|
383
562
|
try {
|
|
384
563
|
await fetch('/dsh-cron/tasks/' + encodeURIComponent(id), { method: 'DELETE' });
|
|
385
564
|
fetchTasks();
|
|
@@ -422,15 +601,15 @@ const React = window.React || require('react');
|
|
|
422
601
|
if (data && data.ok) {
|
|
423
602
|
setSettingsModalOpen(false);
|
|
424
603
|
} else {
|
|
425
|
-
alert('
|
|
604
|
+
alert(T_KEY(t, 'settings.saveError', { error: (data && data.error) || T_KEY(t, 'settings.saveErrorUnknown') }));
|
|
426
605
|
}
|
|
427
606
|
} catch (err) {
|
|
428
|
-
alert('
|
|
607
|
+
alert(T_KEY(t, 'settings.saveError', { error: err.message }));
|
|
429
608
|
}
|
|
430
609
|
};
|
|
431
610
|
|
|
432
611
|
const handleTestTelegram = async () => {
|
|
433
|
-
setTestStatus({ loading: true, message:
|
|
612
|
+
setTestStatus({ loading: true, message: T_KEY(t, 'settings.sending') });
|
|
434
613
|
try {
|
|
435
614
|
const res = await fetch('/dsh-cron/telegram/test', {
|
|
436
615
|
method: 'POST',
|
|
@@ -442,12 +621,12 @@ const React = window.React || require('react');
|
|
|
442
621
|
});
|
|
443
622
|
const data = await res.json();
|
|
444
623
|
if (data && data.ok) {
|
|
445
|
-
setTestStatus({ loading: false, success: true, message:
|
|
624
|
+
setTestStatus({ loading: false, success: true, message: T_KEY(t, 'settings.testOk') });
|
|
446
625
|
} else {
|
|
447
|
-
setTestStatus({ loading: false, success: false, message:
|
|
626
|
+
setTestStatus({ loading: false, success: false, message: T_KEY(t, 'settings.testFail', { error: (data && data.error) || '' }) });
|
|
448
627
|
}
|
|
449
628
|
} catch (err) {
|
|
450
|
-
setTestStatus({ loading: false, success: false, message:
|
|
629
|
+
setTestStatus({ loading: false, success: false, message: T_KEY(t, 'settings.testFail', { error: err.message }) });
|
|
451
630
|
}
|
|
452
631
|
};
|
|
453
632
|
|
|
@@ -544,13 +723,17 @@ const React = window.React || require('react');
|
|
|
544
723
|
overlapPolicy: formOverlapPolicy,
|
|
545
724
|
kanbanMode: formKanbanMode,
|
|
546
725
|
};
|
|
726
|
+
// Cross-origin hardening: script tasks need the explicit confirm
|
|
727
|
+
// header, which browsers cannot attach to forged cross-site posts (#86)
|
|
728
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
729
|
+
if (formType === 'script') headers[SCRIPT_CONFIRM_HEADER] = 'script';
|
|
547
730
|
const res = await fetch('/dsh-cron/tasks', {
|
|
548
731
|
method: 'POST',
|
|
549
|
-
headers
|
|
732
|
+
headers,
|
|
550
733
|
body: JSON.stringify(payload)
|
|
551
734
|
});
|
|
552
735
|
const data = await res.json();
|
|
553
|
-
if (!data.ok) { alert(data.error || '
|
|
736
|
+
if (!data.ok) { alert(data.error || T_KEY(t, 'settings.saveError', { error: '' })); return; }
|
|
554
737
|
setManualModalOpen(false);
|
|
555
738
|
fetchTasks();
|
|
556
739
|
} catch (err) { alert(err.message); }
|
|
@@ -575,7 +758,7 @@ const React = window.React || require('react');
|
|
|
575
758
|
});
|
|
576
759
|
const data = await res.json();
|
|
577
760
|
if (!data.ok || !data.sessionId) {
|
|
578
|
-
alert(data.error || '
|
|
761
|
+
alert(T_KEY(t, 'dsh.startFailed', { error: data.error || '' }));
|
|
579
762
|
setDshLoading(false);
|
|
580
763
|
return;
|
|
581
764
|
}
|
|
@@ -586,61 +769,63 @@ const React = window.React || require('react');
|
|
|
586
769
|
|
|
587
770
|
await openSession(ctx, data.sessionId);
|
|
588
771
|
} catch (err) {
|
|
589
|
-
alert('
|
|
772
|
+
alert(T_KEY(t, 'dsh.launchError', { error: err.message }));
|
|
590
773
|
} finally {
|
|
591
774
|
setDshLoading(false);
|
|
592
775
|
}
|
|
593
776
|
};
|
|
594
777
|
|
|
778
|
+
const iconHtml = (html) => ({ dangerouslySetInnerHTML: { __html: html } });
|
|
779
|
+
const svgProps = (html) => ({ style: { display: 'flex', alignItems: 'center' }, ...iconHtml(html) });
|
|
780
|
+
|
|
595
781
|
return React.createElement('div', { className: 'dsh-cron-overlay' },
|
|
596
782
|
React.createElement('div', { className: 'dsh-cron-container' },
|
|
597
783
|
React.createElement('div', { className: 'dsh-cron-top-bar' },
|
|
598
784
|
React.createElement('button', {
|
|
599
785
|
type: 'button',
|
|
600
786
|
className: 'dsh-cron-back-btn',
|
|
601
|
-
title:
|
|
787
|
+
title: T_KEY(t, 'panel.back'),
|
|
602
788
|
onClick: () => { if (onClose) onClose(); else toggle.set(false); }
|
|
603
789
|
},
|
|
604
|
-
React.createElement('span',
|
|
605
|
-
|
|
606
|
-
dangerouslySetInnerHTML: { __html: BACK_ICON }
|
|
607
|
-
}),
|
|
608
|
-
React.createElement('span', null, 'Назад к чату')
|
|
790
|
+
React.createElement('span', svgProps(BACK_ICON)),
|
|
791
|
+
React.createElement('span', null, T_KEY(t, 'panel.back'))
|
|
609
792
|
)
|
|
610
793
|
),
|
|
611
794
|
React.createElement('div', { className: 'dsh-cron-header' },
|
|
612
795
|
React.createElement('div', null,
|
|
613
|
-
React.createElement('div', { className: 'dsh-cron-title' },
|
|
614
|
-
React.createElement('div', { className: 'dsh-cron-subtitle' },
|
|
796
|
+
React.createElement('div', { className: 'dsh-cron-title' }, T_KEY(t, 'panel.title')),
|
|
797
|
+
React.createElement('div', { className: 'dsh-cron-subtitle' }, T_KEY(t, 'panel.subtitle'))
|
|
615
798
|
),
|
|
616
799
|
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: '10px' } },
|
|
617
800
|
React.createElement('button', {
|
|
618
801
|
type: 'button',
|
|
619
802
|
className: 'dsh-cron-btn-secondary',
|
|
620
803
|
style: { display: 'flex', alignItems: 'center', gap: '6px', height: '36px', padding: '0 14px' },
|
|
621
|
-
title:
|
|
804
|
+
title: T_KEY(t, 'actions.settings'),
|
|
622
805
|
onClick: openSettingsModal
|
|
623
806
|
},
|
|
624
|
-
React.createElement('span', { style: { display: 'flex', alignItems: 'center', color: '
|
|
625
|
-
|
|
807
|
+
React.createElement('span', { style: { display: 'flex', alignItems: 'center', color: 'var(--dsh-cron-info)' }, ...iconHtml(ICON_TELEGRAM) }),
|
|
808
|
+
T_KEY(t, 'actions.settings')
|
|
626
809
|
),
|
|
627
810
|
React.createElement('div', { style: { position: 'relative' } },
|
|
628
811
|
React.createElement('button', { className: 'dsh-cron-create-btn', onClick: () => setDropdownOpen(!dropdownOpen) },
|
|
629
|
-
'
|
|
630
|
-
React.createElement('span',
|
|
812
|
+
T_KEY(t, 'actions.create'),
|
|
813
|
+
React.createElement('span', iconHtml(ICON_CHEVRON_DOWN))
|
|
631
814
|
),
|
|
632
815
|
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) },
|
|
816
|
+
React.createElement('button', { className: 'dsh-cron-dropdown-item', onClick: openDshModal }, T_KEY(t, 'actions.createWithDsh')),
|
|
817
|
+
React.createElement('button', { className: 'dsh-cron-dropdown-item', onClick: () => openManualModal(null) }, T_KEY(t, 'actions.setupManually'))
|
|
635
818
|
)
|
|
636
819
|
)
|
|
637
820
|
)
|
|
638
821
|
),
|
|
639
822
|
React.createElement('div', { className: 'dsh-cron-search-bar' },
|
|
640
|
-
React.createElement('span', { className: 'dsh-cron-search-icon',
|
|
823
|
+
React.createElement('span', { className: 'dsh-cron-search-icon', ...iconHtml(ICON_SEARCH) }),
|
|
641
824
|
React.createElement('input', {
|
|
642
825
|
className: 'dsh-cron-search-input',
|
|
643
|
-
|
|
826
|
+
type: 'search',
|
|
827
|
+
'aria-label': T_KEY(t, 'search.placeholder'),
|
|
828
|
+
placeholder: T_KEY(t, 'search.placeholder'),
|
|
644
829
|
value: query,
|
|
645
830
|
onChange: (e) => setQuery(e.target.value)
|
|
646
831
|
})
|
|
@@ -648,29 +833,33 @@ const React = window.React || require('react');
|
|
|
648
833
|
stats && React.createElement('div', { className: 'dsh-cron-stats-bar' },
|
|
649
834
|
React.createElement('div', { className: 'dsh-cron-stat-card' },
|
|
650
835
|
React.createElement('div', { className: 'dsh-cron-stat-val' }, stats.activeTasks || 0),
|
|
651
|
-
React.createElement('div', { className: 'dsh-cron-stat-lbl' },
|
|
836
|
+
React.createElement('div', { className: 'dsh-cron-stat-lbl' }, T_KEY(t, 'stats.activeTasks'))
|
|
652
837
|
),
|
|
653
838
|
React.createElement('div', { className: 'dsh-cron-stat-card' },
|
|
654
839
|
React.createElement('div', { className: 'dsh-cron-stat-val' }, stats.totalRuns || 0),
|
|
655
|
-
React.createElement('div', { className: 'dsh-cron-stat-lbl' },
|
|
840
|
+
React.createElement('div', { className: 'dsh-cron-stat-lbl' }, T_KEY(t, 'stats.totalRuns'))
|
|
656
841
|
),
|
|
657
842
|
React.createElement('div', { className: 'dsh-cron-stat-card' },
|
|
658
843
|
React.createElement('div', { className: 'dsh-cron-stat-val' }, (stats.totalTokens || 0).toLocaleString()),
|
|
659
|
-
React.createElement('div', { className: 'dsh-cron-stat-lbl' },
|
|
844
|
+
React.createElement('div', { className: 'dsh-cron-stat-lbl' }, T_KEY(t, 'stats.totalTokens'))
|
|
660
845
|
),
|
|
661
846
|
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' },
|
|
847
|
+
React.createElement('div', { className: 'dsh-cron-stat-val', style: { color: 'var(--dsh-cron-success)' } }, '$' + (stats.totalCostUsd || 0).toFixed(4)),
|
|
848
|
+
React.createElement('div', { className: 'dsh-cron-stat-lbl' }, T_KEY(t, 'stats.totalCost'))
|
|
664
849
|
)
|
|
665
850
|
),
|
|
666
851
|
React.createElement('div', { className: 'dsh-cron-tabs' },
|
|
667
|
-
['all', 'active', 'paused', 'completed'].map((
|
|
852
|
+
['all', 'active', 'paused', 'completed'].map((tabName) =>
|
|
668
853
|
React.createElement('button', {
|
|
669
|
-
key:
|
|
854
|
+
key: tabName,
|
|
670
855
|
className: 'dsh-cron-tab',
|
|
671
|
-
'data-active': tab ===
|
|
672
|
-
onClick: () => setTab(
|
|
673
|
-
},
|
|
856
|
+
'data-active': tab === tabName ? 'true' : undefined,
|
|
857
|
+
onClick: () => setTab(tabName)
|
|
858
|
+
},
|
|
859
|
+
tabName === 'all' ? T_KEY(t, 'tabs.all')
|
|
860
|
+
: tabName === 'active' ? T_KEY(t, 'tabs.active')
|
|
861
|
+
: tabName === 'paused' ? T_KEY(t, 'tabs.paused')
|
|
862
|
+
: T_KEY(t, 'tabs.completed'))
|
|
674
863
|
)
|
|
675
864
|
),
|
|
676
865
|
fetchError ? React.createElement('div', {
|
|
@@ -678,50 +867,59 @@ const React = window.React || require('react');
|
|
|
678
867
|
'data-type': 'error',
|
|
679
868
|
style: { margin: '8px 0', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }
|
|
680
869
|
},
|
|
681
|
-
React.createElement('span', null, '
|
|
870
|
+
React.createElement('span', null, T_KEY(t, 'banner.loadError', { error: fetchError })),
|
|
682
871
|
React.createElement('button', {
|
|
683
872
|
type: 'button',
|
|
684
|
-
className: 'dsh-cron-btn',
|
|
873
|
+
className: 'dsh-cron-btn-secondary',
|
|
685
874
|
style: { marginLeft: '8px', padding: '2px 8px', fontSize: '12px' },
|
|
686
875
|
onClick: () => fetchTasks()
|
|
687
|
-
}, '
|
|
876
|
+
}, T_KEY(t, 'banner.retry'))
|
|
688
877
|
) : null,
|
|
689
878
|
React.createElement('div', { className: 'dsh-cron-task-list' },
|
|
690
879
|
loading && tasks.length === 0
|
|
691
|
-
? React.createElement('div', { style: { color: '#888', padding: '16px 0', fontSize: '14px' } },
|
|
880
|
+
? React.createElement('div', { style: { color: 'var(--dsw-alias-label-tertiary, #888)', padding: '16px 0', fontSize: '14px' } }, T_KEY(t, 'list.loading'))
|
|
692
881
|
: tasks.length === 0
|
|
693
|
-
? React.createElement('div', { style: { color: '#777', padding: '16px 0', fontSize: '14px' } },
|
|
882
|
+
? React.createElement('div', { style: { color: 'var(--dsw-alias-label-tertiary, #777)', padding: '16px 0', fontSize: '14px' } }, T_KEY(t, 'list.empty'))
|
|
694
883
|
: tasks.map((task) =>
|
|
695
884
|
React.createElement('div', {
|
|
696
885
|
key: task.id,
|
|
697
886
|
className: 'dsh-cron-task-item',
|
|
698
|
-
|
|
887
|
+
role: 'button',
|
|
888
|
+
tabIndex: 0,
|
|
889
|
+
'aria-label': task.title,
|
|
890
|
+
onClick: () => openEditModal(task),
|
|
891
|
+
onKeyDown: (e) => {
|
|
892
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
893
|
+
e.preventDefault();
|
|
894
|
+
openEditModal(task);
|
|
895
|
+
}
|
|
896
|
+
}
|
|
699
897
|
},
|
|
700
898
|
React.createElement('button', {
|
|
701
899
|
type: 'button',
|
|
702
900
|
className: 'dsh-cron-task-status-btn',
|
|
703
901
|
'data-active': task.status === 'active' ? 'true' : undefined,
|
|
704
|
-
title: task.status === 'active' ? '
|
|
902
|
+
title: task.status === 'active' ? T_KEY(t, 'actions.pause') : T_KEY(t, 'actions.resume'),
|
|
903
|
+
'aria-label': task.status === 'active' ? T_KEY(t, 'actions.pause') : T_KEY(t, 'actions.resume'),
|
|
705
904
|
onClick: (e) => { e.stopPropagation(); handleToggleTask(task.id); }
|
|
706
|
-
}, React.createElement('span',
|
|
905
|
+
}, React.createElement('span', svgProps(task.status === 'active' ? ICON_PAUSE : ICON_PLAY))),
|
|
707
906
|
React.createElement('div', { className: 'dsh-cron-task-info' },
|
|
708
907
|
React.createElement('div', { className: 'dsh-cron-task-title' },
|
|
709
908
|
task.title,
|
|
710
909
|
React.createElement('span', {
|
|
711
910
|
className: 'dsh-cron-type-tag',
|
|
712
|
-
style:
|
|
713
|
-
background:
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
}, task.type === 'script' ? 'Shell' : 'LLM'),
|
|
911
|
+
style: task.type === 'script'
|
|
912
|
+
? { background: 'var(--dsh-cron-info-bg)', color: 'var(--dsh-cron-info)' }
|
|
913
|
+
: { background: 'var(--dsh-cron-success-bg)', color: 'var(--dsh-cron-success)' }
|
|
914
|
+
}, task.type === 'script' ? T_KEY(t, 'list.typeShell') : T_KEY(t, 'list.typeLlm')),
|
|
717
915
|
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' }, '
|
|
916
|
+
task.oneShot ? React.createElement('span', { className: 'dsh-cron-oneshot-tag' }, T_KEY(t, 'list.oneShot')) : null,
|
|
719
917
|
task.totalCostUsd > 0 ? React.createElement('span', { className: 'dsh-cron-cost-tag' }, '$' + task.totalCostUsd.toFixed(4)) : null,
|
|
720
918
|
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
919
|
),
|
|
722
920
|
React.createElement('div', { className: 'dsh-cron-task-sched' },
|
|
723
921
|
task.scheduleText || task.schedule,
|
|
724
|
-
task.nextRunAt ?
|
|
922
|
+
task.nextRunAt ? T_KEY(t, 'list.next', { time: new Date(task.nextRunAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) }) : ''
|
|
725
923
|
),
|
|
726
924
|
task.prompt ? React.createElement('div', {
|
|
727
925
|
className: 'dsh-cron-task-prompt-preview',
|
|
@@ -735,37 +933,45 @@ const React = window.React || require('react');
|
|
|
735
933
|
React.createElement('button', {
|
|
736
934
|
type: 'button',
|
|
737
935
|
className: 'dsh-cron-icon-btn',
|
|
738
|
-
title:
|
|
936
|
+
title: T_KEY(t, 'actions.runNow'),
|
|
739
937
|
onClick: () => handleRunNow(task.id)
|
|
740
|
-
}, '
|
|
938
|
+
}, T_KEY(t, 'actions.runNowShort')),
|
|
741
939
|
React.createElement('button', {
|
|
742
940
|
type: 'button',
|
|
743
941
|
className: 'dsh-cron-icon-btn',
|
|
744
|
-
title:
|
|
942
|
+
title: T_KEY(t, 'actions.edit'),
|
|
943
|
+
'aria-label': T_KEY(t, 'actions.edit'),
|
|
745
944
|
onClick: () => openEditModal(task)
|
|
746
|
-
}, React.createElement('span',
|
|
945
|
+
}, React.createElement('span', svgProps(ICON_EDIT))),
|
|
747
946
|
React.createElement('button', {
|
|
748
947
|
type: 'button',
|
|
749
948
|
className: 'dsh-cron-icon-btn',
|
|
750
|
-
title:
|
|
949
|
+
title: T_KEY(t, 'actions.delete'),
|
|
950
|
+
'aria-label': T_KEY(t, 'actions.delete'),
|
|
751
951
|
onClick: () => handleDelete(task.id)
|
|
752
|
-
}, React.createElement('span',
|
|
952
|
+
}, React.createElement('span', svgProps(ICON_TRASH)))
|
|
753
953
|
)
|
|
754
954
|
)
|
|
755
955
|
)
|
|
756
956
|
),
|
|
757
|
-
React.createElement('div', { className: 'dsh-cron-recs-title' },
|
|
957
|
+
React.createElement('div', { className: 'dsh-cron-recs-title' }, T_KEY(t, 'recs.title')),
|
|
758
958
|
React.createElement('div', { className: 'dsh-cron-recs-list' },
|
|
759
959
|
recs.map((rec) =>
|
|
760
960
|
React.createElement('div', {
|
|
761
961
|
key: rec.id,
|
|
762
962
|
className: 'dsh-cron-rec-card',
|
|
763
|
-
|
|
963
|
+
role: 'button',
|
|
964
|
+
tabIndex: 0,
|
|
965
|
+
onClick: () => openManualModal(rec),
|
|
966
|
+
onKeyDown: (e) => {
|
|
967
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
968
|
+
e.preventDefault();
|
|
969
|
+
openManualModal(rec);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
764
972
|
},
|
|
765
973
|
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
|
-
} })
|
|
974
|
+
React.createElement('span', iconHtml(rec.icon === 'bell' ? ICON_BELL : rec.icon === 'clipboard' ? ICON_CLIPBOARD : ICON_ACTIVITY))
|
|
769
975
|
),
|
|
770
976
|
React.createElement('div', { className: 'dsh-cron-rec-content' },
|
|
771
977
|
React.createElement('div', { className: 'dsh-cron-rec-title' },
|
|
@@ -784,285 +990,288 @@ const React = window.React || require('react');
|
|
|
784
990
|
React.createElement('div', {
|
|
785
991
|
className: 'dsh-cron-modal',
|
|
786
992
|
style: { maxWidth: '640px' },
|
|
993
|
+
role: 'dialog',
|
|
994
|
+
'aria-modal': 'true',
|
|
787
995
|
onClick: (e) => e.stopPropagation()
|
|
788
996
|
},
|
|
789
997
|
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 ?
|
|
998
|
+
React.createElement('div', { className: 'dsh-cron-modal-title', style: { margin: 0 } }, formId ? T_KEY(t, 'modal.editTitle') : T_KEY(t, 'modal.newTitle')),
|
|
791
999
|
selectedTaskMeta && React.createElement('span', {
|
|
792
1000
|
style: {
|
|
793
1001
|
fontSize: '12px',
|
|
794
1002
|
padding: '3px 8px',
|
|
795
1003
|
borderRadius: '6px',
|
|
796
|
-
background: selectedTaskMeta.status === 'active' ? '
|
|
797
|
-
color: selectedTaskMeta.status === 'active' ? '
|
|
1004
|
+
background: selectedTaskMeta.status === 'active' ? 'var(--dsh-cron-success-bg)' : 'var(--dsh-cron-danger-bg)',
|
|
1005
|
+
color: selectedTaskMeta.status === 'active' ? 'var(--dsh-cron-success)' : 'var(--dsh-cron-danger)'
|
|
798
1006
|
}
|
|
799
|
-
}, selectedTaskMeta.status === 'active' ?
|
|
1007
|
+
}, selectedTaskMeta.status === 'active' ? T_KEY(t, 'modal.statusActive') : T_KEY(t, 'modal.statusPaused'))
|
|
800
1008
|
),
|
|
801
1009
|
|
|
802
1010
|
formId && React.createElement('div', { className: 'dsh-cron-modal-nav' },
|
|
803
1011
|
React.createElement('button', {
|
|
804
1012
|
type: 'button',
|
|
805
1013
|
className: 'dsh-cron-modal-tab',
|
|
806
|
-
'data-active': modalTab === 'params',
|
|
1014
|
+
'data-active': modalTab === 'params' ? 'true' : undefined,
|
|
807
1015
|
onClick: () => setModalTab('params')
|
|
808
|
-
}, '
|
|
1016
|
+
}, T_KEY(t, 'modal.params')),
|
|
809
1017
|
React.createElement('button', {
|
|
810
1018
|
type: 'button',
|
|
811
1019
|
className: 'dsh-cron-modal-tab',
|
|
812
|
-
'data-active': modalTab === 'history',
|
|
1020
|
+
'data-active': modalTab === 'history' ? 'true' : undefined,
|
|
813
1021
|
onClick: () => { setModalTab('history'); if (formId) loadTaskHistory(formId); }
|
|
814
|
-
}, '
|
|
1022
|
+
}, T_KEY(t, 'modal.history', { count: taskHistory.length || 0 }))
|
|
815
1023
|
),
|
|
816
1024
|
|
|
817
1025
|
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
|
-
|
|
1026
|
+
historyLoading ? React.createElement('div', { className: 'dsh-cron-history-empty' }, T_KEY(t, 'history.loading')) :
|
|
1027
|
+
taskHistory.length === 0 ? React.createElement('div', { className: 'dsh-cron-history-empty' }, T_KEY(t, 'history.empty')) :
|
|
1028
|
+
React.createElement('div', { className: 'dsh-cron-history-list' },
|
|
1029
|
+
taskHistory.map((run) => React.createElement('div', { key: run.id, className: 'dsh-cron-history-item' },
|
|
1030
|
+
React.createElement('div', { className: 'dsh-cron-history-head' },
|
|
1031
|
+
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: '8px' } },
|
|
1032
|
+
React.createElement('span', {
|
|
1033
|
+
className: 'dsh-cron-history-status',
|
|
1034
|
+
'data-status': run.status
|
|
1035
|
+
}, statusLabel(t, run.status)),
|
|
1036
|
+
React.createElement('span', { className: 'dsh-cron-history-time' }, run.at ? new Date(run.at).toLocaleString() : '')
|
|
1037
|
+
),
|
|
1038
|
+
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: '8px' } },
|
|
1039
|
+
run.costUsd > 0 && React.createElement('span', { className: 'dsh-cron-cost-tag' }, '$' + run.costUsd.toFixed(5)),
|
|
1040
|
+
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'),
|
|
1041
|
+
React.createElement('span', { style: { color: 'var(--dsw-alias-label-tertiary, #888)', fontSize: '11.5px' } }, (run.durationMs || 0) + ' ms')
|
|
1042
|
+
)
|
|
1043
|
+
),
|
|
1044
|
+
run.output && React.createElement('div', { className: 'dsh-cron-history-output' }, run.output),
|
|
1045
|
+
run.error && React.createElement('div', { className: 'dsh-cron-history-output', style: { color: 'var(--dsh-cron-danger)', borderColor: 'var(--dsh-cron-danger-bg)' } }, run.error)
|
|
1046
|
+
))
|
|
835
1047
|
),
|
|
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
1048
|
React.createElement('div', { className: 'dsh-cron-modal-foot', style: { marginTop: '18px' } },
|
|
841
1049
|
React.createElement('button', {
|
|
842
1050
|
type: 'button',
|
|
843
1051
|
className: 'dsh-cron-btn-secondary',
|
|
844
1052
|
onClick: () => { if (formId) loadTaskHistory(formId); }
|
|
845
|
-
},
|
|
1053
|
+
}, T_KEY(t, 'history.refresh')),
|
|
846
1054
|
React.createElement('button', {
|
|
847
1055
|
type: 'button',
|
|
848
1056
|
className: 'dsh-cron-btn-primary',
|
|
849
1057
|
onClick: () => setManualModalOpen(false)
|
|
850
|
-
}, '
|
|
1058
|
+
}, T_KEY(t, 'modal.close'))
|
|
851
1059
|
)
|
|
852
1060
|
) :
|
|
853
1061
|
|
|
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' } },
|
|
1062
|
+
React.createElement('form', { onSubmit: handleSaveManual },
|
|
865
1063
|
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 или интервал)'),
|
|
1064
|
+
React.createElement('label', null, T_KEY(t, 'form.nameLabel')),
|
|
877
1065
|
React.createElement('input', {
|
|
878
1066
|
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)
|
|
1067
|
+
autoFocus: true,
|
|
1068
|
+
value: formTitle,
|
|
1069
|
+
placeholder: T_KEY(t, 'form.namePlaceholder'),
|
|
1070
|
+
onChange: (e) => setFormTitle(e.target.value)
|
|
894
1071
|
})
|
|
895
1072
|
),
|
|
896
|
-
React.createElement('div', { className: 'dsh-cron-form-
|
|
897
|
-
React.createElement('
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
1073
|
+
React.createElement('div', { className: 'dsh-cron-form-row' },
|
|
1074
|
+
React.createElement('div', { className: 'dsh-cron-form-group' },
|
|
1075
|
+
React.createElement('label', null, T_KEY(t, 'form.typeLabel')),
|
|
1076
|
+
React.createElement('select', {
|
|
1077
|
+
value: formType,
|
|
1078
|
+
onChange: (e) => setFormType(e.target.value)
|
|
1079
|
+
},
|
|
1080
|
+
React.createElement('option', { value: 'llm' }, T_KEY(t, 'form.typeLlm')),
|
|
1081
|
+
React.createElement('option', { value: 'script' }, T_KEY(t, 'form.typeScript'))
|
|
1082
|
+
)
|
|
1083
|
+
),
|
|
1084
|
+
React.createElement('div', { className: 'dsh-cron-form-group' },
|
|
1085
|
+
React.createElement('label', null, T_KEY(t, 'form.scheduleLabel')),
|
|
1086
|
+
React.createElement('input', {
|
|
1087
|
+
required: true,
|
|
1088
|
+
value: formSchedule,
|
|
1089
|
+
placeholder: T_KEY(t, 'form.schedulePlaceholder'),
|
|
1090
|
+
onChange: (e) => setFormSchedule(e.target.value)
|
|
1091
|
+
})
|
|
905
1092
|
)
|
|
906
|
-
)
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
1093
|
+
),
|
|
1094
|
+
React.createElement('div', { className: 'dsh-cron-form-row' },
|
|
1095
|
+
React.createElement('div', { className: 'dsh-cron-form-group' },
|
|
1096
|
+
React.createElement('label', null, T_KEY(t, 'form.timeoutLabel')),
|
|
1097
|
+
React.createElement('input', {
|
|
1098
|
+
type: 'number',
|
|
1099
|
+
min: 1,
|
|
1100
|
+
value: formTimeoutSeconds,
|
|
1101
|
+
placeholder: '1800',
|
|
1102
|
+
onChange: (e) => setFormTimeoutSeconds(e.target.value)
|
|
1103
|
+
})
|
|
1104
|
+
),
|
|
1105
|
+
React.createElement('div', { className: 'dsh-cron-form-group' },
|
|
1106
|
+
React.createElement('label', null, T_KEY(t, 'form.overlapLabel')),
|
|
1107
|
+
React.createElement('select', {
|
|
1108
|
+
value: formOverlapPolicy,
|
|
1109
|
+
onChange: (e) => setFormOverlapPolicy(e.target.value)
|
|
1110
|
+
},
|
|
1111
|
+
React.createElement('option', { value: 'skip' }, T_KEY(t, 'form.overlapSkip')),
|
|
1112
|
+
React.createElement('option', { value: 'queue' }, T_KEY(t, 'form.overlapQueue')),
|
|
1113
|
+
React.createElement('option', { value: 'replace' }, T_KEY(t, 'form.overlapReplace'))
|
|
1114
|
+
)
|
|
928
1115
|
)
|
|
929
1116
|
),
|
|
930
1117
|
React.createElement('div', { className: 'dsh-cron-form-group' },
|
|
931
|
-
React.createElement('label', null,
|
|
1118
|
+
React.createElement('label', null, T_KEY(t, 'form.kanbanLabel')),
|
|
932
1119
|
React.createElement('select', {
|
|
933
|
-
value:
|
|
934
|
-
onChange: (e) =>
|
|
1120
|
+
value: formKanbanMode,
|
|
1121
|
+
onChange: (e) => setFormKanbanMode(e.target.value)
|
|
935
1122
|
},
|
|
936
|
-
React.createElement('option', { value: '' },
|
|
937
|
-
|
|
1123
|
+
React.createElement('option', { value: 'none' }, T_KEY(t, 'form.kanbanNone')),
|
|
1124
|
+
React.createElement('option', { value: 'on_failure' }, T_KEY(t, 'form.kanbanOnFailure')),
|
|
1125
|
+
React.createElement('option', { value: 'always' }, T_KEY(t, 'form.kanbanAlways'))
|
|
938
1126
|
)
|
|
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
1127
|
),
|
|
965
|
-
React.createElement('
|
|
966
|
-
React.createElement('
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
1128
|
+
formType !== 'script' && React.createElement('div', { className: 'dsh-cron-form-row' },
|
|
1129
|
+
React.createElement('div', { className: 'dsh-cron-form-group' },
|
|
1130
|
+
React.createElement('label', null, T_KEY(t, 'form.providerLabel')),
|
|
1131
|
+
React.createElement('select', {
|
|
1132
|
+
value: formProvider,
|
|
1133
|
+
onChange: handleProviderChange
|
|
1134
|
+
},
|
|
1135
|
+
React.createElement('option', { value: '' }, T_KEY(t, 'form.providerDefault')),
|
|
1136
|
+
providers.map((p) => React.createElement('option', { key: p.id, value: p.id }, p.name || p.id))
|
|
1137
|
+
)
|
|
1138
|
+
),
|
|
1139
|
+
React.createElement('div', { className: 'dsh-cron-form-group' },
|
|
1140
|
+
React.createElement('label', null, T_KEY(t, 'form.modelLabel')),
|
|
1141
|
+
React.createElement('select', {
|
|
1142
|
+
value: formModel,
|
|
1143
|
+
onChange: (e) => setFormModel(e.target.value)
|
|
1144
|
+
},
|
|
1145
|
+
React.createElement('option', { value: '' }, T_KEY(t, 'form.modelDefault')),
|
|
1146
|
+
models.map((m) => React.createElement('option', { key: m.id, value: m.id }, m.name || m.id))
|
|
1147
|
+
)
|
|
1148
|
+
)
|
|
972
1149
|
),
|
|
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() : 'не запланирован')
|
|
1150
|
+
React.createElement('div', { className: 'dsh-cron-form-group' },
|
|
1151
|
+
React.createElement('label', null, formType === 'script' ? T_KEY(t, 'form.promptLabelScript') : T_KEY(t, 'form.promptLabelLlm')),
|
|
1152
|
+
React.createElement('textarea', {
|
|
1153
|
+
required: true,
|
|
1154
|
+
rows: 9,
|
|
1155
|
+
style: { fontFamily: 'monospace', fontSize: '12.5px', lineHeight: '1.45' },
|
|
1156
|
+
value: formPrompt,
|
|
1157
|
+
placeholder: formType === 'script' ? T_KEY(t, 'form.promptPlaceholderScript') : T_KEY(t, 'form.promptPlaceholderLlm'),
|
|
1158
|
+
onChange: (e) => setFormPrompt(e.target.value)
|
|
1159
|
+
})
|
|
999
1160
|
),
|
|
1000
|
-
React.createElement('
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1161
|
+
React.createElement('div', {
|
|
1162
|
+
style: {
|
|
1163
|
+
background: 'var(--dsw-alias-bg-layer-3, rgba(255,255,255,0.02))',
|
|
1164
|
+
border: '1px solid var(--dsw-alias-border-l2, #2e2e2e)',
|
|
1165
|
+
borderRadius: '8px',
|
|
1166
|
+
padding: '12px 14px',
|
|
1167
|
+
marginBottom: '16px'
|
|
1007
1168
|
}
|
|
1008
|
-
},
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
className: 'dsh-cron-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1169
|
+
},
|
|
1170
|
+
React.createElement('div', { style: { fontWeight: 500, fontSize: '13px', color: 'var(--dsw-alias-label-primary, #eee)', marginBottom: '8px', display: 'flex', alignItems: 'center', gap: '6px' } },
|
|
1171
|
+
React.createElement('span', { style: { display: 'flex', alignItems: 'center', color: 'var(--dsh-cron-info)' }, ...iconHtml(ICON_TELEGRAM) }),
|
|
1172
|
+
T_KEY(t, 'form.notifyTitle')
|
|
1173
|
+
),
|
|
1174
|
+
React.createElement('label', { className: 'dsh-cron-checkbox-row', style: { marginBottom: formNotifyTelegram ? '8px' : '0' } },
|
|
1175
|
+
React.createElement('input', {
|
|
1176
|
+
type: 'checkbox',
|
|
1177
|
+
checked: formNotifyTelegram,
|
|
1178
|
+
onChange: (e) => setFormNotifyTelegram(e.target.checked)
|
|
1179
|
+
}),
|
|
1180
|
+
React.createElement('span', null, T_KEY(t, 'form.notifyTelegram'))
|
|
1181
|
+
),
|
|
1182
|
+
formNotifyTelegram && React.createElement('label', { className: 'dsh-cron-checkbox-row', style: { marginLeft: '24px', marginTop: '6px' } },
|
|
1183
|
+
React.createElement('input', {
|
|
1184
|
+
type: 'checkbox',
|
|
1185
|
+
checked: formOnlyOnFailure,
|
|
1186
|
+
onChange: (e) => setFormOnlyOnFailure(e.target.checked)
|
|
1187
|
+
}),
|
|
1188
|
+
React.createElement('span', null, T_KEY(t, 'form.notifyOnlyFailure'))
|
|
1189
|
+
)
|
|
1190
|
+
),
|
|
1191
|
+
selectedTaskMeta && React.createElement('div', {
|
|
1192
|
+
style: {
|
|
1193
|
+
background: 'var(--dsw-alias-bg-layer-3, rgba(255,255,255,0.03))',
|
|
1194
|
+
border: '1px solid var(--dsw-alias-border-l2, #2e2e2e)',
|
|
1195
|
+
borderRadius: '8px',
|
|
1196
|
+
padding: '10px 14px',
|
|
1197
|
+
fontSize: '12.5px',
|
|
1198
|
+
color: 'var(--dsw-alias-label-secondary, #999)',
|
|
1199
|
+
marginBottom: '16px',
|
|
1200
|
+
display: 'flex',
|
|
1201
|
+
justifyContent: 'space-between',
|
|
1202
|
+
alignItems: 'center'
|
|
1203
|
+
}
|
|
1204
|
+
},
|
|
1205
|
+
React.createElement('div', null,
|
|
1206
|
+
React.createElement('span', null, T_KEY(t, 'form.nextRunLabel')),
|
|
1207
|
+
React.createElement('strong', { style: { color: 'var(--dsw-alias-label-primary, #eee)' } }, selectedTaskMeta.nextRunAt ? new Date(selectedTaskMeta.nextRunAt).toLocaleString() : T_KEY(t, 'form.notScheduled'))
|
|
1208
|
+
),
|
|
1209
|
+
React.createElement('button', {
|
|
1210
|
+
type: 'button',
|
|
1211
|
+
className: 'dsh-cron-btn-secondary',
|
|
1212
|
+
style: { padding: '4px 10px', fontSize: '12px' },
|
|
1213
|
+
onClick: async () => {
|
|
1214
|
+
await handleRunNow(selectedTaskMeta.id);
|
|
1215
|
+
if (formId) loadTaskHistory(formId);
|
|
1216
|
+
}
|
|
1217
|
+
}, T_KEY(t, 'actions.runNow'))
|
|
1218
|
+
),
|
|
1219
|
+
React.createElement('div', { className: 'dsh-cron-modal-foot' },
|
|
1220
|
+
React.createElement('button', {
|
|
1221
|
+
type: 'button',
|
|
1222
|
+
className: 'dsh-cron-btn-secondary',
|
|
1223
|
+
onClick: () => setManualModalOpen(false)
|
|
1224
|
+
}, T_KEY(t, 'modal.cancel')),
|
|
1225
|
+
React.createElement('button', {
|
|
1226
|
+
type: 'submit',
|
|
1227
|
+
className: 'dsh-cron-btn-primary'
|
|
1228
|
+
}, formId ? T_KEY(t, 'modal.save') : T_KEY(t, 'modal.create'))
|
|
1229
|
+
)
|
|
1020
1230
|
)
|
|
1021
|
-
)
|
|
1022
1231
|
)
|
|
1023
1232
|
),
|
|
1024
1233
|
|
|
1025
|
-
//
|
|
1234
|
+
// 2. Settings modal
|
|
1026
1235
|
settingsModalOpen && React.createElement('div', { className: 'dsh-cron-modal-overlay', onClick: () => setSettingsModalOpen(false) },
|
|
1027
|
-
React.createElement('div', { className: 'dsh-cron-modal', onClick: (e) => e.stopPropagation() },
|
|
1236
|
+
React.createElement('div', { className: 'dsh-cron-modal', role: 'dialog', 'aria-modal': 'true', onClick: (e) => e.stopPropagation() },
|
|
1028
1237
|
React.createElement('div', { className: 'dsh-cron-modal-head', style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '18px' } },
|
|
1029
1238
|
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
|
-
|
|
1239
|
+
React.createElement('span', { style: { display: 'flex', alignItems: 'center', color: 'var(--dsh-cron-info)' }, ...iconHtml(ICON_TELEGRAM) }),
|
|
1240
|
+
T_KEY(t, 'settings.modalTitle')
|
|
1032
1241
|
),
|
|
1033
1242
|
React.createElement('button', {
|
|
1034
1243
|
type: 'button',
|
|
1035
|
-
style: { background: 'none', border: 'none', color: '#888', cursor: 'pointer', fontSize: '18px' },
|
|
1244
|
+
style: { background: 'none', border: 'none', color: 'var(--dsw-alias-label-tertiary, #888)', cursor: 'pointer', fontSize: '18px' },
|
|
1036
1245
|
onClick: () => setSettingsModalOpen(false)
|
|
1037
1246
|
}, '✕')
|
|
1038
1247
|
),
|
|
1039
1248
|
React.createElement('form', { onSubmit: handleSaveSettings },
|
|
1040
1249
|
React.createElement('div', { className: 'dsh-cron-form-group' },
|
|
1041
|
-
React.createElement('label', null,
|
|
1250
|
+
React.createElement('label', null, T_KEY(t, 'settings.tokenLabel')),
|
|
1042
1251
|
React.createElement('input', {
|
|
1043
1252
|
type: 'password',
|
|
1044
1253
|
value: settingsBotToken,
|
|
1045
|
-
placeholder: settingsHasDefault ?
|
|
1254
|
+
placeholder: settingsHasDefault ? T_KEY(t, 'settings.tokenPlaceholderDefault') : T_KEY(t, 'settings.tokenPlaceholder'),
|
|
1046
1255
|
onChange: (e) => setSettingsBotToken(e.target.value)
|
|
1047
1256
|
}),
|
|
1048
|
-
settingsHasDefault && React.createElement('div', { className: 'dsh-cron-hint' },
|
|
1257
|
+
settingsHasDefault && React.createElement('div', { className: 'dsh-cron-hint' }, T_KEY(t, 'settings.defaultHint'))
|
|
1049
1258
|
),
|
|
1050
1259
|
React.createElement('div', { className: 'dsh-cron-form-group' },
|
|
1051
|
-
React.createElement('label', null,
|
|
1260
|
+
React.createElement('label', null, T_KEY(t, 'settings.chatIdLabel')),
|
|
1052
1261
|
React.createElement('input', {
|
|
1053
1262
|
value: settingsChatId,
|
|
1054
|
-
placeholder:
|
|
1263
|
+
placeholder: T_KEY(t, 'settings.chatIdPlaceholder'),
|
|
1055
1264
|
onChange: (e) => setSettingsChatId(e.target.value)
|
|
1056
1265
|
})
|
|
1057
1266
|
),
|
|
1058
|
-
React.createElement('div', { style: { background: 'rgba(255,255,255,0.02)', border: '1px solid #2e2e2e', borderRadius: '8px', padding: '12px 14px', marginBottom: '16px' } },
|
|
1267
|
+
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
1268
|
React.createElement('label', { className: 'dsh-cron-checkbox-row', style: { marginBottom: '8px' } },
|
|
1060
1269
|
React.createElement('input', {
|
|
1061
1270
|
type: 'checkbox',
|
|
1062
1271
|
checked: settingsNotifyGlobal,
|
|
1063
1272
|
onChange: (e) => setSettingsNotifyGlobal(e.target.checked)
|
|
1064
1273
|
}),
|
|
1065
|
-
React.createElement('span', null,
|
|
1274
|
+
React.createElement('span', null, T_KEY(t, 'settings.globalNotify'))
|
|
1066
1275
|
),
|
|
1067
1276
|
React.createElement('label', { className: 'dsh-cron-checkbox-row' },
|
|
1068
1277
|
React.createElement('input', {
|
|
@@ -1070,7 +1279,7 @@ const React = window.React || require('react');
|
|
|
1070
1279
|
checked: settingsOnlyOnFailureGlobal,
|
|
1071
1280
|
onChange: (e) => setSettingsOnlyOnFailureGlobal(e.target.checked)
|
|
1072
1281
|
}),
|
|
1073
|
-
React.createElement('span', null,
|
|
1282
|
+
React.createElement('span', null, T_KEY(t, 'settings.globalOnlyFailure'))
|
|
1074
1283
|
)
|
|
1075
1284
|
),
|
|
1076
1285
|
React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '14px' } },
|
|
@@ -1080,7 +1289,7 @@ const React = window.React || require('react');
|
|
|
1080
1289
|
style: { display: 'inline-flex', alignItems: 'center', gap: '6px' },
|
|
1081
1290
|
disabled: testStatus?.loading,
|
|
1082
1291
|
onClick: handleTestTelegram
|
|
1083
|
-
}, testStatus?.loading ? '
|
|
1292
|
+
}, testStatus?.loading ? T_KEY(t, 'settings.sending') : T_KEY(t, 'settings.testTelegram'))
|
|
1084
1293
|
),
|
|
1085
1294
|
testStatus && testStatus.message && React.createElement('div', {
|
|
1086
1295
|
className: 'dsh-cron-status-banner',
|
|
@@ -1091,28 +1300,29 @@ const React = window.React || require('react');
|
|
|
1091
1300
|
type: 'button',
|
|
1092
1301
|
className: 'dsh-cron-btn-secondary',
|
|
1093
1302
|
onClick: () => setSettingsModalOpen(false)
|
|
1094
|
-
}, '
|
|
1303
|
+
}, T_KEY(t, 'modal.cancel')),
|
|
1095
1304
|
React.createElement('button', {
|
|
1096
1305
|
type: 'submit',
|
|
1097
1306
|
className: 'dsh-cron-btn-primary'
|
|
1098
|
-
},
|
|
1307
|
+
}, T_KEY(t, 'settings.save'))
|
|
1099
1308
|
)
|
|
1100
1309
|
)
|
|
1101
1310
|
)
|
|
1102
1311
|
),
|
|
1103
|
-
|
|
1312
|
+
|
|
1313
|
+
// 3. "Create with DSH" modal
|
|
1104
1314
|
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' },
|
|
1315
|
+
React.createElement('div', { className: 'dsh-cron-modal', role: 'dialog', 'aria-modal': 'true', onClick: (e) => e.stopPropagation() },
|
|
1316
|
+
React.createElement('div', { className: 'dsh-cron-modal-title' }, T_KEY(t, 'dsh.modalTitle')),
|
|
1107
1317
|
React.createElement('div', { className: 'dsh-cron-form-group' },
|
|
1108
|
-
React.createElement('label', null,
|
|
1318
|
+
React.createElement('label', null, T_KEY(t, 'dsh.descLabel')),
|
|
1109
1319
|
React.createElement('div', { className: 'dsh-cron-chat-box' },
|
|
1110
1320
|
React.createElement('textarea', {
|
|
1111
1321
|
className: 'dsh-cron-chat-input',
|
|
1112
1322
|
rows: 4,
|
|
1113
1323
|
autoFocus: true,
|
|
1114
1324
|
disabled: dshLoading,
|
|
1115
|
-
placeholder:
|
|
1325
|
+
placeholder: T_KEY(t, 'dsh.placeholder'),
|
|
1116
1326
|
value: dshPrompt,
|
|
1117
1327
|
onKeyDown: (e) => {
|
|
1118
1328
|
if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
|
|
@@ -1123,18 +1333,15 @@ const React = window.React || require('react');
|
|
|
1123
1333
|
onChange: (e) => setDshPrompt(e.target.value)
|
|
1124
1334
|
}),
|
|
1125
1335
|
React.createElement('div', { className: 'dsh-cron-chat-foot' },
|
|
1126
|
-
React.createElement('span', { className: 'dsh-cron-chat-hint' },
|
|
1336
|
+
React.createElement('span', { className: 'dsh-cron-chat-hint' }, T_KEY(t, 'dsh.sendHint')),
|
|
1127
1337
|
React.createElement('button', {
|
|
1128
1338
|
type: 'button',
|
|
1129
1339
|
className: 'dsh-cron-send-btn',
|
|
1130
1340
|
disabled: dshLoading || !dshPrompt.trim(),
|
|
1131
|
-
title:
|
|
1341
|
+
title: T_KEY(t, 'dsh.sendTitle'),
|
|
1132
1342
|
onClick: handleStartWithDSH
|
|
1133
1343
|
},
|
|
1134
|
-
React.createElement('span',
|
|
1135
|
-
style: { display: 'flex', alignItems: 'center' },
|
|
1136
|
-
dangerouslySetInnerHTML: { __html: ICON_SEND }
|
|
1137
|
-
})
|
|
1344
|
+
React.createElement('span', svgProps(ICON_SEND))
|
|
1138
1345
|
)
|
|
1139
1346
|
)
|
|
1140
1347
|
)
|
|
@@ -1145,7 +1352,7 @@ const React = window.React || require('react');
|
|
|
1145
1352
|
className: 'dsh-cron-btn-secondary',
|
|
1146
1353
|
disabled: dshLoading,
|
|
1147
1354
|
onClick: () => setDshModalOpen(false)
|
|
1148
|
-
}, '
|
|
1355
|
+
}, T_KEY(t, 'modal.cancel'))
|
|
1149
1356
|
)
|
|
1150
1357
|
)
|
|
1151
1358
|
)
|
|
@@ -1154,6 +1361,7 @@ const React = window.React || require('react');
|
|
|
1154
1361
|
|
|
1155
1362
|
function CronSettingsCard(props) {
|
|
1156
1363
|
const { ctx, toggle } = props;
|
|
1364
|
+
const t = props.t || translate;
|
|
1157
1365
|
const [token, setToken] = React.useState('');
|
|
1158
1366
|
const [chatId, setChatId] = React.useState('');
|
|
1159
1367
|
const [notify, setNotify] = React.useState(false);
|
|
@@ -1162,24 +1370,36 @@ const React = window.React || require('react');
|
|
|
1162
1370
|
const [hasDefault, setHasDefault] = React.useState(false);
|
|
1163
1371
|
const [saved, setSaved] = React.useState(false);
|
|
1164
1372
|
const [testInfo, setTestInfo] = React.useState(null);
|
|
1373
|
+
// Collapsed by default, head is the toggle (canonical card contract, #100)
|
|
1374
|
+
const [cardOpen, setCardOpen] = React.useState(false);
|
|
1375
|
+
|
|
1376
|
+
// Settings snapshot status (#102): never render phantom inputs before
|
|
1377
|
+
// the snapshot arrives; offer a retry when it is unavailable.
|
|
1378
|
+
const [loadState, setLoadState] = React.useState('loading');
|
|
1379
|
+
const [reloadTick, setReloadTick] = React.useState(0);
|
|
1165
1380
|
|
|
1166
1381
|
React.useEffect(() => {
|
|
1167
1382
|
let active = true;
|
|
1383
|
+
setLoadState('loading');
|
|
1168
1384
|
fetch('/dsh-cron/settings')
|
|
1169
1385
|
.then(r => r.json())
|
|
1170
1386
|
.then(data => {
|
|
1171
|
-
if (active
|
|
1387
|
+
if (!active) return;
|
|
1388
|
+
if (data && data.ok && data.settings) {
|
|
1172
1389
|
setToken(data.settings.botToken || '');
|
|
1173
1390
|
setChatId(data.settings.chatId || '');
|
|
1174
1391
|
setNotify(Boolean(data.settings.notifyTelegram));
|
|
1175
1392
|
setOnlyFailure(Boolean(data.settings.onlyOnFailure));
|
|
1176
1393
|
setKanbanUrl(data.settings.kanbanBaseUrl || 'http://127.0.0.1:3000');
|
|
1177
1394
|
setHasDefault(Boolean(data.settings.hasDefaultCredentials));
|
|
1395
|
+
setLoadState('ready');
|
|
1396
|
+
} else {
|
|
1397
|
+
setLoadState('error');
|
|
1178
1398
|
}
|
|
1179
1399
|
})
|
|
1180
|
-
.catch(() => {});
|
|
1400
|
+
.catch(() => { if (active) setLoadState('error'); });
|
|
1181
1401
|
return () => { active = false; };
|
|
1182
|
-
}, []);
|
|
1402
|
+
}, [reloadTick]);
|
|
1183
1403
|
|
|
1184
1404
|
const saveSettings = async (e) => {
|
|
1185
1405
|
if (e) e.preventDefault();
|
|
@@ -1204,7 +1424,7 @@ const React = window.React || require('react');
|
|
|
1204
1424
|
};
|
|
1205
1425
|
|
|
1206
1426
|
const testTelegram = async () => {
|
|
1207
|
-
setTestInfo({ loading: true, message: '
|
|
1427
|
+
setTestInfo({ loading: true, message: T_KEY(t, 'settings.sending') });
|
|
1208
1428
|
try {
|
|
1209
1429
|
const res = await fetch('/dsh-cron/telegram/test', {
|
|
1210
1430
|
method: 'POST',
|
|
@@ -1213,17 +1433,17 @@ const React = window.React || require('react');
|
|
|
1213
1433
|
});
|
|
1214
1434
|
const data = await res.json();
|
|
1215
1435
|
if (data && data.ok) {
|
|
1216
|
-
setTestInfo({ success: true, message:
|
|
1436
|
+
setTestInfo({ success: true, message: T_KEY(t, 'settings.testOk') });
|
|
1217
1437
|
} else {
|
|
1218
|
-
setTestInfo({ success: false, message:
|
|
1438
|
+
setTestInfo({ success: false, message: T_KEY(t, 'settings.testFail', { error: (data && data.error) || '' }) });
|
|
1219
1439
|
}
|
|
1220
1440
|
} catch (err) {
|
|
1221
|
-
setTestInfo({ success: false, message:
|
|
1441
|
+
setTestInfo({ success: false, message: T_KEY(t, 'settings.testFail', { error: err.message }) });
|
|
1222
1442
|
}
|
|
1223
1443
|
};
|
|
1224
1444
|
|
|
1225
1445
|
const testKanban = async () => {
|
|
1226
|
-
setTestInfo({ loading: true, message:
|
|
1446
|
+
setTestInfo({ loading: true, message: T_KEY(t, 'settings.checkingKanban') });
|
|
1227
1447
|
try {
|
|
1228
1448
|
const res = await fetch('/dsh-cron/kanban/test', {
|
|
1229
1449
|
method: 'POST',
|
|
@@ -1232,62 +1452,87 @@ const React = window.React || require('react');
|
|
|
1232
1452
|
});
|
|
1233
1453
|
const data = await res.json();
|
|
1234
1454
|
if (data && data.ok) {
|
|
1235
|
-
setTestInfo({ success: true, message:
|
|
1455
|
+
setTestInfo({ success: true, message: T_KEY(t, 'settings.kanbanOk') });
|
|
1236
1456
|
} else {
|
|
1237
|
-
setTestInfo({ success: false, message:
|
|
1457
|
+
setTestInfo({ success: false, message: T_KEY(t, 'settings.kanbanFail', { error: (data && data.error) || '' }) });
|
|
1238
1458
|
}
|
|
1239
1459
|
} catch (err) {
|
|
1240
|
-
setTestInfo({ success: false, message:
|
|
1460
|
+
setTestInfo({ success: false, message: T_KEY(t, 'settings.kanbanFail', { error: err.message }) });
|
|
1241
1461
|
}
|
|
1242
1462
|
};
|
|
1243
1463
|
|
|
1464
|
+
if (loadState !== 'ready') {
|
|
1465
|
+
return React.createElement('div', { className: 'dsh-cron-card' },
|
|
1466
|
+
React.createElement('div', { className: 'dsh-cron-card-title' }, T_KEY(t, 'settings.cardTitle')),
|
|
1467
|
+
React.createElement('div', {
|
|
1468
|
+
className: 'dsh-cron-status-banner',
|
|
1469
|
+
'data-type': loadState === 'error' ? 'error' : 'info',
|
|
1470
|
+
style: { marginTop: '10px', display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: '10px' }
|
|
1471
|
+
},
|
|
1472
|
+
React.createElement('span', null, loadState === 'error' ? T_KEY(t, 'settings.unavailable') : T_KEY(t, 'settings.loading')),
|
|
1473
|
+
loadState === 'error' && React.createElement('button', {
|
|
1474
|
+
type: 'button',
|
|
1475
|
+
className: 'dsh-cron-btn-secondary',
|
|
1476
|
+
onClick: () => setReloadTick(x => x + 1)
|
|
1477
|
+
}, T_KEY(t, 'settings.retry'))
|
|
1478
|
+
)
|
|
1479
|
+
);
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1244
1482
|
return React.createElement('div', { className: 'dsh-cron-card' },
|
|
1245
|
-
React.createElement('
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
React.createElement('div', { className: 'dsh-cron-card-desc', style: { marginBottom: '16px' } },
|
|
1254
|
-
'Автоматическое выполнение задач по расписанию (cron/интервалы), фоновые сценарии агентов и интеграция с Telegram и Kanban.'
|
|
1483
|
+
React.createElement('button', {
|
|
1484
|
+
type: 'button',
|
|
1485
|
+
className: 'dsh-cron-card-head-btn',
|
|
1486
|
+
'aria-expanded': cardOpen ? 'true' : 'false',
|
|
1487
|
+
onClick: () => setCardOpen(!cardOpen)
|
|
1488
|
+
},
|
|
1489
|
+
React.createElement('span', { className: 'dsh-cron-card-title' }, T_KEY(t, 'settings.cardTitle')),
|
|
1490
|
+
React.createElement('span', { className: 'dsh-cron-chev' + (cardOpen ? ' dsh-cron-chev-open' : '') }, chevronNode())
|
|
1255
1491
|
),
|
|
1256
|
-
React.createElement('
|
|
1492
|
+
cardOpen && React.createElement('div', { className: 'dsh-cron-card-body' },
|
|
1493
|
+
React.createElement('div', { className: 'dsh-cron-card-desc' }, T_KEY(t, 'settings.cardDesc')),
|
|
1494
|
+
React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', marginBottom: '12px' } },
|
|
1495
|
+
React.createElement('button', {
|
|
1496
|
+
type: 'button',
|
|
1497
|
+
className: 'dsh-cron-btn-primary',
|
|
1498
|
+
onClick: () => { if (toggle) toggle.set(true); }
|
|
1499
|
+
}, T_KEY(t, 'settings.openPanel'))
|
|
1500
|
+
),
|
|
1501
|
+
React.createElement('form', { onSubmit: saveSettings, style: { display: 'flex', flexDirection: 'column', gap: '12px' } },
|
|
1257
1502
|
React.createElement('div', { className: 'dsh-cron-form-group', style: { marginBottom: 0 } },
|
|
1258
|
-
React.createElement('label', null,
|
|
1503
|
+
React.createElement('label', null, T_KEY(t, 'settings.tokenLabel')),
|
|
1259
1504
|
React.createElement('input', {
|
|
1260
1505
|
type: 'password',
|
|
1261
1506
|
value: token,
|
|
1262
|
-
placeholder: hasDefault ?
|
|
1507
|
+
placeholder: hasDefault ? T_KEY(t, 'settings.tokenPlaceholderDefault') : T_KEY(t, 'settings.tokenPlaceholder'),
|
|
1263
1508
|
onChange: (e) => setToken(e.target.value)
|
|
1264
1509
|
}),
|
|
1265
|
-
hasDefault && React.createElement('div', { className: 'dsh-cron-hint' },
|
|
1510
|
+
hasDefault && React.createElement('div', { className: 'dsh-cron-hint' }, T_KEY(t, 'settings.defaultHint'))
|
|
1266
1511
|
),
|
|
1267
1512
|
React.createElement('div', { className: 'dsh-cron-form-group', style: { marginBottom: 0 } },
|
|
1268
|
-
React.createElement('label', null,
|
|
1513
|
+
React.createElement('label', null, T_KEY(t, 'settings.chatIdLabel')),
|
|
1269
1514
|
React.createElement('input', {
|
|
1270
1515
|
value: chatId,
|
|
1271
|
-
placeholder:
|
|
1516
|
+
placeholder: T_KEY(t, 'settings.chatIdPlaceholder'),
|
|
1272
1517
|
onChange: (e) => setChatId(e.target.value)
|
|
1273
1518
|
})
|
|
1274
1519
|
),
|
|
1275
1520
|
React.createElement('div', { className: 'dsh-cron-form-group', style: { marginBottom: 0 } },
|
|
1276
|
-
React.createElement('label', null,
|
|
1521
|
+
React.createElement('label', null, T_KEY(t, 'settings.kanbanUrlLabel')),
|
|
1277
1522
|
React.createElement('input', {
|
|
1278
1523
|
value: kanbanUrl,
|
|
1279
1524
|
placeholder: 'http://127.0.0.1:3000',
|
|
1280
1525
|
onChange: (e) => setKanbanUrl(e.target.value)
|
|
1281
1526
|
})
|
|
1282
1527
|
),
|
|
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' } },
|
|
1528
|
+
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
1529
|
React.createElement('label', { className: 'dsh-cron-checkbox-row' },
|
|
1285
1530
|
React.createElement('input', {
|
|
1286
1531
|
type: 'checkbox',
|
|
1287
1532
|
checked: notify,
|
|
1288
1533
|
onChange: (e) => setNotify(e.target.checked)
|
|
1289
1534
|
}),
|
|
1290
|
-
React.createElement('span', null,
|
|
1535
|
+
React.createElement('span', null, T_KEY(t, 'settings.globalNotify'))
|
|
1291
1536
|
),
|
|
1292
1537
|
React.createElement('label', { className: 'dsh-cron-checkbox-row' },
|
|
1293
1538
|
React.createElement('input', {
|
|
@@ -1295,7 +1540,7 @@ const React = window.React || require('react');
|
|
|
1295
1540
|
checked: onlyFailure,
|
|
1296
1541
|
onChange: (e) => setOnlyFailure(e.target.checked)
|
|
1297
1542
|
}),
|
|
1298
|
-
React.createElement('span', null,
|
|
1543
|
+
React.createElement('span', null, T_KEY(t, 'settings.globalOnlyFailure'))
|
|
1299
1544
|
)
|
|
1300
1545
|
),
|
|
1301
1546
|
testInfo && testInfo.message && React.createElement('div', {
|
|
@@ -1308,22 +1553,23 @@ const React = window.React || require('react');
|
|
|
1308
1553
|
type: 'submit',
|
|
1309
1554
|
className: 'dsh-cron-btn-primary',
|
|
1310
1555
|
style: { minWidth: '140px' }
|
|
1311
|
-
}, saved ?
|
|
1556
|
+
}, saved ? T_KEY(t, 'settings.saved') : T_KEY(t, 'settings.save')),
|
|
1312
1557
|
React.createElement('button', {
|
|
1313
1558
|
type: 'button',
|
|
1314
1559
|
className: 'dsh-cron-btn-secondary',
|
|
1315
1560
|
onClick: testTelegram,
|
|
1316
1561
|
disabled: testInfo?.loading
|
|
1317
|
-
},
|
|
1562
|
+
}, T_KEY(t, 'settings.testTelegram')),
|
|
1318
1563
|
React.createElement('button', {
|
|
1319
1564
|
type: 'button',
|
|
1320
1565
|
className: 'dsh-cron-btn-secondary',
|
|
1321
1566
|
onClick: testKanban,
|
|
1322
1567
|
disabled: testInfo?.loading
|
|
1323
|
-
},
|
|
1568
|
+
}, T_KEY(t, 'settings.testKanban'))
|
|
1324
1569
|
)
|
|
1325
1570
|
)
|
|
1326
|
-
)
|
|
1571
|
+
)
|
|
1572
|
+
);
|
|
1327
1573
|
}
|
|
1328
1574
|
|
|
1329
1575
|
function nativeButton() {
|
|
@@ -1338,7 +1584,7 @@ const React = window.React || require('react');
|
|
|
1338
1584
|
const entry = target.cloneNode(true);
|
|
1339
1585
|
entry.className = String(target.className) + ' dsh-cron-entry-clone';
|
|
1340
1586
|
entry.setAttribute(ENTRY_ATTR, '');
|
|
1341
|
-
entry.setAttribute('data-dsh-plugin', 'cron');
|
|
1587
|
+
entry.setAttribute('data-dsh-plugin', 'dsh-cron');
|
|
1342
1588
|
entry.setAttribute('data-dsh-part', 'sidebar-entry');
|
|
1343
1589
|
entry.setAttribute('aria-label', label);
|
|
1344
1590
|
entry.removeAttribute('id');
|
|
@@ -1416,13 +1662,13 @@ const React = window.React || require('react');
|
|
|
1416
1662
|
if (column === null) return;
|
|
1417
1663
|
container = document.createElement('div');
|
|
1418
1664
|
container.setAttribute(VIEW_ATTR, '');
|
|
1419
|
-
container.setAttribute('data-dsh-plugin', 'cron');
|
|
1665
|
+
container.setAttribute('data-dsh-plugin', 'dsh-cron');
|
|
1420
1666
|
column.appendChild(container);
|
|
1421
1667
|
if (ReactDOM && typeof ReactDOM.createRoot === 'function') {
|
|
1422
1668
|
root = ReactDOM.createRoot(container);
|
|
1423
|
-
root.render(React.createElement(CronScreen, { ctx, toggle, onClose: () => toggle.set(false) }));
|
|
1669
|
+
root.render(React.createElement(CronScreen, { ctx, toggle, onClose: () => toggle.set(false), t: translate }));
|
|
1424
1670
|
} else if (ReactDOM && typeof ReactDOM.render === 'function') {
|
|
1425
|
-
ReactDOM.render(React.createElement(CronScreen, { ctx, toggle, onClose: () => toggle.set(false) }), container);
|
|
1671
|
+
ReactDOM.render(React.createElement(CronScreen, { ctx, toggle, onClose: () => toggle.set(false), t: translate }), container);
|
|
1426
1672
|
}
|
|
1427
1673
|
};
|
|
1428
1674
|
|
|
@@ -1465,43 +1711,82 @@ const React = window.React || require('react');
|
|
|
1465
1711
|
};
|
|
1466
1712
|
}
|
|
1467
1713
|
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1714
|
+
/**
|
|
1715
|
+
* Register a card into a DSH mount point. Mount points are populated via
|
|
1716
|
+
* ctx.slots.inject(mount, cb) — a direct slots.register does not throw but
|
|
1717
|
+
* also never appears in the target surface (settings plugins tab, header),
|
|
1718
|
+
* so the inject contract is mandatory (#85).
|
|
1719
|
+
*/
|
|
1720
|
+
function registerIntoMount(ctx, mountName, entry, component) {
|
|
1721
|
+
if (!ctx || !ctx.slots || typeof ctx.slots.inject !== 'function') return false;
|
|
1722
|
+
try {
|
|
1723
|
+
return !!ctx.slots.inject(mountName, () => {
|
|
1724
|
+
ctx.slots.register(entry, component);
|
|
1725
|
+
});
|
|
1726
|
+
} catch (err) {
|
|
1727
|
+
console.warn('[dsh-cron] slot injection skipped:', mountName, '-', err && err.message);
|
|
1728
|
+
return false;
|
|
1479
1729
|
}
|
|
1480
|
-
return undefined;
|
|
1481
1730
|
}
|
|
1482
1731
|
|
|
1483
1732
|
function apply(ctx) {
|
|
1484
1733
|
ensureStyles();
|
|
1734
|
+
|
|
1735
|
+
// Register the English canonical strings; rebinding the translator lets
|
|
1736
|
+
// the translation plugin drive the active language at runtime (#87).
|
|
1737
|
+
try {
|
|
1738
|
+
if (ctx.locale && typeof ctx.locale.register === 'function') {
|
|
1739
|
+
ctx.locale.register(NS, STRINGS);
|
|
1740
|
+
}
|
|
1741
|
+
} catch (e) {
|
|
1742
|
+
// Re-registration in the same session throws — the dictionary is live already.
|
|
1743
|
+
}
|
|
1744
|
+
try {
|
|
1745
|
+
if (ctx.locale && typeof ctx.locale.bind === 'function') {
|
|
1746
|
+
translate = ctx.locale.bind(NS);
|
|
1747
|
+
}
|
|
1748
|
+
} catch (e) {}
|
|
1749
|
+
|
|
1485
1750
|
const toggle = createToggle();
|
|
1486
1751
|
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1752
|
+
// The settings card lives ONLY in the plugins settings tab (#102): no
|
|
1753
|
+
// top-level section fallback — the flat sidebar list is a shared core
|
|
1754
|
+
// resource, and the card slot is proven on current builds (#85).
|
|
1755
|
+
const cardOk = registerIntoMount(ctx, 'settings.plugin.item', {
|
|
1756
|
+
name: 'settings.plugin.item',
|
|
1757
|
+
key: NS,
|
|
1758
|
+
locale: NS,
|
|
1759
|
+
inject: () => ({ ctx, toggle }),
|
|
1760
|
+
}, CronSettingsCard);
|
|
1761
|
+
if (!cardOk) {
|
|
1762
|
+
console.warn('[dsh-cron] settings.plugin.item slot unavailable — settings card not registered');
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1765
|
+
const chipOk = registerIntoMount(ctx, 'conversation.session.header.utilities', {
|
|
1766
|
+
name: 'conversation.session.header.utilities',
|
|
1767
|
+
id: PLUGIN_ID + '.chip',
|
|
1768
|
+
order: 27,
|
|
1769
|
+
locale: NS,
|
|
1770
|
+
inject: () => ({ ctx, toggle }),
|
|
1771
|
+
}, CronChip);
|
|
1772
|
+
const cardSlot = cardOk ? 'settings.plugin.item' : undefined;
|
|
1773
|
+
const chipSlot = chipOk ? 'conversation.session.header.utilities' : undefined;
|
|
1774
|
+
|
|
1775
|
+
function CronChip(props) {
|
|
1776
|
+
const chipT = props.t || translate;
|
|
1777
|
+
return React.createElement('button', {
|
|
1778
|
+
type: 'button',
|
|
1779
|
+
style: { background: 'none', border: 'none', color: 'var(--dsw-alias-label-secondary, #999)', cursor: 'pointer', padding: '4px', display: 'inline-flex', alignItems: 'center' },
|
|
1780
|
+
title: T_KEY(chipT, 'chip.title'),
|
|
1781
|
+
'aria-label': T_KEY(chipT, 'chip.title'),
|
|
1782
|
+
onClick: () => toggle.toggle(),
|
|
1783
|
+
dangerouslySetInnerHTML: { __html: ICON_TIMER }
|
|
1784
|
+
});
|
|
1785
|
+
}
|
|
1501
1786
|
|
|
1502
1787
|
ctx.effect(() => {
|
|
1503
1788
|
const off = [
|
|
1504
|
-
mountSidebarEntry(toggle, '
|
|
1789
|
+
mountSidebarEntry(toggle, translate('sidebar.label')),
|
|
1505
1790
|
mountCronScreen(ctx, toggle)
|
|
1506
1791
|
];
|
|
1507
1792
|
return () => { for (const dispose of off) dispose(); };
|