@goodandready/dsh-cron 0.2.1 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client.js +105 -59
- package/lib/http-utils.js +11 -5
- package/lib/index.js +25 -9
- package/lib/scheduler.js +0 -33
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -180,20 +180,57 @@ window.__ModuleLoader__.load({
|
|
|
180
180
|
} catch (e) {}
|
|
181
181
|
const chevronNode = () => (CoreChevron ? React.createElement(CoreChevron) : React.createElement('span', { style: { display: 'flex', alignItems: 'center' }, dangerouslySetInnerHTML: { __html: ICON_CHEVRON_DOWN } }));
|
|
182
182
|
|
|
183
|
-
//
|
|
184
|
-
//
|
|
183
|
+
// Own React error boundary (#106): the core's SlotErrorBoundary renders an
|
|
184
|
+
// empty div on crash, so a slot failure would silently vanish. Ours keeps
|
|
185
|
+
// the failure visible with a retry.
|
|
186
|
+
function createErrorBoundary() {
|
|
187
|
+
if (!React || typeof React.Component !== 'function') {
|
|
188
|
+
return function NoopBoundary(props) { return (props && props.children) || null; };
|
|
189
|
+
}
|
|
190
|
+
return class ErrorBoundary extends React.Component {
|
|
191
|
+
constructor(props) {
|
|
192
|
+
super(props);
|
|
193
|
+
this.state = { hasError: false, error: null };
|
|
194
|
+
}
|
|
195
|
+
static getDerivedStateFromError(error) {
|
|
196
|
+
return { hasError: true, error };
|
|
197
|
+
}
|
|
198
|
+
componentDidCatch(error, errorInfo) {
|
|
199
|
+
console.error('[dsh-cron] React error:', error, errorInfo);
|
|
200
|
+
}
|
|
201
|
+
render() {
|
|
202
|
+
if (this.state.hasError) {
|
|
203
|
+
return React.createElement('div', { className: 'dsh-cron-status-banner', 'data-type': 'error', style: { margin: '12px 0' } },
|
|
204
|
+
React.createElement('div', { style: { fontWeight: 600, marginBottom: '6px' } }, '⚠️ Cron UI error:'),
|
|
205
|
+
React.createElement('div', { style: { fontSize: '12px', wordBreak: 'break-all' } }, String((this.state.error && this.state.error.message) || this.state.error)),
|
|
206
|
+
React.createElement('button', {
|
|
207
|
+
type: 'button',
|
|
208
|
+
className: 'dsh-cron-btn-secondary',
|
|
209
|
+
style: { marginTop: '10px', fontSize: '12px', padding: '4px 10px' },
|
|
210
|
+
onClick: () => this.setState({ hasError: false, error: null })
|
|
211
|
+
}, 'Retry')
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
return (this.props && this.props.children) || null;
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
const ErrorBoundary = createErrorBoundary();
|
|
219
|
+
|
|
220
|
+
// Status colors: core semantic state tokens first (same set dsh-clinebot
|
|
221
|
+
// uses), plugin fallbacks as a single point of change (#105).
|
|
185
222
|
const STYLES = `
|
|
186
223
|
:root {
|
|
187
|
-
--dsh-cron-success: var(--dsw-alias-
|
|
188
|
-
--dsh-cron-success-bg:
|
|
189
|
-
--dsh-cron-danger: var(--dsw-alias-
|
|
190
|
-
--dsh-cron-danger-bg:
|
|
191
|
-
--dsh-cron-info: var(--dsw-alias-
|
|
192
|
-
--dsh-cron-info-bg:
|
|
193
|
-
--dsh-cron-accent: var(--dsw-alias-
|
|
194
|
-
--dsh-cron-accent-bg:
|
|
195
|
-
--dsh-cron-warning: var(--dsw-alias-
|
|
196
|
-
--dsh-cron-warning-bg:
|
|
224
|
+
--dsh-cron-success: var(--dsw-alias-state-success-primary, #10b981);
|
|
225
|
+
--dsh-cron-success-bg: rgba(16, 185, 129, 0.1);
|
|
226
|
+
--dsh-cron-danger: var(--dsw-alias-state-error-primary, #ef4444);
|
|
227
|
+
--dsh-cron-danger-bg: rgba(239, 68, 68, 0.1);
|
|
228
|
+
--dsh-cron-info: var(--dsw-alias-state-brand-primary, #60a5fa);
|
|
229
|
+
--dsh-cron-info-bg: rgba(96, 165, 250, 0.12);
|
|
230
|
+
--dsh-cron-accent: var(--dsw-alias-state-info-primary, #c084fc);
|
|
231
|
+
--dsh-cron-accent-bg: rgba(168, 85, 247, 0.12);
|
|
232
|
+
--dsh-cron-warning: var(--dsw-alias-state-warning-primary, #eab308);
|
|
233
|
+
--dsh-cron-warning-bg: rgba(234, 179, 8, 0.12);
|
|
197
234
|
}
|
|
198
235
|
[data-pane=conversation], [class*=centerCol] {
|
|
199
236
|
position: relative !important;
|
|
@@ -218,43 +255,46 @@ window.__ModuleLoader__.load({
|
|
|
218
255
|
.dsh-cron-entry-label { text-overflow: ellipsis; overflow: hidden; }
|
|
219
256
|
[data-dsh-frame][data-sidebar-collapsed] .dsh-cron-entry-label { display: none; }
|
|
220
257
|
|
|
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:
|
|
222
|
-
.dsh-cron-container { max-width:
|
|
258
|
+
.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: 24px 48px 32px; z-index: 100; box-sizing: border-box; }
|
|
259
|
+
.dsh-cron-container { max-width: 960px; width: 100%; margin: 0 auto; display: flex; flex-direction: column; gap: 20px; padding: 8px 0 32px; box-sizing: border-box; }
|
|
223
260
|
|
|
224
|
-
.dsh-cron-top-bar { display: flex; align-items: center; gap: 12px;
|
|
261
|
+
.dsh-cron-top-bar { display: flex; align-items: center; gap: 12px; }
|
|
225
262
|
.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; }
|
|
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)); }
|
|
263
|
+
.dsh-cron-back-btn:hover { color: var(--dsw-alias-label-primary, #ededed); background: var(--dsw-alias-bg-layer-4, var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,0.06))); }
|
|
227
264
|
|
|
228
|
-
.dsh-cron-header { display: flex; justify-content: space-between; align-items: flex-start;
|
|
229
|
-
.dsh-cron-title { font-size:
|
|
230
|
-
.dsh-cron-subtitle { font-size: 14px; color: var(--dsw-alias-label-secondary, #9ca3af); }
|
|
231
|
-
.dsh-cron-create-btn { appearance: none; display: inline-flex; align-items: center; gap: 6px; background: var(--dsw-alias-bg-layer-
|
|
265
|
+
.dsh-cron-header { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; padding-bottom: 16px; border-bottom: 1px solid var(--dsw-alias-border-l2, #383838); }
|
|
266
|
+
.dsh-cron-title { font-size: 22px; font-weight: 700; letter-spacing: -0.02em; margin-bottom: 6px; }
|
|
267
|
+
.dsh-cron-subtitle { font-size: 14px; color: var(--dsw-alias-label-secondary, #9ca3af); line-height: 1.5; }
|
|
268
|
+
.dsh-cron-create-btn { appearance: none; display: inline-flex; align-items: center; gap: 6px; background: var(--dsw-alias-bg-layer-2, #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
269
|
.dsh-cron-create-btn:hover { background: var(--dsw-alias-bg-layer-hover, var(--dsw-alias-interactive-bg-hover, #333)); }
|
|
233
270
|
.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; }
|
|
234
271
|
.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
272
|
.dsh-cron-dropdown-item:hover { background: var(--dsw-alias-interactive-bg-hover, #2a2a2a); }
|
|
236
|
-
.dsh-cron-search-bar { position: relative; margin-bottom:
|
|
237
|
-
.dsh-cron-search-input { width: 100%; background: var(--dsw-alias-bg-layer-
|
|
273
|
+
.dsh-cron-search-bar { position: relative; margin-bottom: 0; }
|
|
274
|
+
.dsh-cron-search-input { width: 100%; height: 36px; background: var(--dsw-alias-bg-layer-2, #1f1f1f); border: 1px solid var(--dsw-alias-border-l2, #333); border-radius: 8px; padding: 0 12px 0 38px; color: var(--dsw-alias-label-primary, inherit); font-size: 13px; outline: none; box-sizing: border-box; }
|
|
275
|
+
.dsh-cron-search-input:focus { outline: none; border-color: var(--dsh-cron-info); }
|
|
238
276
|
.dsh-cron-search-icon { position: absolute; left: 12px; top: 50%; transform: translateY(-50%); color: var(--dsw-alias-label-tertiary, #666); }
|
|
239
|
-
.dsh-cron-tabs { display: flex;
|
|
240
|
-
.dsh-cron-tab { appearance: none; background: transparent; border:
|
|
241
|
-
.dsh-cron-tab[data-active="true"] { background: var(--dsw-alias-bg-layer-4, #2a2a2a); color: var(--dsw-alias-label-primary, #fff);
|
|
242
|
-
.dsh-cron-task-list { display: flex; flex-direction: column; gap: 12px; margin-bottom:
|
|
277
|
+
.dsh-cron-tabs { display: flex; flex-wrap: wrap; gap: 8px; }
|
|
278
|
+
.dsh-cron-tab { appearance: none; background: transparent; border: 1px solid var(--dsw-alias-border-l2, #383838); padding: 4px 12px; border-radius: 999px; color: var(--dsw-alias-label-secondary, #9ca3af); font-size: 12px; font-weight: 500; cursor: pointer; }
|
|
279
|
+
.dsh-cron-tab[data-active="true"] { background: var(--dsw-alias-bg-layer-4, #2a2a2a); color: var(--dsw-alias-label-primary, #fff); border-color: var(--dsw-alias-label-dimmed, var(--dsw-alias-border-l2, #383838)); }
|
|
280
|
+
.dsh-cron-task-list { display: flex; flex-direction: column; gap: 12px; margin-bottom: 0; }
|
|
243
281
|
.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; }
|
|
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-
|
|
246
|
-
.dsh-cron-type-tag { display: inline-
|
|
247
|
-
.dsh-cron-task-model-tag { display: inline-
|
|
282
|
+
.dsh-cron-task-item:hover, .dsh-cron-task-item:focus-visible { background: var(--dsw-alias-bg-layer-4, #242424); border-color: var(--dsw-alias-label-dimmed, var(--dsw-alias-border-l1, #444)); outline: none; }
|
|
283
|
+
.dsh-cron-task-prompt-preview { font-size: 12.5px; color: var(--dsw-alias-label-secondary, #888); margin-top: 5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 520px; line-height: 1.3; }
|
|
284
|
+
.dsh-cron-type-tag { display: inline-flex; align-items: center; font-size: 11px; padding: 2px 8px; border-radius: 999px; border: 1px solid var(--dsw-alias-border-l2, #383838); text-transform: uppercase; font-weight: 500; margin-left: 8px; vertical-align: middle; }
|
|
285
|
+
.dsh-cron-task-model-tag { display: inline-flex; align-items: center; font-size: 11px; padding: 2px 8px; border-radius: 999px; border: 1px solid var(--dsw-alias-border-l2, #383838); background: var(--dsw-alias-bg-layer-2, rgba(255,255,255,0.06)); color: var(--dsw-alias-label-secondary, #aaa); margin-left: 8px; vertical-align: middle; }
|
|
248
286
|
.dsh-cron-task-actions { display: flex; gap: 8px; }
|
|
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-
|
|
250
|
-
.dsh-cron-icon-btn:hover { background: var(--dsw-alias-
|
|
251
|
-
.dsh-cron-
|
|
287
|
+
.dsh-cron-icon-btn { appearance: none; font: inherit; background: var(--dsw-alias-bg-layer-2, transparent); border: 1px solid var(--dsw-alias-border-l2, #333); border-radius: 8px; padding: 6px 12px; font-size: 12.5px; font-weight: 500; color: var(--dsw-alias-label-primary, #aaa); cursor: pointer; display: inline-flex; align-items: center; gap: 6px; transition: all 0.15s ease; }
|
|
288
|
+
.dsh-cron-icon-btn:hover { background: var(--dsw-alias-bg-layer-4, #2a2a2a); border-color: var(--dsw-alias-label-dimmed, var(--dsw-alias-border-l2, #444)); }
|
|
289
|
+
.dsh-cron-icon-btn.dsh-cron-danger { color: var(--dsh-cron-danger); border-color: var(--dsh-cron-danger-bg); }
|
|
290
|
+
.dsh-cron-icon-btn.dsh-cron-danger:hover { background: var(--dsh-cron-danger-bg); border-color: var(--dsh-cron-danger); }
|
|
291
|
+
.dsh-cron-task-status-btn { background: none; border: 1px solid var(--dsw-alias-border-l2, #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
292
|
.dsh-cron-task-status-btn[data-active="true"] { color: var(--dsh-cron-success); border-color: var(--dsh-cron-success); }
|
|
253
|
-
.dsh-cron-recs-title { font-size:
|
|
293
|
+
.dsh-cron-recs-title { font-size: 16px; font-weight: 600; margin-bottom: 14px; }
|
|
254
294
|
.dsh-cron-recs-list { display: flex; flex-direction: column; gap: 10px; }
|
|
255
|
-
.dsh-cron-rec-card { display: flex; align-items: center; gap: 16px; padding:
|
|
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-
|
|
295
|
+
.dsh-cron-rec-card { display: flex; align-items: center; gap: 16px; padding: 14px 18px; background: var(--dsw-alias-bg-layer-3, #1c1c1c); border: 1px solid var(--dsw-alias-border-l2, #282828); border-radius: 12px; cursor: pointer; transition: background 0.15s; }
|
|
296
|
+
.dsh-cron-rec-card:hover, .dsh-cron-rec-card:focus-visible { background: var(--dsw-alias-bg-layer-4, #242424); border-color: var(--dsw-alias-label-dimmed, var(--dsw-alias-border-l2, #383838)); outline: none; }
|
|
297
|
+
.dsh-cron-rec-icon { width: 36px; height: 36px; border-radius: 10px; background: var(--dsw-alias-bg-layer-2, rgba(255,255,255,0.03)); display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
|
|
258
298
|
.dsh-cron-rec-content { flex: 1; min-width: 0; }
|
|
259
299
|
.dsh-cron-rec-title { font-size: 14.5px; font-weight: 500; margin-bottom: 4px; }
|
|
260
300
|
.dsh-cron-rec-time { font-size: 13px; color: var(--dsw-alias-label-secondary, #9ca3af); font-weight: normal; margin-left: 8px; }
|
|
@@ -265,14 +305,17 @@ window.__ModuleLoader__.load({
|
|
|
265
305
|
.dsh-cron-modal-title { font-size: 18px; font-weight: 600; margin-bottom: 18px; }
|
|
266
306
|
.dsh-cron-form-group { margin-bottom: 16px; }
|
|
267
307
|
.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-
|
|
308
|
+
.dsh-cron-form-group input, .dsh-cron-form-group textarea, .dsh-cron-form-group select { width: 100%; background: var(--dsw-alias-bg-layer-2, #141414); border: 1px solid var(--dsw-alias-border-l2, #333); border-radius: 8px; padding: 8px 12px; color: var(--dsw-alias-label-primary, #fff); font-size: 13px; outline: none; box-sizing: border-box; }
|
|
309
|
+
.dsh-cron-form-group input:focus, .dsh-cron-form-group textarea:focus, .dsh-cron-form-group select:focus { outline: none; border-color: var(--dsh-cron-info); }
|
|
269
310
|
.dsh-cron-form-group select { cursor: pointer; }
|
|
270
311
|
.dsh-cron-form-group textarea { min-height: 80px; resize: vertical; }
|
|
271
312
|
.dsh-cron-form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
|
|
272
313
|
.dsh-cron-modal-foot { display: flex; justify-content: flex-end; align-items: center; gap: 10px; margin-top: 20px; }
|
|
273
|
-
.dsh-cron-btn-primary { background: var(--dsw-alias-label-primary, #ededed); color: var(--dsw-alias-bg-
|
|
314
|
+
.dsh-cron-btn-primary { background: var(--dsw-alias-label-primary, #ededed); color: var(--dsw-alias-bg-layer-3, #111); border: 1px solid transparent; border-radius: 8px; padding: 7px 14px; font-size: 13px; font-weight: 500; cursor: pointer; display: inline-flex; align-items: center; gap: 6px; }
|
|
315
|
+
.dsh-cron-btn-primary:hover:not(:disabled) { opacity: 0.88; }
|
|
274
316
|
.dsh-cron-btn-primary:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
275
|
-
.dsh-cron-btn-secondary { background: transparent; color: var(--dsw-alias-label-
|
|
317
|
+
.dsh-cron-btn-secondary { appearance: none; font: inherit; background: var(--dsw-alias-bg-layer-2, transparent); color: var(--dsw-alias-label-primary, #999); border: 1px solid var(--dsw-alias-border-l2, #383838); border-radius: 8px; padding: 7px 14px; font-size: 13px; font-weight: 500; cursor: pointer; display: inline-flex; align-items: center; gap: 6px; transition: all 0.15s ease; }
|
|
318
|
+
.dsh-cron-btn-secondary:hover:not(:disabled) { background: var(--dsw-alias-bg-layer-4, #2a2a2a); border-color: var(--dsw-alias-label-dimmed, var(--dsw-alias-border-l2, #444)); }
|
|
276
319
|
.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
320
|
.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
321
|
.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); }
|
|
@@ -281,15 +324,15 @@ window.__ModuleLoader__.load({
|
|
|
281
324
|
.dsh-cron-send-btn:hover:not(:disabled) { transform: translateY(-1px); }
|
|
282
325
|
.dsh-cron-send-btn:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
283
326
|
|
|
284
|
-
.dsh-cron-card { background: var(--dsw-alias-bg-layer-
|
|
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:
|
|
327
|
+
.dsh-cron-card { background: var(--dsw-alias-bg-layer-3, #1f1f1f); border: 1px solid var(--dsw-alias-border-l2, #333); border-radius: 12px; padding: 18px 20px; margin-bottom: 0; display: flex; flex-direction: column; gap: 14px; }
|
|
328
|
+
.dsh-cron-card-head-btn { appearance: none; width: 100%; font: inherit; color: inherit; text-align: left; cursor: pointer; background: 0 0; border: 0; border-radius: 12px; display: flex; align-items: center; gap: 12px; padding: 0; }
|
|
286
329
|
.dsh-cron-card-head-btn .dsh-cron-card-title { flex: 1; }
|
|
287
|
-
.dsh-cron-card-title { font-size:
|
|
330
|
+
.dsh-cron-card-title { font-size: 16px; font-weight: 600; color: var(--dsw-alias-label-primary, #fff); }
|
|
288
331
|
.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
332
|
.dsh-cron-chev-open { transform: rotate(180deg); }
|
|
290
|
-
.dsh-cron-card-body { border-top: 1px solid var(--dsw-alias-border-
|
|
333
|
+
.dsh-cron-card-body { border-top: 1px solid var(--dsw-alias-border-l2, #333); margin-top: 2px; padding-top: 14px; }
|
|
291
334
|
.dsh-cron-card-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; }
|
|
292
|
-
.dsh-cron-card-desc { font-size: 13px; color: var(--dsw-alias-label-secondary, #888);
|
|
335
|
+
.dsh-cron-card-desc { font-size: 13px; color: var(--dsw-alias-label-secondary, #888); line-height: 1.4; }
|
|
293
336
|
|
|
294
337
|
.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
338
|
.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; }
|
|
@@ -298,21 +341,21 @@ window.__ModuleLoader__.load({
|
|
|
298
341
|
.dsh-cron-history-list { display: flex; flex-direction: column; gap: 10px; max-height: 420px; overflow-y: auto; padding-right: 4px; }
|
|
299
342
|
.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; }
|
|
300
343
|
.dsh-cron-history-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px; }
|
|
301
|
-
.dsh-cron-history-status { font-weight:
|
|
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); }
|
|
344
|
+
.dsh-cron-history-status { font-weight: 500; font-size: 11px; text-transform: uppercase; padding: 2px 8px; border-radius: 999px; border: 1px solid var(--dsw-alias-border-l2, #383838); }
|
|
345
|
+
.dsh-cron-history-status[data-status="success"] { background: var(--dsh-cron-success-bg); color: var(--dsh-cron-success); border-color: var(--dsh-cron-success); }
|
|
346
|
+
.dsh-cron-history-status[data-status="error"], .dsh-cron-history-status[data-status="timeout"] { background: var(--dsh-cron-danger-bg); color: var(--dsh-cron-danger); border-color: var(--dsh-cron-danger); }
|
|
347
|
+
.dsh-cron-history-status[data-status="skipped"], .dsh-cron-history-status[data-status="missed"] { background: var(--dsh-cron-warning-bg); color: var(--dsh-cron-warning); border-color: var(--dsh-cron-warning); }
|
|
305
348
|
.dsh-cron-history-time { color: var(--dsw-alias-label-tertiary, #777); font-size: 12px; }
|
|
306
349
|
.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
350
|
.dsh-cron-history-empty { text-align: center; padding: 36px 0; color: var(--dsw-alias-label-tertiary, #666); font-size: 13.5px; }
|
|
308
351
|
|
|
309
|
-
.dsh-cron-stats-bar { display: grid; grid-template-columns: repeat(
|
|
310
|
-
.dsh-cron-stat-card { background: var(--dsw-alias-bg-layer-
|
|
311
|
-
.dsh-cron-stat-val { font-size:
|
|
312
|
-
.dsh-cron-stat-lbl { font-size:
|
|
313
|
-
.dsh-cron-oneshot-tag { font-size: 11px; padding: 2px
|
|
314
|
-
.dsh-cron-cost-tag { font-size: 11px; padding: 2px
|
|
315
|
-
.dsh-cron-tokens-tag { font-size: 11px; padding: 2px
|
|
352
|
+
.dsh-cron-stats-bar { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; margin-bottom: 0; }
|
|
353
|
+
.dsh-cron-stat-card { background: var(--dsw-alias-bg-layer-2, rgba(255, 255, 255, 0.02)); border: 1px solid var(--dsw-alias-border-l2, #222); border-radius: 8px; padding: 12px 14px; display: flex; flex-direction: column; gap: 4px; }
|
|
354
|
+
.dsh-cron-stat-val { font-size: 18px; font-weight: 700; color: var(--dsw-alias-label-primary, #fff); }
|
|
355
|
+
.dsh-cron-stat-lbl { font-size: 12px; color: var(--dsw-alias-label-secondary, #777); margin-top: 0; }
|
|
356
|
+
.dsh-cron-oneshot-tag { display: inline-flex; align-items: center; font-size: 11px; padding: 2px 8px; border-radius: 999px; border: 1px solid var(--dsh-cron-accent); background: var(--dsh-cron-accent-bg); color: var(--dsh-cron-accent); font-weight: 500; }
|
|
357
|
+
.dsh-cron-cost-tag { display: inline-flex; align-items: center; font-size: 11px; padding: 2px 8px; border-radius: 999px; border: 1px solid var(--dsh-cron-success); background: var(--dsh-cron-success-bg); color: var(--dsh-cron-success); font-weight: 500; }
|
|
358
|
+
.dsh-cron-tokens-tag { display: inline-flex; align-items: center; font-size: 11px; padding: 2px 8px; border-radius: 999px; border: 1px solid var(--dsh-cron-info); background: var(--dsh-cron-info-bg); color: var(--dsh-cron-info); }
|
|
316
359
|
.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
360
|
.dsh-cron-checkbox-row input { width: 15px; height: 15px; margin: 0; cursor: pointer; accent-color: var(--dsw-alias-label-primary, #ededed); }
|
|
318
361
|
.dsh-cron-hint { font-size: 12px; color: var(--dsw-alias-label-tertiary, #888); margin-top: 4px; }
|
|
@@ -945,7 +988,7 @@ window.__ModuleLoader__.load({
|
|
|
945
988
|
}, React.createElement('span', svgProps(ICON_EDIT))),
|
|
946
989
|
React.createElement('button', {
|
|
947
990
|
type: 'button',
|
|
948
|
-
className: 'dsh-cron-icon-btn',
|
|
991
|
+
className: 'dsh-cron-icon-btn dsh-cron-danger',
|
|
949
992
|
title: T_KEY(t, 'actions.delete'),
|
|
950
993
|
'aria-label': T_KEY(t, 'actions.delete'),
|
|
951
994
|
onClick: () => handleDelete(task.id)
|
|
@@ -1666,7 +1709,7 @@ window.__ModuleLoader__.load({
|
|
|
1666
1709
|
column.appendChild(container);
|
|
1667
1710
|
if (ReactDOM && typeof ReactDOM.createRoot === 'function') {
|
|
1668
1711
|
root = ReactDOM.createRoot(container);
|
|
1669
|
-
root.render(React.createElement(CronScreen, { ctx, toggle, onClose: () => toggle.set(false), t: translate }));
|
|
1712
|
+
root.render(React.createElement(ErrorBoundary, null, React.createElement(CronScreen, { ctx, toggle, onClose: () => toggle.set(false), t: translate })));
|
|
1670
1713
|
} else if (ReactDOM && typeof ReactDOM.render === 'function') {
|
|
1671
1714
|
ReactDOM.render(React.createElement(CronScreen, { ctx, toggle, onClose: () => toggle.set(false), t: translate }), container);
|
|
1672
1715
|
}
|
|
@@ -1752,12 +1795,15 @@ window.__ModuleLoader__.load({
|
|
|
1752
1795
|
// The settings card lives ONLY in the plugins settings tab (#102): no
|
|
1753
1796
|
// top-level section fallback — the flat sidebar list is a shared core
|
|
1754
1797
|
// resource, and the card slot is proven on current builds (#85).
|
|
1798
|
+
function SettingsCardWithBoundary(props) {
|
|
1799
|
+
return React.createElement(ErrorBoundary, null, CronSettingsCard(props));
|
|
1800
|
+
}
|
|
1755
1801
|
const cardOk = registerIntoMount(ctx, 'settings.plugin.item', {
|
|
1756
1802
|
name: 'settings.plugin.item',
|
|
1757
1803
|
key: NS,
|
|
1758
1804
|
locale: NS,
|
|
1759
1805
|
inject: () => ({ ctx, toggle }),
|
|
1760
|
-
},
|
|
1806
|
+
}, SettingsCardWithBoundary);
|
|
1761
1807
|
if (!cardOk) {
|
|
1762
1808
|
console.warn('[dsh-cron] settings.plugin.item slot unavailable — settings card not registered');
|
|
1763
1809
|
}
|
package/lib/http-utils.js
CHANGED
|
@@ -8,12 +8,15 @@ export const MAX_BODY_BYTES = 1024 * 1024;
|
|
|
8
8
|
export const SCRIPT_CONFIRM_HEADER = 'x-dsh-cron-confirm';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
* Parse a JSON request body with a hard size cap.
|
|
11
|
+
* Parse a JSON request body with a hard byte size cap.
|
|
12
|
+
* Chunks are accumulated as Buffers so multi-byte UTF-8 characters split
|
|
13
|
+
* across chunk boundaries decode correctly (#106, #97 follow-up).
|
|
12
14
|
* Rejects with err.statusCode = 413 when the body exceeds the limit.
|
|
13
15
|
*/
|
|
14
16
|
export function parseJsonBody(req, limit = MAX_BODY_BYTES) {
|
|
15
17
|
return new Promise((resolve, reject) => {
|
|
16
|
-
|
|
18
|
+
const chunks = [];
|
|
19
|
+
let bytes = 0;
|
|
17
20
|
let settled = false;
|
|
18
21
|
const fail = (err) => {
|
|
19
22
|
if (settled) return;
|
|
@@ -22,17 +25,20 @@ export function parseJsonBody(req, limit = MAX_BODY_BYTES) {
|
|
|
22
25
|
};
|
|
23
26
|
req.on('data', (chunk) => {
|
|
24
27
|
if (settled) return;
|
|
25
|
-
|
|
26
|
-
|
|
28
|
+
const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
29
|
+
bytes += buf.length;
|
|
30
|
+
if (bytes > limit) {
|
|
27
31
|
fail(Object.assign(new Error('Request body exceeds the allowed size'), { statusCode: 413 }));
|
|
28
32
|
req.destroy();
|
|
33
|
+
return;
|
|
29
34
|
}
|
|
35
|
+
chunks.push(buf);
|
|
30
36
|
});
|
|
31
37
|
req.on('end', () => {
|
|
32
38
|
if (settled) return;
|
|
33
39
|
settled = true;
|
|
34
40
|
try {
|
|
35
|
-
resolve(
|
|
41
|
+
resolve(chunks.length ? JSON.parse(Buffer.concat(chunks).toString('utf8')) : {});
|
|
36
42
|
} catch (err) {
|
|
37
43
|
reject(err);
|
|
38
44
|
}
|
package/lib/index.js
CHANGED
|
@@ -71,7 +71,7 @@ async function readBody(req, res) {
|
|
|
71
71
|
* snapshot stays authoritative (#102). Returns null when the settings
|
|
72
72
|
* service is unavailable (headless profiles fall back to store-only writes).
|
|
73
73
|
*/
|
|
74
|
-
async function applySettingsToScope(settingsScope, body) {
|
|
74
|
+
export async function applySettingsToScope(settingsScope, body) {
|
|
75
75
|
if (!settingsScope || typeof settingsScope.set !== 'function') return null;
|
|
76
76
|
const errors = [];
|
|
77
77
|
for (const key of ['botToken', 'chatId', 'notifyTelegram', 'onlyOnFailure', 'kanbanBaseUrl']) {
|
|
@@ -85,6 +85,18 @@ async function applySettingsToScope(settingsScope, body) {
|
|
|
85
85
|
return errors;
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
/**
|
|
89
|
+
* Drop values that must never re-enter the store from the client: a masked
|
|
90
|
+
* secret echoed back by the settings UI would overwrite the real token.
|
|
91
|
+
*/
|
|
92
|
+
export function sanitizeSettingsPayload(body) {
|
|
93
|
+
const payload = Object.assign({}, body);
|
|
94
|
+
if (typeof payload.botToken === 'string' && (payload.botToken.includes('••') || payload.botToken.includes('****'))) {
|
|
95
|
+
delete payload.botToken;
|
|
96
|
+
}
|
|
97
|
+
return payload;
|
|
98
|
+
}
|
|
99
|
+
|
|
88
100
|
/**
|
|
89
101
|
* Shared implementation for the /dsh-cron/tasks... and /dsh-cron/action...
|
|
90
102
|
* route families — the #66 alias is kept alive by the same code path instead
|
|
@@ -92,9 +104,9 @@ async function applySettingsToScope(settingsScope, body) {
|
|
|
92
104
|
*
|
|
93
105
|
* IMPORTANT: prefix routes must be registered WITHOUT a trailing slash —
|
|
94
106
|
* with one, the core degrades matching to exact-path equality and every
|
|
95
|
-
* deeper path unrouted (#96).
|
|
107
|
+
* deeper path unrouted (#96). Exported for unit tests.
|
|
96
108
|
*/
|
|
97
|
-
function createCronApiHandler(store, scheduler, collection) {
|
|
109
|
+
export function createCronApiHandler(store, scheduler, collection) {
|
|
98
110
|
return async function handleCronApi(req, res) {
|
|
99
111
|
try {
|
|
100
112
|
const url = new URL(req.url, 'http://127.0.0.1');
|
|
@@ -174,6 +186,10 @@ function createCronApiHandler(store, scheduler, collection) {
|
|
|
174
186
|
}
|
|
175
187
|
|
|
176
188
|
if (req.method === 'POST' && action === 'run') {
|
|
189
|
+
if (!store.get(id)) {
|
|
190
|
+
sendJson(res, 404, { ok: false, error: 'Task not found' });
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
177
193
|
await scheduler.triggerManualRun(id);
|
|
178
194
|
sendJson(res, 200, { ok: true, task: store.get(id) });
|
|
179
195
|
return;
|
|
@@ -259,9 +275,9 @@ function createCronApiHandler(store, scheduler, collection) {
|
|
|
259
275
|
|
|
260
276
|
/**
|
|
261
277
|
* Single implementation behind the cron_create_task / cron_schedule_task
|
|
262
|
-
* alias pair (#70, #92).
|
|
278
|
+
* alias pair (#70, #92). Exported for unit tests.
|
|
263
279
|
*/
|
|
264
|
-
function executeCreateTask(store, scheduler, args) {
|
|
280
|
+
export function executeCreateTask(store, scheduler, args) {
|
|
265
281
|
const taskType = args.type === 'script' ? 'script' : 'llm';
|
|
266
282
|
const parsed = parseScheduleExpression(args.schedule);
|
|
267
283
|
const task = store.set({
|
|
@@ -339,6 +355,9 @@ export function apply(ctx, config) {
|
|
|
339
355
|
}
|
|
340
356
|
});
|
|
341
357
|
}
|
|
358
|
+
sctx.effect(() => () => {
|
|
359
|
+
cronSettingsScope = null;
|
|
360
|
+
});
|
|
342
361
|
} catch (e) {
|
|
343
362
|
console.warn('[dsh-cron] settings registration warning:', e.message);
|
|
344
363
|
}
|
|
@@ -432,10 +451,7 @@ export function apply(ctx, config) {
|
|
|
432
451
|
if (error) return;
|
|
433
452
|
// Masked secrets echoed back by the client must never overwrite the
|
|
434
453
|
// stored token (store.saveSettings guards this too, #86-era).
|
|
435
|
-
const payload =
|
|
436
|
-
if (typeof payload.botToken === 'string' && (payload.botToken.includes('••') || payload.botToken.includes('****'))) {
|
|
437
|
-
delete payload.botToken;
|
|
438
|
-
}
|
|
454
|
+
const payload = sanitizeSettingsPayload(body);
|
|
439
455
|
const scopeErrors = await applySettingsToScope(cronSettingsScope, payload);
|
|
440
456
|
if (scopeErrors === null) {
|
|
441
457
|
store.saveSettings(payload);
|
package/lib/scheduler.js
CHANGED
|
@@ -434,40 +434,7 @@ export class TaskScheduler {
|
|
|
434
434
|
}
|
|
435
435
|
}
|
|
436
436
|
|
|
437
|
-
removeTask(taskId) {
|
|
438
|
-
if (this.jobs.has(taskId)) {
|
|
439
|
-
this.jobs.get(taskId).stop();
|
|
440
|
-
this.jobs.delete(taskId);
|
|
441
|
-
}
|
|
442
|
-
if (this.timers.has(taskId)) {
|
|
443
|
-
clearTimeout(this.timers.get(taskId));
|
|
444
|
-
this.timers.delete(taskId);
|
|
445
|
-
}
|
|
446
|
-
const running = this.running.get(taskId);
|
|
447
|
-
if (running) {
|
|
448
|
-
try {
|
|
449
|
-
running.controller.abort(new Error('Task deleted'));
|
|
450
|
-
} catch {}
|
|
451
|
-
this.running.delete(taskId);
|
|
452
|
-
}
|
|
453
|
-
return this.store.delete(taskId);
|
|
454
|
-
}
|
|
455
|
-
|
|
456
437
|
triggerManualRun(taskId) {
|
|
457
438
|
return this.runTask(taskId);
|
|
458
439
|
}
|
|
459
|
-
|
|
460
|
-
getNextRun(schedule) {
|
|
461
|
-
try {
|
|
462
|
-
const parsed = parseScheduleExpression(schedule);
|
|
463
|
-
if (parsed.isOneShot) {
|
|
464
|
-
return parsed.targetTimestamp;
|
|
465
|
-
}
|
|
466
|
-
const tempJob = new Cron(parsed.cronPattern);
|
|
467
|
-
const next = tempJob.nextRun();
|
|
468
|
-
return next ? next.getTime() : null;
|
|
469
|
-
} catch {
|
|
470
|
-
return null;
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
440
|
}
|