alchemy-chimera 1.3.0-alpha.4 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/CLAUDE.md +297 -0
- package/assets/stylesheets/chimera/chimera.scss +277 -0
- package/config/routes.js +38 -0
- package/controller/00-chimera_controller.js +36 -6
- package/controller/chimera_editor_controller.js +52 -7
- package/controller/chimera_settings_controller.js +1 -0
- package/controller/chimera_static_controller.js +163 -2
- package/controller/system_task_controller.js +224 -0
- package/element/chimera_dashboard_button.js +100 -0
- package/element/chimera_run_task_button.js +93 -0
- package/element/chimera_task_monitor.js +672 -0
- package/lib/chimera_config.js +94 -0
- package/lib/toolbar_buttons.js +78 -0
- package/model/00_chimera_model.js +11 -0
- package/model/chimera_dashboard_config_model.js +44 -0
- package/package.json +4 -4
- package/view/chimera/dashboard.hwk +4 -3
- package/view/chimera/editor/task_monitor.hwk +32 -0
- package/view/chimera/toolbar/customize_dashboard_button.hwk +26 -0
- package/view/chimera/toolbar/monitor_button.hwk +8 -0
- package/view/chimera/toolbar/reset_dashboard_button.hwk +26 -0
- package/view/chimera/toolbar/run_task_button.hwk +25 -0
- package/view/elements/chimera_task_monitor.hwk +80 -0
- package/view/layouts/chimera_body.hwk +46 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
## 1.3.0 (2026-01-21)
|
|
2
|
+
|
|
3
|
+
* Add `addRowAction()` to ChimeraConfig for model-defined row actions
|
|
4
|
+
* Fix WebSocket compatibility in ChimeraController (skip theme/beforeAction for WS connections)
|
|
5
|
+
* Add live task monitoring UI with WebSocket support
|
|
6
|
+
* Add "Monitor" button to TaskHistory index and edit pages
|
|
7
|
+
* Add widgets to dashboard
|
|
8
|
+
* Move `setToolbarManagerVariable()` call to end of actions (after document context setup) to ensure proper serialization of document watcher state
|
|
9
|
+
* Fix `setToolbarManagerVariable()` to clear document watcher synchronously for non-document actions, preventing avatar from persisting on list pages
|
|
10
|
+
|
|
1
11
|
## 1.3.0-alpha.4 (2025-07-10)
|
|
2
12
|
|
|
3
13
|
* Always set a new toolbar_manager received from the server
|
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
# Alchemy Chimera Development Guide
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Chimera is the admin/CMS interface plugin for AlchemyMVC. It provides a complete backend administration interface for managing application data models with CRUD operations, live task monitoring, and system settings management.
|
|
6
|
+
|
|
7
|
+
## Commands
|
|
8
|
+
- Run tests: `npm test`
|
|
9
|
+
|
|
10
|
+
## Dependencies
|
|
11
|
+
- alchemymvc (>=1.4.0-alpha)
|
|
12
|
+
- alchemy-acl (~0.9.0-alpha) - Access control
|
|
13
|
+
- alchemy-form (~0.3.0-alpha) - Form handling
|
|
14
|
+
- alchemy-widget (~0.3.0-alpha) - Widget system
|
|
15
|
+
- alchemy-styleboost (>=0.5.0-alpha) - SCSS framework
|
|
16
|
+
|
|
17
|
+
## Directory Structure
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
controller/
|
|
21
|
+
├── 00-chimera_controller.js # Base controller
|
|
22
|
+
├── chimera_editor_controller.js # CRUD interface
|
|
23
|
+
├── chimera_settings_controller.js # Settings management
|
|
24
|
+
├── chimera_static_controller.js # Dashboard/sidebar
|
|
25
|
+
└── system_task_controller.js # Task execution/monitoring
|
|
26
|
+
|
|
27
|
+
lib/
|
|
28
|
+
└── chimera_config.js # Model configuration class
|
|
29
|
+
|
|
30
|
+
model/
|
|
31
|
+
└── model.js # Adds chimera property to models
|
|
32
|
+
|
|
33
|
+
element/
|
|
34
|
+
├── chimera_run_task_button.js # Task execution button
|
|
35
|
+
└── chimera_task_monitor.js # Live task progress UI
|
|
36
|
+
|
|
37
|
+
view/
|
|
38
|
+
├── chimera/
|
|
39
|
+
│ ├── dashboard.hwk
|
|
40
|
+
│ ├── sidebar.hwk
|
|
41
|
+
│ ├── editor/ # CRUD views
|
|
42
|
+
│ └── settings/
|
|
43
|
+
└── layouts/
|
|
44
|
+
└── chimera_*.hwk
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Routes
|
|
48
|
+
|
|
49
|
+
All routes require `'chimera'` permission and are prefixed with `/chimera`.
|
|
50
|
+
|
|
51
|
+
| Route | Method | Handler | Description |
|
|
52
|
+
|-------|--------|---------|-------------|
|
|
53
|
+
| `/` | GET | `Chimera.Static#dashboard` | Dashboard |
|
|
54
|
+
| `/settings` | GET/POST | `Chimera.Settings#editor` | System settings |
|
|
55
|
+
| `/editor/{model}/index` | GET | `Chimera.Editor#index` | List records |
|
|
56
|
+
| `/editor/{model}/add` | GET/POST | `Chimera.Editor#add` | Create record |
|
|
57
|
+
| `/editor/{model}/edit/{pk}` | GET/POST | `Chimera.Editor#edit` | Edit record |
|
|
58
|
+
| `/editor/{model}/trash/{pk}` | GET/POST | `Chimera.Editor#trash` | Delete record |
|
|
59
|
+
| `/editor/{model}/preview/{pk}` | GET | `Chimera.Editor#preview` | Preview record |
|
|
60
|
+
| `/task-monitor/{task_history_id}` | GET | `Chimera.Editor#taskMonitor` | Task monitor page |
|
|
61
|
+
|
|
62
|
+
### API Routes
|
|
63
|
+
| Route | Method | Handler |
|
|
64
|
+
|-------|--------|---------|
|
|
65
|
+
| `/api/editor/{model}/records` | POST | Fetch paginated records |
|
|
66
|
+
| `/api/content/sidebar` | GET | Dynamic sidebar content |
|
|
67
|
+
| `/api/system/task/{id}/run` | POST | Execute system task |
|
|
68
|
+
|
|
69
|
+
### WebSocket Linkups
|
|
70
|
+
- `chimera@taskmonitor` - Live task monitoring
|
|
71
|
+
|
|
72
|
+
## Model Configuration
|
|
73
|
+
|
|
74
|
+
Every model has a static `chimera` property (ChimeraConfig instance):
|
|
75
|
+
|
|
76
|
+
### Field Sets
|
|
77
|
+
|
|
78
|
+
Define which fields appear in different contexts:
|
|
79
|
+
|
|
80
|
+
```javascript
|
|
81
|
+
// In model constitute() or after model definition
|
|
82
|
+
const MyModel = Model.getClass('MyModel');
|
|
83
|
+
|
|
84
|
+
// Edit form fields
|
|
85
|
+
MyModel.chimera.getFieldSet('edit')
|
|
86
|
+
.add('title', {group: 'main'})
|
|
87
|
+
.add('body', {group: 'content'})
|
|
88
|
+
.add('status', {group: 'settings'});
|
|
89
|
+
|
|
90
|
+
// List/table fields
|
|
91
|
+
MyModel.chimera.getFieldSet('list')
|
|
92
|
+
.add('title')
|
|
93
|
+
.add('status')
|
|
94
|
+
.add('created');
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Field Options
|
|
98
|
+
```javascript
|
|
99
|
+
{
|
|
100
|
+
title: 'Field Title', // Display name
|
|
101
|
+
purpose: 'default', // Field purpose for form
|
|
102
|
+
mode: 'default', // Edit mode
|
|
103
|
+
readonly: false, // Read-only field
|
|
104
|
+
view: 'default', // View template
|
|
105
|
+
wrapper: 'default', // Wrapper template
|
|
106
|
+
group: 'main', // Tab group (multiple = tabs)
|
|
107
|
+
widget_settings: {} // Widget-specific config
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Row Actions
|
|
112
|
+
|
|
113
|
+
Add custom actions to record rows:
|
|
114
|
+
|
|
115
|
+
```javascript
|
|
116
|
+
MyModel.chimera.addRowAction({
|
|
117
|
+
name: 'publish',
|
|
118
|
+
icon: 'share',
|
|
119
|
+
title: 'Publish',
|
|
120
|
+
placement: ['row', 'context'], // Where to show
|
|
121
|
+
route: 'PublishController#publish',
|
|
122
|
+
route_params: {
|
|
123
|
+
pk: '$pk', // Auto-substituted with record primary key
|
|
124
|
+
type: 'article'
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Built-in actions: `edit`, `trash`
|
|
130
|
+
|
|
131
|
+
### Record Preview
|
|
132
|
+
|
|
133
|
+
```javascript
|
|
134
|
+
MyModel.chimera.setRecordPreview('MyController#preview');
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Widget Generation
|
|
138
|
+
|
|
139
|
+
Chimera auto-generates UI based on field sets:
|
|
140
|
+
|
|
141
|
+
### Edit Action
|
|
142
|
+
- Single group → Flat form
|
|
143
|
+
- Multiple groups → Tab navigation
|
|
144
|
+
- Includes save button with states (default, busy, done, error)
|
|
145
|
+
|
|
146
|
+
### List Action
|
|
147
|
+
- `<alchemy-table>` with pagination
|
|
148
|
+
- Filter row for searching
|
|
149
|
+
- Sortable columns
|
|
150
|
+
- Configurable page size
|
|
151
|
+
|
|
152
|
+
## Task Monitoring
|
|
153
|
+
|
|
154
|
+
### Run Task Button
|
|
155
|
+
```html
|
|
156
|
+
<chimera-run-task-button task-id="{{task.$pk}}">
|
|
157
|
+
Run Task
|
|
158
|
+
</chimera-run-task-button>
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Task Monitor Element
|
|
162
|
+
```html
|
|
163
|
+
<chimera-task-monitor task-history-id="{{history.$pk}}">
|
|
164
|
+
</chimera-task-monitor>
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Features:
|
|
168
|
+
- Real-time progress via WebSocket
|
|
169
|
+
- Log output with timestamps
|
|
170
|
+
- Controls: Stop, Pause, Resume
|
|
171
|
+
- Status display with elapsed time
|
|
172
|
+
- Error handling with fallback to database state
|
|
173
|
+
|
|
174
|
+
### Linkup Events
|
|
175
|
+
| From Server | Description |
|
|
176
|
+
|-------------|-------------|
|
|
177
|
+
| `state` | Initial state |
|
|
178
|
+
| `progress` | Percentage updates |
|
|
179
|
+
| `report` | Log entries |
|
|
180
|
+
| `complete` | Task finished |
|
|
181
|
+
| `paused`, `resumed`, `stopped` | Control confirmations |
|
|
182
|
+
| `error` | Error occurred |
|
|
183
|
+
|
|
184
|
+
| To Server | Description |
|
|
185
|
+
|-----------|-------------|
|
|
186
|
+
| `stop` | Stop running task |
|
|
187
|
+
| `pause` | Pause task |
|
|
188
|
+
| `resume` | Resume paused task |
|
|
189
|
+
|
|
190
|
+
## Records API
|
|
191
|
+
|
|
192
|
+
**POST `/chimera/api/editor/{model}/records`**
|
|
193
|
+
|
|
194
|
+
Request:
|
|
195
|
+
```javascript
|
|
196
|
+
{
|
|
197
|
+
page_size: 20,
|
|
198
|
+
page: 1,
|
|
199
|
+
fields: ['title', 'status'],
|
|
200
|
+
sort: {field: 'created', dir: 'desc'},
|
|
201
|
+
filters: {status: 'published'}
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Response:
|
|
206
|
+
```javascript
|
|
207
|
+
{
|
|
208
|
+
records: [{
|
|
209
|
+
// Record data
|
|
210
|
+
$hold: {
|
|
211
|
+
actions: [{name: 'edit', icon, url, placement}]
|
|
212
|
+
}
|
|
213
|
+
}]
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## Sidebar Configuration
|
|
218
|
+
|
|
219
|
+
Auto-generated from models with Chimera configuration, or customize:
|
|
220
|
+
|
|
221
|
+
```javascript
|
|
222
|
+
alchemy.plugins.chimera.sidebar_menu = [
|
|
223
|
+
{model: 'Article'}, // Auto-generate link
|
|
224
|
+
{model: 'User'},
|
|
225
|
+
{href: '/custom', title: 'Custom Page'}
|
|
226
|
+
];
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## Plugin Configuration
|
|
230
|
+
|
|
231
|
+
```javascript
|
|
232
|
+
alchemy.plugins.chimera = {
|
|
233
|
+
theme: 'custom-theme', // Custom theme name
|
|
234
|
+
title: 'My Admin', // Window title prefix
|
|
235
|
+
sidebar_menu: [...] // Custom sidebar (optional)
|
|
236
|
+
};
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
## Controllers
|
|
240
|
+
|
|
241
|
+
### ChimeraController (Base)
|
|
242
|
+
- Sets theme on HTTP requests
|
|
243
|
+
- Manages toolbar for document editing
|
|
244
|
+
- Provides `setTitle(title)` method
|
|
245
|
+
|
|
246
|
+
### ChimeraEditor
|
|
247
|
+
- `index` - Record list with table
|
|
248
|
+
- `add` - Create form
|
|
249
|
+
- `edit` - Edit form with document watching
|
|
250
|
+
- `trash` - Delete confirmation
|
|
251
|
+
- `preview` - Delegates to model's preview action
|
|
252
|
+
- `records` - API for fetching records
|
|
253
|
+
|
|
254
|
+
### ChimeraSettings
|
|
255
|
+
- `editor` - System settings form
|
|
256
|
+
|
|
257
|
+
### SystemTask
|
|
258
|
+
- `run` - Execute task manually
|
|
259
|
+
- `monitor` - WebSocket handler for live updates
|
|
260
|
+
|
|
261
|
+
## View Templates
|
|
262
|
+
|
|
263
|
+
Templates use Hawkejs (`.hwk` files):
|
|
264
|
+
|
|
265
|
+
- `chimera/dashboard.hwk` - Main dashboard
|
|
266
|
+
- `chimera/sidebar.hwk` - Navigation
|
|
267
|
+
- `chimera/editor/index.hwk` - Record list
|
|
268
|
+
- `chimera/editor/add.hwk` - Create form
|
|
269
|
+
- `chimera/editor/edit.hwk` - Edit form
|
|
270
|
+
- `chimera/editor/trash.hwk` - Delete confirmation
|
|
271
|
+
- `chimera/editor/task_monitor.hwk` - Task progress
|
|
272
|
+
- `chimera/settings/editor.hwk` - Settings form
|
|
273
|
+
|
|
274
|
+
## Utility
|
|
275
|
+
|
|
276
|
+
```javascript
|
|
277
|
+
// Convert model name to URL segment
|
|
278
|
+
Plugin.modelNameToUrl('MyModel'); // 'my_model'
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## Gotchas
|
|
282
|
+
|
|
283
|
+
1. **Permission required:** All Chimera routes require `'chimera'` permission
|
|
284
|
+
|
|
285
|
+
2. **Field sets:** Models need `getFieldSet('edit')` configured to appear in admin
|
|
286
|
+
|
|
287
|
+
3. **has_configuration:** Models only show in sidebar if `chimera.has_configuration` is true
|
|
288
|
+
|
|
289
|
+
4. **Group tabs:** Multiple groups in edit fieldset automatically creates tabbed interface
|
|
290
|
+
|
|
291
|
+
5. **WebSocket skip:** Theme/beforeAction skipped for WebSocket connections
|
|
292
|
+
|
|
293
|
+
6. **Document watching:** Edit page uses toolbar manager for tracking document changes
|
|
294
|
+
|
|
295
|
+
7. **Private fields:** Call `record.keepPrivateFields()` to preserve sensitive data in responses
|
|
296
|
+
|
|
297
|
+
8. **Row action $pk:** Use `$pk` in route_params to substitute record's primary key
|
|
@@ -614,6 +614,56 @@ al-field[mode="inline"] {
|
|
|
614
614
|
display: flex;
|
|
615
615
|
gap: 1rem;
|
|
616
616
|
}
|
|
617
|
+
|
|
618
|
+
[data-he-slot="right"] {
|
|
619
|
+
display: flex;
|
|
620
|
+
gap: 1rem;
|
|
621
|
+
align-items: center;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// Button visibility based on toolbar state
|
|
625
|
+
&[state="editing"] {
|
|
626
|
+
.start-edit {
|
|
627
|
+
display: none;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
&[state="default"],
|
|
632
|
+
&[state="ready"] {
|
|
633
|
+
.stop-and-save,
|
|
634
|
+
.stop-edit,
|
|
635
|
+
.save-all,
|
|
636
|
+
chimera-dashboard-button[action="reset"] {
|
|
637
|
+
display: none;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
&[state="saving"],
|
|
642
|
+
&[state="saving-before-stop"] {
|
|
643
|
+
.start-edit,
|
|
644
|
+
.stop-edit {
|
|
645
|
+
display: none;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
&[state="saving"] {
|
|
650
|
+
.stop-and-save {
|
|
651
|
+
display: none;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
&[state="saving-before-stop"] {
|
|
656
|
+
.save-all {
|
|
657
|
+
display: none;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
// Save button styling
|
|
662
|
+
.stop-and-save,
|
|
663
|
+
.save-all {
|
|
664
|
+
--al-button-bg-color: green;
|
|
665
|
+
--al-button-bg-color-hover: rgb(55, 155, 55);
|
|
666
|
+
}
|
|
617
667
|
}
|
|
618
668
|
}
|
|
619
669
|
|
|
@@ -670,4 +720,231 @@ al-table {
|
|
|
670
720
|
background-color: #edeff5;
|
|
671
721
|
}
|
|
672
722
|
}
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
// Task Monitor Styles
|
|
726
|
+
chimera-task-monitor {
|
|
727
|
+
display: block;
|
|
728
|
+
|
|
729
|
+
.task-monitor-container {
|
|
730
|
+
background-color: white;
|
|
731
|
+
border: 1px solid var(--color-box-border, #dadee0);
|
|
732
|
+
border-radius: 4px;
|
|
733
|
+
padding: 1.5rem;
|
|
734
|
+
margin: 1rem;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
.task-monitor-header {
|
|
738
|
+
display: flex;
|
|
739
|
+
justify-content: space-between;
|
|
740
|
+
align-items: flex-start;
|
|
741
|
+
margin-bottom: 1.5rem;
|
|
742
|
+
padding-bottom: 1rem;
|
|
743
|
+
border-bottom: 1px solid var(--color-box-border, #dadee0);
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
.task-info {
|
|
747
|
+
.task-title {
|
|
748
|
+
margin: 0 0 0.5rem 0;
|
|
749
|
+
font-size: 1.5rem;
|
|
750
|
+
color: var(--color-title, #475466);
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
.task-type {
|
|
754
|
+
font-size: 0.9rem;
|
|
755
|
+
color: #888;
|
|
756
|
+
font-family: monospace;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
.task-meta {
|
|
761
|
+
display: flex;
|
|
762
|
+
gap: 2rem;
|
|
763
|
+
align-items: flex-start;
|
|
764
|
+
|
|
765
|
+
.meta-item {
|
|
766
|
+
display: flex;
|
|
767
|
+
flex-direction: column;
|
|
768
|
+
align-items: flex-end;
|
|
769
|
+
|
|
770
|
+
.meta-label {
|
|
771
|
+
font-size: 0.75rem;
|
|
772
|
+
color: #888;
|
|
773
|
+
text-transform: uppercase;
|
|
774
|
+
margin-bottom: 0.25rem;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
.task-status {
|
|
778
|
+
font-weight: 600;
|
|
779
|
+
padding: 0.25rem 0.75rem;
|
|
780
|
+
border-radius: 4px;
|
|
781
|
+
font-size: 0.9rem;
|
|
782
|
+
|
|
783
|
+
&.status-running {
|
|
784
|
+
background-color: #cff4fc;
|
|
785
|
+
color: #055160;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
&.status-paused {
|
|
789
|
+
background-color: #fff3cd;
|
|
790
|
+
color: #664d03;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
&.status-completed {
|
|
794
|
+
background-color: #d1e7dd;
|
|
795
|
+
color: #0f5132;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
&.status-error {
|
|
799
|
+
background-color: #f8d7da;
|
|
800
|
+
color: #842029;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
&.status-pending {
|
|
804
|
+
background-color: #e2e3e5;
|
|
805
|
+
color: #41464b;
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
.elapsed-time,
|
|
810
|
+
.progress-text {
|
|
811
|
+
font-size: 1.1rem;
|
|
812
|
+
font-weight: 600;
|
|
813
|
+
font-family: monospace;
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
.task-error {
|
|
819
|
+
background-color: #f8d7da;
|
|
820
|
+
color: #842029;
|
|
821
|
+
border: 1px solid #f5c2c7;
|
|
822
|
+
padding: 1rem;
|
|
823
|
+
border-radius: 4px;
|
|
824
|
+
margin-bottom: 1rem;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
.progress-container {
|
|
828
|
+
margin-bottom: 1.5rem;
|
|
829
|
+
|
|
830
|
+
.progress-track {
|
|
831
|
+
height: 8px;
|
|
832
|
+
background-color: #e9ecef;
|
|
833
|
+
border-radius: 4px;
|
|
834
|
+
overflow: hidden;
|
|
835
|
+
|
|
836
|
+
.progress-bar {
|
|
837
|
+
height: 100%;
|
|
838
|
+
background-color: var(--color-active, #3699FF);
|
|
839
|
+
border-radius: 4px;
|
|
840
|
+
transition: width 0.3s ease;
|
|
841
|
+
|
|
842
|
+
&.complete {
|
|
843
|
+
background-color: #198754;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
&.error {
|
|
847
|
+
background-color: #dc3545;
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
.task-controls {
|
|
854
|
+
display: flex;
|
|
855
|
+
gap: 1rem;
|
|
856
|
+
margin-bottom: 1.5rem;
|
|
857
|
+
|
|
858
|
+
.btn-danger {
|
|
859
|
+
--context-color: #842029;
|
|
860
|
+
--context-background-color: #f8d7da;
|
|
861
|
+
--context-border-color: #f5c2c7;
|
|
862
|
+
@extend .danger;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
.task-logs {
|
|
867
|
+
margin-bottom: 1.5rem;
|
|
868
|
+
|
|
869
|
+
.logs-title {
|
|
870
|
+
margin: 0 0 0.75rem 0;
|
|
871
|
+
font-size: 1rem;
|
|
872
|
+
color: var(--color-title, #475466);
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
.log-output {
|
|
876
|
+
background-color: #1e1e1e;
|
|
877
|
+
color: #d4d4d4;
|
|
878
|
+
border-radius: 4px;
|
|
879
|
+
padding: 1rem;
|
|
880
|
+
font-family: 'Consolas', 'Monaco', monospace;
|
|
881
|
+
font-size: 0.85rem;
|
|
882
|
+
line-height: 1.5;
|
|
883
|
+
min-height: 200px;
|
|
884
|
+
max-height: 400px;
|
|
885
|
+
overflow-y: auto;
|
|
886
|
+
|
|
887
|
+
.log-entry {
|
|
888
|
+
margin-bottom: 0.25rem;
|
|
889
|
+
display: flex;
|
|
890
|
+
gap: 1rem;
|
|
891
|
+
|
|
892
|
+
.log-timestamp {
|
|
893
|
+
color: #888;
|
|
894
|
+
flex-shrink: 0;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
.log-content {
|
|
898
|
+
white-space: pre-wrap;
|
|
899
|
+
word-break: break-word;
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
&.log-success .log-content {
|
|
903
|
+
color: #4ec9b0;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
&.log-error .log-content {
|
|
907
|
+
color: #f14c4c;
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
&.log-warning .log-content {
|
|
911
|
+
color: #cca700;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
&.log-info .log-content {
|
|
915
|
+
color: #3dc9b0;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
&.log-detail .log-content {
|
|
919
|
+
color: #9cdcfe;
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
&.log-stack .log-content {
|
|
923
|
+
font-size: 0.8rem;
|
|
924
|
+
color: #808080;
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
.task-timestamps {
|
|
931
|
+
display: flex;
|
|
932
|
+
gap: 2rem;
|
|
933
|
+
padding-top: 1rem;
|
|
934
|
+
border-top: 1px solid var(--color-box-border, #dadee0);
|
|
935
|
+
font-size: 0.9rem;
|
|
936
|
+
|
|
937
|
+
.timestamp-item {
|
|
938
|
+
display: flex;
|
|
939
|
+
gap: 0.5rem;
|
|
940
|
+
|
|
941
|
+
.timestamp-label {
|
|
942
|
+
color: #888;
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
.timestamp-value {
|
|
946
|
+
font-family: monospace;
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
}
|
|
673
950
|
}
|
package/config/routes.js
CHANGED
|
@@ -13,6 +13,22 @@ chimera_section.add({
|
|
|
13
13
|
handler : 'Chimera.Static#dashboard',
|
|
14
14
|
});
|
|
15
15
|
|
|
16
|
+
// Customize dashboard - create a user-specific copy
|
|
17
|
+
chimera_section.add({
|
|
18
|
+
name : 'Chimera.Static#customizeDashboard',
|
|
19
|
+
methods : 'post',
|
|
20
|
+
paths : '/api/dashboard/customize',
|
|
21
|
+
is_system_route : true,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// Reset dashboard - remove user-specific copy, revert to default
|
|
25
|
+
chimera_section.add({
|
|
26
|
+
name : 'Chimera.Static#resetDashboard',
|
|
27
|
+
methods : 'post',
|
|
28
|
+
paths : '/api/dashboard/reset',
|
|
29
|
+
is_system_route : true,
|
|
30
|
+
});
|
|
31
|
+
|
|
16
32
|
// Settings editor
|
|
17
33
|
chimera_section.add({
|
|
18
34
|
name : 'Chimera.Settings#editor',
|
|
@@ -52,6 +68,14 @@ chimera_section.add({
|
|
|
52
68
|
breadcrumb : 'chimera.editor.{model}.trash.{pk}'
|
|
53
69
|
});
|
|
54
70
|
|
|
71
|
+
// Task monitor page - shows live progress of a running task
|
|
72
|
+
chimera_section.add({
|
|
73
|
+
name : 'Chimera.Editor#taskMonitor',
|
|
74
|
+
methods : ['get'],
|
|
75
|
+
paths : '/task-monitor/{task_history_id}',
|
|
76
|
+
breadcrumb : 'chimera.task-monitor'
|
|
77
|
+
});
|
|
78
|
+
|
|
55
79
|
// Editor data action
|
|
56
80
|
chimera_section.add({
|
|
57
81
|
name : 'Chimera.Editor#records',
|
|
@@ -68,6 +92,20 @@ chimera_section.add({
|
|
|
68
92
|
is_system_route : true,
|
|
69
93
|
});
|
|
70
94
|
|
|
95
|
+
// System Task run action - starts a task manually
|
|
96
|
+
chimera_section.add({
|
|
97
|
+
name : 'Chimera.SystemTask#run',
|
|
98
|
+
paths : '/api/system/task/{[System.Task]_id}/run',
|
|
99
|
+
methods : ['post'],
|
|
100
|
+
is_system_route : true,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// Linkup route for live task monitoring via WebSocket
|
|
104
|
+
// Permission is inherited from chimera_section.requirePermission('chimera')
|
|
105
|
+
// Note: eventname is 'taskmonitor' (not 'chimera@taskmonitor') because the section prefix
|
|
106
|
+
// is handled by the router lookup, not stored in the key
|
|
107
|
+
chimera_section.linkup('Chimera.SystemTask#monitor', 'taskmonitor', 'Chimera.SystemTask#monitor');
|
|
108
|
+
|
|
71
109
|
alchemy.sputnik.after('base_app', () => {
|
|
72
110
|
|
|
73
111
|
let prefixes = Prefix.all();
|