xcomponent-ai 0.3.2 → 0.4.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/README.md +42 -6
- package/dist/cli.js +159 -3
- package/dist/cli.js.map +1 -1
- package/dist/component-registry.d.ts.map +1 -1
- package/dist/component-registry.js +18 -0
- package/dist/component-registry.js.map +1 -1
- package/dist/fsm-runtime.d.ts.map +1 -1
- package/dist/fsm-runtime.js +13 -4
- package/dist/fsm-runtime.js.map +1 -1
- package/dist/mermaid-generator.d.ts +29 -1
- package/dist/mermaid-generator.d.ts.map +1 -1
- package/dist/mermaid-generator.js +106 -19
- package/dist/mermaid-generator.js.map +1 -1
- package/dist/types.d.ts +14 -0
- package/dist/types.d.ts.map +1 -1
- package/examples/approval-workflow/README.md +98 -0
- package/examples/approval-workflow/component.yaml +202 -0
- package/examples/e-commerce-order/README.md +75 -0
- package/examples/e-commerce-order/component.yaml +294 -0
- package/examples/subscription-lifecycle/README.md +122 -0
- package/examples/subscription-lifecycle/component.yaml +279 -0
- package/examples/xcomponent-pattern-demo.yaml +218 -0
- package/package.json +2 -2
- package/public/dashboard.html +1502 -813
package/public/dashboard.html
CHANGED
|
@@ -10,1113 +10,1802 @@
|
|
|
10
10
|
/* Reset & Base */
|
|
11
11
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
:root {
|
|
14
|
+
--bg-primary: #0f0f0f;
|
|
15
|
+
--bg-secondary: #1a1a1a;
|
|
16
|
+
--bg-tertiary: #252525;
|
|
17
|
+
--glass-bg: rgba(255, 255, 255, 0.08);
|
|
18
|
+
--glass-border: rgba(255, 255, 255, 0.15);
|
|
19
|
+
--text-primary: #f0f0f0;
|
|
20
|
+
--text-secondary: rgba(255, 255, 255, 0.7);
|
|
21
|
+
--text-muted: rgba(255, 255, 255, 0.5);
|
|
22
|
+
--accent: #fbbf24;
|
|
23
|
+
--accent-hover: #f59e0b;
|
|
24
|
+
--success: #10b981;
|
|
25
|
+
--error: #ef4444;
|
|
26
|
+
--info: #3b82f6;
|
|
27
|
+
--inter-machine: #10b981;
|
|
28
|
+
--sidebar-width: 300px;
|
|
29
|
+
--header-height: 60px;
|
|
30
|
+
--history-height: 260px;
|
|
31
|
+
}
|
|
32
|
+
|
|
14
33
|
body {
|
|
15
34
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
16
|
-
background:
|
|
17
|
-
background-size: 400% 400%;
|
|
18
|
-
animation: gradientShift 15s ease infinite;
|
|
35
|
+
background: var(--bg-primary);
|
|
19
36
|
min-height: 100vh;
|
|
20
|
-
color:
|
|
37
|
+
color: var(--text-primary);
|
|
38
|
+
overflow: hidden;
|
|
21
39
|
}
|
|
22
40
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
41
|
+
/* Main Layout */
|
|
42
|
+
.app-layout {
|
|
43
|
+
display: grid;
|
|
44
|
+
grid-template-columns: var(--sidebar-width) 1fr;
|
|
45
|
+
grid-template-rows: var(--header-height) 1fr var(--history-height);
|
|
46
|
+
height: 100vh;
|
|
47
|
+
gap: 1px;
|
|
48
|
+
background: var(--glass-border);
|
|
27
49
|
}
|
|
28
50
|
|
|
29
|
-
|
|
30
|
-
max-width: 1600px;
|
|
31
|
-
margin: 0 auto;
|
|
32
|
-
padding: 30px 20px;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/* Glass Morphism Header */
|
|
51
|
+
/* Header */
|
|
36
52
|
.header {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
-webkit-backdrop-filter: blur(20px);
|
|
40
|
-
border: 1px solid rgba(255, 255, 255, 0.3);
|
|
41
|
-
box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.15);
|
|
42
|
-
color: white;
|
|
43
|
-
padding: 30px;
|
|
44
|
-
border-radius: 20px;
|
|
45
|
-
margin-bottom: 30px;
|
|
53
|
+
grid-column: 1 / -1;
|
|
54
|
+
background: var(--bg-secondary);
|
|
46
55
|
display: flex;
|
|
47
|
-
justify-content: space-between;
|
|
48
56
|
align-items: center;
|
|
49
|
-
|
|
57
|
+
justify-content: space-between;
|
|
58
|
+
padding: 0 24px;
|
|
59
|
+
border-bottom: 1px solid var(--glass-border);
|
|
50
60
|
}
|
|
51
61
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
62
|
+
.header-left {
|
|
63
|
+
display: flex;
|
|
64
|
+
align-items: center;
|
|
65
|
+
gap: 16px;
|
|
55
66
|
}
|
|
56
67
|
|
|
57
|
-
.
|
|
58
|
-
font-size:
|
|
68
|
+
.logo {
|
|
69
|
+
font-size: 20px;
|
|
59
70
|
font-weight: 700;
|
|
60
|
-
|
|
61
|
-
-webkit-background-clip: text;
|
|
62
|
-
-webkit-text-fill-color: transparent;
|
|
63
|
-
background-clip: text;
|
|
64
|
-
letter-spacing: -0.5px;
|
|
71
|
+
color: var(--text-primary);
|
|
65
72
|
}
|
|
66
73
|
|
|
67
|
-
.
|
|
68
|
-
|
|
69
|
-
.status-dot {
|
|
70
|
-
width: 12px;
|
|
71
|
-
height: 12px;
|
|
72
|
-
border-radius: 50%;
|
|
73
|
-
position: relative;
|
|
74
|
+
.logo span {
|
|
75
|
+
color: var(--accent);
|
|
74
76
|
}
|
|
75
77
|
|
|
76
|
-
.status
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
.connection-status {
|
|
79
|
+
display: flex;
|
|
80
|
+
align-items: center;
|
|
81
|
+
gap: 8px;
|
|
82
|
+
font-size: 13px;
|
|
83
|
+
color: var(--text-secondary);
|
|
80
84
|
}
|
|
81
85
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
86
|
+
.status-dot {
|
|
87
|
+
width: 8px;
|
|
88
|
+
height: 8px;
|
|
89
|
+
border-radius: 50%;
|
|
90
|
+
background: var(--error);
|
|
85
91
|
}
|
|
86
92
|
|
|
87
|
-
.status-dot.
|
|
88
|
-
background:
|
|
89
|
-
box-shadow: 0 0 10px
|
|
93
|
+
.status-dot.connected {
|
|
94
|
+
background: var(--success);
|
|
95
|
+
box-shadow: 0 0 10px var(--success);
|
|
90
96
|
}
|
|
91
97
|
|
|
92
|
-
.header-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
padding: 10px 20px;
|
|
96
|
-
background: rgba(255, 255, 255, 0.2);
|
|
97
|
-
border: 1px solid rgba(255, 255, 255, 0.3);
|
|
98
|
-
border-radius: 10px;
|
|
99
|
-
color: white;
|
|
100
|
-
text-decoration: none;
|
|
101
|
-
font-size: 14px;
|
|
102
|
-
font-weight: 600;
|
|
103
|
-
transition: all 0.3s ease;
|
|
98
|
+
.header-center {
|
|
99
|
+
display: flex;
|
|
100
|
+
gap: 8px;
|
|
104
101
|
}
|
|
105
102
|
|
|
106
|
-
.
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
/* Stats Cards with Gradients */
|
|
113
|
-
.stats-grid {
|
|
114
|
-
display: grid;
|
|
115
|
-
grid-template-columns: repeat(5, 1fr);
|
|
116
|
-
gap: 20px;
|
|
117
|
-
margin-bottom: 30px;
|
|
118
|
-
animation: fadeIn 0.6s ease-out 0.2s both;
|
|
103
|
+
.view-toggle {
|
|
104
|
+
display: flex;
|
|
105
|
+
background: var(--bg-tertiary);
|
|
106
|
+
border-radius: 8px;
|
|
107
|
+
padding: 4px;
|
|
119
108
|
}
|
|
120
109
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
110
|
+
.view-toggle-btn {
|
|
111
|
+
padding: 8px 16px;
|
|
112
|
+
border: none;
|
|
113
|
+
background: transparent;
|
|
114
|
+
color: var(--text-secondary);
|
|
115
|
+
font-size: 13px;
|
|
116
|
+
font-weight: 500;
|
|
117
|
+
cursor: pointer;
|
|
118
|
+
border-radius: 6px;
|
|
119
|
+
transition: all 0.2s;
|
|
124
120
|
}
|
|
125
121
|
|
|
126
|
-
.
|
|
127
|
-
background:
|
|
128
|
-
|
|
129
|
-
-webkit-backdrop-filter: blur(20px);
|
|
130
|
-
border: 1px solid rgba(255, 255, 255, 0.3);
|
|
131
|
-
border-radius: 16px;
|
|
132
|
-
padding: 25px;
|
|
133
|
-
box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.1);
|
|
134
|
-
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
135
|
-
position: relative;
|
|
136
|
-
overflow: hidden;
|
|
122
|
+
.view-toggle-btn.active {
|
|
123
|
+
background: var(--accent);
|
|
124
|
+
color: #000;
|
|
137
125
|
}
|
|
138
126
|
|
|
139
|
-
.
|
|
140
|
-
|
|
141
|
-
position: absolute;
|
|
142
|
-
top: 0;
|
|
143
|
-
left: 0;
|
|
144
|
-
right: 0;
|
|
145
|
-
height: 4px;
|
|
146
|
-
background: linear-gradient(90deg, #fbbf24, #f59e0b);
|
|
147
|
-
transform: scaleX(0);
|
|
148
|
-
transition: transform 0.3s ease;
|
|
127
|
+
.view-toggle-btn:hover:not(.active) {
|
|
128
|
+
color: var(--text-primary);
|
|
149
129
|
}
|
|
150
130
|
|
|
151
|
-
.
|
|
152
|
-
|
|
153
|
-
|
|
131
|
+
.header-stats {
|
|
132
|
+
display: flex;
|
|
133
|
+
gap: 24px;
|
|
154
134
|
}
|
|
155
135
|
|
|
156
|
-
.stat-
|
|
157
|
-
|
|
136
|
+
.stat-item {
|
|
137
|
+
text-align: center;
|
|
158
138
|
}
|
|
159
139
|
|
|
160
140
|
.stat-value {
|
|
161
|
-
font-size:
|
|
162
|
-
font-weight:
|
|
163
|
-
|
|
164
|
-
-webkit-background-clip: text;
|
|
165
|
-
-webkit-text-fill-color: transparent;
|
|
166
|
-
background-clip: text;
|
|
167
|
-
line-height: 1.2;
|
|
141
|
+
font-size: 20px;
|
|
142
|
+
font-weight: 700;
|
|
143
|
+
color: var(--accent);
|
|
168
144
|
}
|
|
169
145
|
|
|
170
146
|
.stat-label {
|
|
171
|
-
font-size:
|
|
172
|
-
color:
|
|
173
|
-
margin-top: 8px;
|
|
147
|
+
font-size: 10px;
|
|
148
|
+
color: var(--text-muted);
|
|
174
149
|
text-transform: uppercase;
|
|
175
|
-
letter-spacing:
|
|
176
|
-
font-weight: 600;
|
|
150
|
+
letter-spacing: 0.5px;
|
|
177
151
|
}
|
|
178
|
-
|
|
179
|
-
/*
|
|
180
|
-
.
|
|
152
|
+
|
|
153
|
+
/* Sidebar */
|
|
154
|
+
.sidebar {
|
|
155
|
+
background: var(--bg-secondary);
|
|
181
156
|
display: flex;
|
|
182
|
-
|
|
183
|
-
backdrop-filter: blur(20px);
|
|
184
|
-
-webkit-backdrop-filter: blur(20px);
|
|
185
|
-
border: 1px solid rgba(255, 255, 255, 0.3);
|
|
186
|
-
border-radius: 16px 16px 0 0;
|
|
187
|
-
box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.1);
|
|
157
|
+
flex-direction: column;
|
|
188
158
|
overflow: hidden;
|
|
189
|
-
animation: fadeIn 0.7s ease-out 0.3s both;
|
|
190
159
|
}
|
|
191
160
|
|
|
192
|
-
.
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
161
|
+
.sidebar-section {
|
|
162
|
+
padding: 16px;
|
|
163
|
+
border-bottom: 1px solid var(--glass-border);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.sidebar-section-title {
|
|
167
|
+
font-size: 11px;
|
|
198
168
|
font-weight: 600;
|
|
169
|
+
color: var(--text-muted);
|
|
170
|
+
text-transform: uppercase;
|
|
171
|
+
letter-spacing: 1px;
|
|
172
|
+
margin-bottom: 12px;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.selector {
|
|
176
|
+
width: 100%;
|
|
177
|
+
padding: 10px 12px;
|
|
178
|
+
background: var(--bg-tertiary);
|
|
179
|
+
border: 1px solid var(--glass-border);
|
|
180
|
+
border-radius: 8px;
|
|
181
|
+
color: var(--text-primary);
|
|
199
182
|
font-size: 14px;
|
|
200
|
-
|
|
201
|
-
transition: all 0.
|
|
202
|
-
position: relative;
|
|
183
|
+
cursor: pointer;
|
|
184
|
+
transition: all 0.2s;
|
|
203
185
|
}
|
|
204
186
|
|
|
205
|
-
.
|
|
206
|
-
|
|
207
|
-
position: absolute;
|
|
208
|
-
bottom: 0;
|
|
209
|
-
left: 50%;
|
|
210
|
-
width: 0;
|
|
211
|
-
height: 3px;
|
|
212
|
-
background: linear-gradient(90deg, #fbbf24, #f59e0b);
|
|
213
|
-
transform: translateX(-50%);
|
|
214
|
-
transition: width 0.3s ease;
|
|
187
|
+
.selector:hover {
|
|
188
|
+
border-color: var(--accent);
|
|
215
189
|
}
|
|
216
190
|
|
|
217
|
-
.
|
|
218
|
-
|
|
219
|
-
color:
|
|
191
|
+
.selector:focus {
|
|
192
|
+
outline: none;
|
|
193
|
+
border-color: var(--accent);
|
|
194
|
+
box-shadow: 0 0 0 2px rgba(251, 191, 36, 0.2);
|
|
220
195
|
}
|
|
221
196
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
197
|
+
/* Transitions Panel */
|
|
198
|
+
.transitions-panel {
|
|
199
|
+
padding: 16px;
|
|
200
|
+
border-bottom: 1px solid var(--glass-border);
|
|
201
|
+
max-height: 200px;
|
|
202
|
+
overflow-y: auto;
|
|
225
203
|
}
|
|
226
204
|
|
|
227
|
-
.
|
|
228
|
-
|
|
205
|
+
.transition-item {
|
|
206
|
+
display: flex;
|
|
207
|
+
align-items: center;
|
|
208
|
+
justify-content: space-between;
|
|
209
|
+
padding: 10px 12px;
|
|
210
|
+
background: var(--bg-tertiary);
|
|
211
|
+
border: 1px solid var(--glass-border);
|
|
212
|
+
border-radius: 8px;
|
|
213
|
+
margin-bottom: 8px;
|
|
214
|
+
cursor: pointer;
|
|
215
|
+
transition: all 0.2s;
|
|
229
216
|
}
|
|
230
217
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
background: rgba(255, 255, 255, 0.15);
|
|
235
|
-
backdrop-filter: blur(20px);
|
|
236
|
-
-webkit-backdrop-filter: blur(20px);
|
|
237
|
-
border: 1px solid rgba(255, 255, 255, 0.3);
|
|
238
|
-
border-top: none;
|
|
239
|
-
padding: 30px;
|
|
240
|
-
border-radius: 0 0 16px 16px;
|
|
241
|
-
box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.1);
|
|
242
|
-
min-height: 600px;
|
|
243
|
-
animation: fadeInContent 0.4s ease-out;
|
|
218
|
+
.transition-item:hover {
|
|
219
|
+
border-color: var(--accent);
|
|
220
|
+
background: rgba(251, 191, 36, 0.05);
|
|
244
221
|
}
|
|
245
222
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
to { opacity: 1; transform: translateY(0); }
|
|
223
|
+
.transition-item.inter-machine {
|
|
224
|
+
border-left: 3px solid var(--inter-machine);
|
|
249
225
|
}
|
|
250
226
|
|
|
251
|
-
.
|
|
252
|
-
|
|
227
|
+
.transition-info {
|
|
228
|
+
flex: 1;
|
|
253
229
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
/* Glass Card */
|
|
260
|
-
.card {
|
|
261
|
-
background: rgba(255, 255, 255, 0.1);
|
|
262
|
-
backdrop-filter: blur(10px);
|
|
263
|
-
-webkit-backdrop-filter: blur(10px);
|
|
264
|
-
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
265
|
-
border-radius: 16px;
|
|
266
|
-
padding: 25px;
|
|
267
|
-
box-shadow: 0 4px 24px 0 rgba(31, 38, 135, 0.1);
|
|
230
|
+
|
|
231
|
+
.transition-event {
|
|
232
|
+
font-weight: 600;
|
|
233
|
+
font-size: 13px;
|
|
234
|
+
color: var(--text-primary);
|
|
268
235
|
}
|
|
269
236
|
|
|
270
|
-
.
|
|
271
|
-
font-size:
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
margin-bottom: 20px;
|
|
275
|
-
padding-bottom: 15px;
|
|
276
|
-
border-bottom: 2px solid rgba(255, 255, 255, 0.2);
|
|
277
|
-
background: linear-gradient(135deg, #fff 0%, rgba(255, 255, 255, 0.8) 100%);
|
|
278
|
-
-webkit-background-clip: text;
|
|
279
|
-
-webkit-text-fill-color: transparent;
|
|
280
|
-
background-clip: text;
|
|
237
|
+
.transition-path {
|
|
238
|
+
font-size: 11px;
|
|
239
|
+
color: var(--text-muted);
|
|
240
|
+
margin-top: 2px;
|
|
281
241
|
}
|
|
282
242
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
243
|
+
.transition-target {
|
|
244
|
+
font-size: 10px;
|
|
245
|
+
color: var(--inter-machine);
|
|
246
|
+
margin-top: 2px;
|
|
286
247
|
}
|
|
287
248
|
|
|
288
|
-
.
|
|
289
|
-
|
|
249
|
+
.transition-send-btn {
|
|
250
|
+
padding: 6px 12px;
|
|
251
|
+
background: var(--accent);
|
|
252
|
+
border: none;
|
|
253
|
+
border-radius: 6px;
|
|
254
|
+
color: #000;
|
|
255
|
+
font-size: 11px;
|
|
290
256
|
font-weight: 600;
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
color: rgba(255, 255, 255, 0.9);
|
|
257
|
+
cursor: pointer;
|
|
258
|
+
transition: all 0.2s;
|
|
294
259
|
}
|
|
295
260
|
|
|
296
|
-
.
|
|
297
|
-
|
|
298
|
-
.form-group textarea {
|
|
299
|
-
width: 100%;
|
|
300
|
-
padding: 12px 16px;
|
|
301
|
-
background: rgba(255, 255, 255, 0.15);
|
|
302
|
-
border: 1px solid rgba(255, 255, 255, 0.3);
|
|
303
|
-
border-radius: 10px;
|
|
304
|
-
font-size: 14px;
|
|
305
|
-
color: white;
|
|
306
|
-
transition: all 0.3s ease;
|
|
261
|
+
.transition-send-btn:hover {
|
|
262
|
+
background: var(--accent-hover);
|
|
307
263
|
}
|
|
308
264
|
|
|
309
|
-
.
|
|
310
|
-
|
|
311
|
-
color:
|
|
265
|
+
.transition-send-btn:disabled {
|
|
266
|
+
background: var(--bg-tertiary);
|
|
267
|
+
color: var(--text-muted);
|
|
268
|
+
cursor: not-allowed;
|
|
312
269
|
}
|
|
313
270
|
|
|
314
|
-
|
|
315
|
-
.
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
border-color: #fbbf24;
|
|
320
|
-
box-shadow: 0 0 0 3px rgba(251, 191, 36, 0.2);
|
|
271
|
+
/* Instance List */
|
|
272
|
+
.instance-list-container {
|
|
273
|
+
flex: 1;
|
|
274
|
+
overflow-y: auto;
|
|
275
|
+
padding: 16px;
|
|
321
276
|
}
|
|
322
277
|
|
|
323
|
-
.
|
|
278
|
+
.instance-list-header {
|
|
279
|
+
display: flex;
|
|
280
|
+
justify-content: space-between;
|
|
281
|
+
align-items: center;
|
|
282
|
+
margin-bottom: 12px;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
.instance-count {
|
|
324
286
|
font-size: 12px;
|
|
325
|
-
color:
|
|
326
|
-
margin-top: 6px;
|
|
327
|
-
font-style: italic;
|
|
287
|
+
color: var(--text-muted);
|
|
328
288
|
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
289
|
+
|
|
290
|
+
.btn-create {
|
|
291
|
+
padding: 6px 12px;
|
|
292
|
+
background: var(--accent);
|
|
333
293
|
border: none;
|
|
334
|
-
border-radius:
|
|
294
|
+
border-radius: 6px;
|
|
295
|
+
color: #000;
|
|
296
|
+
font-size: 12px;
|
|
297
|
+
font-weight: 600;
|
|
335
298
|
cursor: pointer;
|
|
336
|
-
|
|
337
|
-
font-size: 14px;
|
|
338
|
-
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
339
|
-
box-shadow: 0 4px 15px 0 rgba(0, 0, 0, 0.1);
|
|
340
|
-
position: relative;
|
|
341
|
-
overflow: hidden;
|
|
299
|
+
transition: all 0.2s;
|
|
342
300
|
}
|
|
343
301
|
|
|
344
|
-
.btn
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
top: 50%;
|
|
348
|
-
left: 50%;
|
|
349
|
-
width: 0;
|
|
350
|
-
height: 0;
|
|
351
|
-
border-radius: 50%;
|
|
352
|
-
background: rgba(255, 255, 255, 0.3);
|
|
353
|
-
transform: translate(-50%, -50%);
|
|
354
|
-
transition: width 0.5s, height 0.5s;
|
|
302
|
+
.btn-create:hover {
|
|
303
|
+
background: var(--accent-hover);
|
|
304
|
+
transform: translateY(-1px);
|
|
355
305
|
}
|
|
356
306
|
|
|
357
|
-
.
|
|
358
|
-
|
|
359
|
-
|
|
307
|
+
.instance-item {
|
|
308
|
+
background: var(--bg-tertiary);
|
|
309
|
+
border: 1px solid var(--glass-border);
|
|
310
|
+
border-radius: 8px;
|
|
311
|
+
padding: 12px;
|
|
312
|
+
margin-bottom: 8px;
|
|
313
|
+
cursor: pointer;
|
|
314
|
+
transition: all 0.2s;
|
|
360
315
|
}
|
|
361
316
|
|
|
362
|
-
.
|
|
363
|
-
|
|
364
|
-
|
|
317
|
+
.instance-item:hover {
|
|
318
|
+
border-color: var(--accent);
|
|
319
|
+
background: rgba(251, 191, 36, 0.05);
|
|
365
320
|
}
|
|
366
321
|
|
|
367
|
-
.
|
|
368
|
-
|
|
369
|
-
|
|
322
|
+
.instance-item.selected {
|
|
323
|
+
border-color: var(--accent);
|
|
324
|
+
background: rgba(251, 191, 36, 0.1);
|
|
325
|
+
box-shadow: 0 0 0 1px var(--accent);
|
|
370
326
|
}
|
|
371
327
|
|
|
372
|
-
.
|
|
373
|
-
|
|
374
|
-
|
|
328
|
+
.instance-header {
|
|
329
|
+
display: flex;
|
|
330
|
+
justify-content: space-between;
|
|
331
|
+
align-items: center;
|
|
332
|
+
margin-bottom: 8px;
|
|
375
333
|
}
|
|
376
334
|
|
|
377
|
-
.
|
|
378
|
-
|
|
379
|
-
|
|
335
|
+
.instance-id {
|
|
336
|
+
font-family: 'Monaco', 'Consolas', monospace;
|
|
337
|
+
font-size: 12px;
|
|
338
|
+
color: var(--text-primary);
|
|
380
339
|
}
|
|
381
340
|
|
|
382
|
-
.
|
|
383
|
-
|
|
384
|
-
|
|
341
|
+
.instance-state {
|
|
342
|
+
padding: 4px 8px;
|
|
343
|
+
border-radius: 12px;
|
|
344
|
+
font-size: 10px;
|
|
345
|
+
font-weight: 600;
|
|
346
|
+
text-transform: uppercase;
|
|
385
347
|
}
|
|
386
348
|
|
|
387
|
-
.
|
|
388
|
-
|
|
389
|
-
|
|
349
|
+
.instance-state.active {
|
|
350
|
+
background: rgba(59, 130, 246, 0.2);
|
|
351
|
+
color: var(--info);
|
|
390
352
|
}
|
|
391
353
|
|
|
392
|
-
.
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
/* Instance List - Modern Cards */
|
|
398
|
-
.instance-list {
|
|
399
|
-
max-height: 500px;
|
|
400
|
-
overflow-y: auto;
|
|
401
|
-
scrollbar-width: thin;
|
|
402
|
-
scrollbar-color: rgba(255, 255, 255, 0.3) transparent;
|
|
354
|
+
.instance-state.terminal {
|
|
355
|
+
background: rgba(16, 185, 129, 0.2);
|
|
356
|
+
color: var(--success);
|
|
403
357
|
}
|
|
404
358
|
|
|
405
|
-
.instance-
|
|
406
|
-
|
|
359
|
+
.instance-state.error {
|
|
360
|
+
background: rgba(239, 68, 68, 0.2);
|
|
361
|
+
color: var(--error);
|
|
407
362
|
}
|
|
408
363
|
|
|
409
|
-
.instance-
|
|
410
|
-
|
|
411
|
-
|
|
364
|
+
.instance-meta {
|
|
365
|
+
font-size: 11px;
|
|
366
|
+
color: var(--text-muted);
|
|
412
367
|
}
|
|
413
368
|
|
|
414
|
-
.
|
|
415
|
-
|
|
416
|
-
|
|
369
|
+
.empty-state {
|
|
370
|
+
text-align: center;
|
|
371
|
+
padding: 40px 20px;
|
|
372
|
+
color: var(--text-muted);
|
|
417
373
|
}
|
|
418
374
|
|
|
419
|
-
.
|
|
420
|
-
|
|
421
|
-
backdrop-filter: blur(10px);
|
|
422
|
-
-webkit-backdrop-filter: blur(10px);
|
|
423
|
-
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
424
|
-
padding: 18px;
|
|
375
|
+
.empty-state-icon {
|
|
376
|
+
font-size: 32px;
|
|
425
377
|
margin-bottom: 12px;
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
378
|
+
opacity: 0.5;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/* Main Content - Diagram */
|
|
382
|
+
.main-content {
|
|
383
|
+
background: var(--bg-primary);
|
|
384
|
+
display: flex;
|
|
385
|
+
flex-direction: column;
|
|
430
386
|
overflow: hidden;
|
|
431
387
|
}
|
|
432
388
|
|
|
433
|
-
.
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
background: linear-gradient(180deg, #fbbf24, #f59e0b);
|
|
441
|
-
opacity: 0;
|
|
442
|
-
transition: opacity 0.3s ease;
|
|
389
|
+
.diagram-header {
|
|
390
|
+
padding: 16px 24px;
|
|
391
|
+
background: var(--bg-secondary);
|
|
392
|
+
border-bottom: 1px solid var(--glass-border);
|
|
393
|
+
display: flex;
|
|
394
|
+
justify-content: space-between;
|
|
395
|
+
align-items: center;
|
|
443
396
|
}
|
|
444
397
|
|
|
445
|
-
.
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
transform: translateY(-3px) translateX(4px);
|
|
398
|
+
.diagram-title {
|
|
399
|
+
font-size: 16px;
|
|
400
|
+
font-weight: 600;
|
|
449
401
|
}
|
|
450
402
|
|
|
451
|
-
.
|
|
452
|
-
|
|
403
|
+
.diagram-title span {
|
|
404
|
+
color: var(--accent);
|
|
453
405
|
}
|
|
454
406
|
|
|
455
|
-
.
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
407
|
+
.diagram-legend {
|
|
408
|
+
display: flex;
|
|
409
|
+
gap: 16px;
|
|
410
|
+
font-size: 12px;
|
|
459
411
|
}
|
|
460
412
|
|
|
461
|
-
.
|
|
413
|
+
.legend-item {
|
|
462
414
|
display: flex;
|
|
463
|
-
justify-content: space-between;
|
|
464
415
|
align-items: center;
|
|
465
|
-
|
|
416
|
+
gap: 6px;
|
|
417
|
+
color: var(--text-secondary);
|
|
466
418
|
}
|
|
467
419
|
|
|
468
|
-
.
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
font-size: 13px;
|
|
473
|
-
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
420
|
+
.legend-dot {
|
|
421
|
+
width: 12px;
|
|
422
|
+
height: 12px;
|
|
423
|
+
border-radius: 3px;
|
|
474
424
|
}
|
|
475
425
|
|
|
476
|
-
.
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
font-weight: 700;
|
|
481
|
-
text-transform: uppercase;
|
|
482
|
-
letter-spacing: 0.5px;
|
|
483
|
-
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
426
|
+
.legend-line {
|
|
427
|
+
width: 20px;
|
|
428
|
+
height: 3px;
|
|
429
|
+
border-radius: 2px;
|
|
484
430
|
}
|
|
485
431
|
|
|
486
|
-
.
|
|
487
|
-
|
|
488
|
-
|
|
432
|
+
.legend-dot.entry { background: #fbbf24; }
|
|
433
|
+
.legend-dot.current { background: #3b82f6; }
|
|
434
|
+
.legend-dot.terminal { background: #10b981; }
|
|
435
|
+
.legend-dot.error { background: #ef4444; }
|
|
436
|
+
.legend-line.inter-machine { background: #10b981; }
|
|
437
|
+
|
|
438
|
+
.diagram-container {
|
|
439
|
+
flex: 1;
|
|
440
|
+
padding: 24px;
|
|
441
|
+
overflow: auto;
|
|
442
|
+
display: flex;
|
|
443
|
+
align-items: center;
|
|
444
|
+
justify-content: center;
|
|
489
445
|
}
|
|
490
446
|
|
|
491
|
-
.
|
|
492
|
-
background:
|
|
493
|
-
|
|
447
|
+
.mermaid-wrapper {
|
|
448
|
+
background: #fff;
|
|
449
|
+
border-radius: 12px;
|
|
450
|
+
padding: 24px;
|
|
451
|
+
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.3);
|
|
452
|
+
max-width: 100%;
|
|
453
|
+
overflow: auto;
|
|
494
454
|
}
|
|
495
455
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
456
|
+
/* Component View */
|
|
457
|
+
.component-view {
|
|
458
|
+
flex: 1;
|
|
459
|
+
padding: 24px;
|
|
460
|
+
overflow: auto;
|
|
461
|
+
position: relative;
|
|
499
462
|
}
|
|
500
463
|
|
|
501
|
-
.
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
464
|
+
.component-graph {
|
|
465
|
+
position: relative;
|
|
466
|
+
min-height: 600px;
|
|
467
|
+
width: 100%;
|
|
505
468
|
}
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
background:
|
|
510
|
-
|
|
511
|
-
-webkit-backdrop-filter: blur(10px);
|
|
512
|
-
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
469
|
+
|
|
470
|
+
.machine-card {
|
|
471
|
+
position: absolute;
|
|
472
|
+
background: var(--bg-secondary);
|
|
473
|
+
border: 2px solid var(--glass-border);
|
|
513
474
|
border-radius: 12px;
|
|
514
|
-
padding:
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
box-shadow: inset 0 2px 10px rgba(0, 0, 0, 0.3);
|
|
475
|
+
padding: 16px;
|
|
476
|
+
min-width: 180px;
|
|
477
|
+
cursor: pointer;
|
|
478
|
+
transition: all 0.2s;
|
|
519
479
|
}
|
|
520
480
|
|
|
521
|
-
.
|
|
522
|
-
border-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
margin-bottom: 10px;
|
|
526
|
-
font-size: 13px;
|
|
527
|
-
border-radius: 0 8px 8px 0;
|
|
528
|
-
transition: all 0.3s ease;
|
|
529
|
-
animation: slideInLeft 0.3s ease-out;
|
|
481
|
+
.machine-card:hover {
|
|
482
|
+
border-color: var(--accent);
|
|
483
|
+
transform: translateY(-2px);
|
|
484
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
|
|
530
485
|
}
|
|
531
486
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
opacity: 0;
|
|
535
|
-
transform: translateX(-20px);
|
|
536
|
-
}
|
|
537
|
-
to {
|
|
538
|
-
opacity: 1;
|
|
539
|
-
transform: translateX(0);
|
|
540
|
-
}
|
|
487
|
+
.machine-card.entry-machine {
|
|
488
|
+
border-color: var(--accent);
|
|
541
489
|
}
|
|
542
490
|
|
|
543
|
-
.
|
|
544
|
-
|
|
545
|
-
|
|
491
|
+
.machine-card-header {
|
|
492
|
+
display: flex;
|
|
493
|
+
justify-content: space-between;
|
|
494
|
+
align-items: center;
|
|
495
|
+
margin-bottom: 12px;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
.machine-card-name {
|
|
499
|
+
font-weight: 600;
|
|
500
|
+
font-size: 14px;
|
|
501
|
+
color: var(--text-primary);
|
|
546
502
|
}
|
|
547
503
|
|
|
548
|
-
.
|
|
549
|
-
|
|
550
|
-
|
|
504
|
+
.machine-card-badge {
|
|
505
|
+
padding: 4px 8px;
|
|
506
|
+
background: var(--accent);
|
|
507
|
+
color: #000;
|
|
508
|
+
font-size: 10px;
|
|
509
|
+
font-weight: 600;
|
|
510
|
+
border-radius: 10px;
|
|
551
511
|
}
|
|
552
512
|
|
|
553
|
-
.
|
|
554
|
-
|
|
555
|
-
|
|
513
|
+
.machine-card-stats {
|
|
514
|
+
font-size: 11px;
|
|
515
|
+
color: var(--text-muted);
|
|
516
|
+
line-height: 1.6;
|
|
556
517
|
}
|
|
557
518
|
|
|
558
|
-
.
|
|
559
|
-
|
|
560
|
-
|
|
519
|
+
.machine-card-instances {
|
|
520
|
+
margin-top: 8px;
|
|
521
|
+
padding-top: 8px;
|
|
522
|
+
border-top: 1px solid var(--glass-border);
|
|
523
|
+
font-size: 12px;
|
|
524
|
+
color: var(--text-secondary);
|
|
561
525
|
}
|
|
562
526
|
|
|
563
|
-
.
|
|
564
|
-
|
|
565
|
-
|
|
527
|
+
.inter-machine-arrow {
|
|
528
|
+
position: absolute;
|
|
529
|
+
pointer-events: none;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
.inter-machine-label {
|
|
533
|
+
position: absolute;
|
|
534
|
+
background: var(--bg-tertiary);
|
|
535
|
+
border: 1px solid var(--inter-machine);
|
|
536
|
+
color: var(--inter-machine);
|
|
537
|
+
padding: 4px 8px;
|
|
538
|
+
border-radius: 4px;
|
|
539
|
+
font-size: 10px;
|
|
566
540
|
font-weight: 600;
|
|
541
|
+
white-space: nowrap;
|
|
567
542
|
}
|
|
568
543
|
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
544
|
+
/* History Panel */
|
|
545
|
+
.history-panel {
|
|
546
|
+
grid-column: 1 / -1;
|
|
547
|
+
background: var(--bg-secondary);
|
|
548
|
+
border-top: 1px solid var(--glass-border);
|
|
549
|
+
display: flex;
|
|
550
|
+
flex-direction: column;
|
|
551
|
+
overflow: hidden;
|
|
573
552
|
}
|
|
574
553
|
|
|
575
|
-
.
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
554
|
+
.history-header {
|
|
555
|
+
padding: 12px 24px;
|
|
556
|
+
background: var(--bg-tertiary);
|
|
557
|
+
border-bottom: 1px solid var(--glass-border);
|
|
558
|
+
display: flex;
|
|
559
|
+
justify-content: space-between;
|
|
560
|
+
align-items: center;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
.history-title {
|
|
564
|
+
font-size: 14px;
|
|
579
565
|
font-weight: 600;
|
|
580
|
-
|
|
566
|
+
display: flex;
|
|
567
|
+
align-items: center;
|
|
568
|
+
gap: 8px;
|
|
581
569
|
}
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
.
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
.
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
.
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
570
|
+
|
|
571
|
+
.history-instance-info {
|
|
572
|
+
font-size: 12px;
|
|
573
|
+
color: var(--text-muted);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
.history-actions {
|
|
577
|
+
display: flex;
|
|
578
|
+
gap: 8px;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
.history-content {
|
|
582
|
+
flex: 1;
|
|
583
|
+
overflow-x: auto;
|
|
584
|
+
overflow-y: hidden;
|
|
585
|
+
padding: 16px 24px;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
.history-timeline {
|
|
589
|
+
display: flex;
|
|
590
|
+
align-items: center;
|
|
591
|
+
gap: 0;
|
|
592
|
+
min-width: max-content;
|
|
593
|
+
height: 100%;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
.history-node {
|
|
597
|
+
display: flex;
|
|
598
|
+
flex-direction: column;
|
|
599
|
+
align-items: center;
|
|
600
|
+
min-width: 120px;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
.history-state {
|
|
604
|
+
padding: 10px 16px;
|
|
605
|
+
background: var(--bg-tertiary);
|
|
606
|
+
border: 2px solid var(--glass-border);
|
|
607
|
+
border-radius: 8px;
|
|
608
|
+
font-size: 13px;
|
|
609
|
+
font-weight: 600;
|
|
610
|
+
color: var(--text-primary);
|
|
611
|
+
transition: all 0.2s;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
.history-state.current {
|
|
615
|
+
border-color: var(--info);
|
|
616
|
+
background: rgba(59, 130, 246, 0.2);
|
|
617
|
+
color: var(--info);
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
.history-state.terminal {
|
|
621
|
+
border-color: var(--success);
|
|
622
|
+
background: rgba(16, 185, 129, 0.2);
|
|
623
|
+
color: var(--success);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
.history-time {
|
|
627
|
+
font-size: 10px;
|
|
628
|
+
color: var(--text-muted);
|
|
629
|
+
margin-top: 8px;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
.history-arrow {
|
|
633
|
+
display: flex;
|
|
634
|
+
flex-direction: column;
|
|
635
|
+
align-items: center;
|
|
636
|
+
padding: 0 8px;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
.history-arrow-line {
|
|
640
|
+
width: 40px;
|
|
641
|
+
height: 2px;
|
|
642
|
+
background: var(--accent);
|
|
643
|
+
position: relative;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
.history-arrow-line::after {
|
|
647
|
+
content: '';
|
|
648
|
+
position: absolute;
|
|
649
|
+
right: -4px;
|
|
650
|
+
top: -4px;
|
|
651
|
+
border: 5px solid transparent;
|
|
652
|
+
border-left-color: var(--accent);
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
.history-event {
|
|
656
|
+
font-size: 10px;
|
|
657
|
+
color: var(--accent);
|
|
658
|
+
margin-top: 4px;
|
|
659
|
+
max-width: 80px;
|
|
660
|
+
text-align: center;
|
|
661
|
+
word-break: break-word;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
.history-empty {
|
|
665
|
+
display: flex;
|
|
666
|
+
align-items: center;
|
|
667
|
+
justify-content: center;
|
|
668
|
+
height: 100%;
|
|
669
|
+
color: var(--text-muted);
|
|
670
|
+
font-size: 14px;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
/* Modal */
|
|
674
|
+
.modal-overlay {
|
|
675
|
+
position: fixed;
|
|
676
|
+
top: 0;
|
|
677
|
+
left: 0;
|
|
678
|
+
right: 0;
|
|
679
|
+
bottom: 0;
|
|
680
|
+
background: rgba(0, 0, 0, 0.8);
|
|
681
|
+
display: flex;
|
|
682
|
+
align-items: center;
|
|
683
|
+
justify-content: center;
|
|
684
|
+
z-index: 1000;
|
|
685
|
+
opacity: 0;
|
|
686
|
+
visibility: hidden;
|
|
687
|
+
transition: all 0.3s;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
.modal-overlay.active {
|
|
691
|
+
opacity: 1;
|
|
692
|
+
visibility: visible;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
.modal {
|
|
696
|
+
background: var(--bg-secondary);
|
|
697
|
+
border: 1px solid var(--glass-border);
|
|
698
|
+
border-radius: 16px;
|
|
699
|
+
padding: 24px;
|
|
700
|
+
width: 90%;
|
|
701
|
+
max-width: 500px;
|
|
702
|
+
transform: translateY(20px);
|
|
703
|
+
transition: transform 0.3s;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
.modal-overlay.active .modal {
|
|
707
|
+
transform: translateY(0);
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
.modal-header {
|
|
711
|
+
display: flex;
|
|
712
|
+
justify-content: space-between;
|
|
713
|
+
align-items: center;
|
|
714
|
+
margin-bottom: 20px;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
.modal-title {
|
|
718
|
+
font-size: 18px;
|
|
719
|
+
font-weight: 600;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
.modal-close {
|
|
723
|
+
background: none;
|
|
724
|
+
border: none;
|
|
725
|
+
color: var(--text-muted);
|
|
726
|
+
font-size: 24px;
|
|
727
|
+
cursor: pointer;
|
|
728
|
+
padding: 4px;
|
|
729
|
+
line-height: 1;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
.modal-close:hover {
|
|
733
|
+
color: var(--text-primary);
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
.form-group {
|
|
737
|
+
margin-bottom: 16px;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
.form-group label {
|
|
741
|
+
display: block;
|
|
742
|
+
font-size: 13px;
|
|
743
|
+
font-weight: 500;
|
|
744
|
+
color: var(--text-secondary);
|
|
745
|
+
margin-bottom: 6px;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
.form-group input,
|
|
749
|
+
.form-group textarea {
|
|
750
|
+
width: 100%;
|
|
751
|
+
padding: 10px 12px;
|
|
752
|
+
background: var(--bg-tertiary);
|
|
753
|
+
border: 1px solid var(--glass-border);
|
|
754
|
+
border-radius: 8px;
|
|
755
|
+
color: var(--text-primary);
|
|
756
|
+
font-size: 14px;
|
|
757
|
+
font-family: inherit;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
.form-group input:focus,
|
|
761
|
+
.form-group textarea:focus {
|
|
762
|
+
outline: none;
|
|
763
|
+
border-color: var(--accent);
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
.form-group textarea {
|
|
767
|
+
resize: vertical;
|
|
768
|
+
min-height: 100px;
|
|
769
|
+
font-family: 'Monaco', 'Consolas', monospace;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
.form-help {
|
|
773
|
+
font-size: 11px;
|
|
774
|
+
color: var(--text-muted);
|
|
775
|
+
margin-top: 4px;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
.modal-actions {
|
|
779
|
+
display: flex;
|
|
780
|
+
gap: 12px;
|
|
781
|
+
justify-content: flex-end;
|
|
782
|
+
margin-top: 24px;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
.btn {
|
|
786
|
+
padding: 10px 20px;
|
|
787
|
+
border: none;
|
|
788
|
+
border-radius: 8px;
|
|
789
|
+
font-size: 14px;
|
|
790
|
+
font-weight: 600;
|
|
791
|
+
cursor: pointer;
|
|
792
|
+
transition: all 0.2s;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
.btn-primary {
|
|
796
|
+
background: var(--accent);
|
|
797
|
+
color: #000;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
.btn-primary:hover {
|
|
801
|
+
background: var(--accent-hover);
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
.btn-secondary {
|
|
805
|
+
background: var(--bg-tertiary);
|
|
806
|
+
color: var(--text-primary);
|
|
807
|
+
border: 1px solid var(--glass-border);
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
.btn-secondary:hover {
|
|
811
|
+
background: var(--bg-primary);
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
/* Scrollbar */
|
|
815
|
+
::-webkit-scrollbar {
|
|
816
|
+
width: 8px;
|
|
817
|
+
height: 8px;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
::-webkit-scrollbar-track {
|
|
821
|
+
background: var(--bg-primary);
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
::-webkit-scrollbar-thumb {
|
|
825
|
+
background: var(--glass-border);
|
|
826
|
+
border-radius: 4px;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
::-webkit-scrollbar-thumb:hover {
|
|
830
|
+
background: var(--text-muted);
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
/* Toast notifications */
|
|
834
|
+
.toast-container {
|
|
835
|
+
position: fixed;
|
|
836
|
+
bottom: 24px;
|
|
837
|
+
right: 24px;
|
|
838
|
+
z-index: 2000;
|
|
839
|
+
display: flex;
|
|
840
|
+
flex-direction: column;
|
|
841
|
+
gap: 8px;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
.toast {
|
|
845
|
+
padding: 12px 16px;
|
|
846
|
+
background: var(--bg-tertiary);
|
|
847
|
+
border: 1px solid var(--glass-border);
|
|
848
|
+
border-radius: 8px;
|
|
849
|
+
color: var(--text-primary);
|
|
850
|
+
font-size: 13px;
|
|
851
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
|
852
|
+
animation: slideIn 0.3s ease;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
.toast.success {
|
|
856
|
+
border-color: var(--success);
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
.toast.error {
|
|
860
|
+
border-color: var(--error);
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
@keyframes slideIn {
|
|
864
|
+
from {
|
|
865
|
+
transform: translateX(100%);
|
|
866
|
+
opacity: 0;
|
|
867
|
+
}
|
|
868
|
+
to {
|
|
869
|
+
transform: translateX(0);
|
|
870
|
+
opacity: 1;
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
.hidden {
|
|
875
|
+
display: none !important;
|
|
876
|
+
}
|
|
877
|
+
</style>
|
|
608
878
|
</head>
|
|
609
879
|
<body>
|
|
610
|
-
<div class="
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
<div class="
|
|
880
|
+
<div class="app-layout">
|
|
881
|
+
<!-- Header -->
|
|
882
|
+
<header class="header">
|
|
883
|
+
<div class="header-left">
|
|
884
|
+
<div class="logo">xcomponent<span>-ai</span></div>
|
|
885
|
+
<div class="connection-status">
|
|
615
886
|
<span class="status-dot" id="ws-status"></span>
|
|
616
|
-
<span id="
|
|
887
|
+
<span id="connection-text">Connecting...</span>
|
|
617
888
|
</div>
|
|
618
889
|
</div>
|
|
619
|
-
<div class="header-
|
|
620
|
-
<
|
|
621
|
-
|
|
890
|
+
<div class="header-center">
|
|
891
|
+
<div class="view-toggle">
|
|
892
|
+
<button class="view-toggle-btn active" id="btn-view-machine" onclick="setView('machine')">
|
|
893
|
+
Machine View
|
|
894
|
+
</button>
|
|
895
|
+
<button class="view-toggle-btn" id="btn-view-component" onclick="setView('component')">
|
|
896
|
+
Component View
|
|
897
|
+
</button>
|
|
898
|
+
</div>
|
|
622
899
|
</div>
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
<
|
|
641
|
-
<div class="
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
</div>
|
|
646
|
-
|
|
647
|
-
<div class="tab-content" id="tab-overview">
|
|
648
|
-
<div class="filter-bar">
|
|
649
|
-
<input type="text" id="filter-instance" placeholder="Filter by instance ID..." oninput="filterInstances()">
|
|
650
|
-
<select id="filter-machine" onchange="filterInstances()">
|
|
651
|
-
<option value="">All Machines</option>
|
|
900
|
+
<div class="header-stats">
|
|
901
|
+
<div class="stat-item">
|
|
902
|
+
<div class="stat-value" id="stat-instances">0</div>
|
|
903
|
+
<div class="stat-label">Instances</div>
|
|
904
|
+
</div>
|
|
905
|
+
<div class="stat-item">
|
|
906
|
+
<div class="stat-value" id="stat-active">0</div>
|
|
907
|
+
<div class="stat-label">Active</div>
|
|
908
|
+
</div>
|
|
909
|
+
<div class="stat-item">
|
|
910
|
+
<div class="stat-value" id="stat-terminal">0</div>
|
|
911
|
+
<div class="stat-label">Terminal</div>
|
|
912
|
+
</div>
|
|
913
|
+
</div>
|
|
914
|
+
</header>
|
|
915
|
+
|
|
916
|
+
<!-- Sidebar -->
|
|
917
|
+
<aside class="sidebar">
|
|
918
|
+
<div class="sidebar-section">
|
|
919
|
+
<div class="sidebar-section-title">Component</div>
|
|
920
|
+
<select class="selector" id="component-select" onchange="selectComponent()">
|
|
921
|
+
<option value="">Loading...</option>
|
|
652
922
|
</select>
|
|
653
|
-
|
|
654
|
-
|
|
923
|
+
</div>
|
|
924
|
+
|
|
925
|
+
<div class="sidebar-section" id="machine-selector-section">
|
|
926
|
+
<div class="sidebar-section-title">State Machine</div>
|
|
927
|
+
<select class="selector" id="machine-select" onchange="selectMachine()">
|
|
928
|
+
<option value="">Select a machine...</option>
|
|
655
929
|
</select>
|
|
656
930
|
</div>
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
931
|
+
|
|
932
|
+
<!-- Transitions Panel -->
|
|
933
|
+
<div class="transitions-panel" id="transitions-panel">
|
|
934
|
+
<div class="sidebar-section-title">Available Transitions</div>
|
|
935
|
+
<div id="transitions-list">
|
|
936
|
+
<div class="empty-state" style="padding: 20px 0;">
|
|
937
|
+
<div style="font-size: 12px;">Select an instance to see transitions</div>
|
|
938
|
+
</div>
|
|
661
939
|
</div>
|
|
662
940
|
</div>
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
941
|
+
|
|
942
|
+
<div class="instance-list-container">
|
|
943
|
+
<div class="instance-list-header">
|
|
944
|
+
<div class="sidebar-section-title" style="margin: 0;">Instances</div>
|
|
945
|
+
<button class="btn-create" id="btn-create-instance" onclick="openCreateModal()">+ New</button>
|
|
946
|
+
</div>
|
|
947
|
+
<div class="instance-count" id="instance-count">0 instances</div>
|
|
948
|
+
<div id="instance-list">
|
|
949
|
+
<div class="empty-state">
|
|
950
|
+
<div class="empty-state-icon">📦</div>
|
|
951
|
+
<div>Select a machine to see instances</div>
|
|
952
|
+
</div>
|
|
953
|
+
</div>
|
|
954
|
+
</div>
|
|
955
|
+
</aside>
|
|
956
|
+
|
|
957
|
+
<!-- Main Content -->
|
|
958
|
+
<main class="main-content">
|
|
959
|
+
<!-- Machine View -->
|
|
960
|
+
<div id="machine-view">
|
|
961
|
+
<div class="diagram-header">
|
|
962
|
+
<div class="diagram-title" id="diagram-title">
|
|
963
|
+
Select a state machine to view its diagram
|
|
964
|
+
</div>
|
|
965
|
+
<div class="diagram-legend">
|
|
966
|
+
<div class="legend-item">
|
|
967
|
+
<div class="legend-dot entry"></div>
|
|
968
|
+
<span>Entry</span>
|
|
969
|
+
</div>
|
|
970
|
+
<div class="legend-item">
|
|
971
|
+
<div class="legend-dot current"></div>
|
|
972
|
+
<span>Current</span>
|
|
973
|
+
</div>
|
|
974
|
+
<div class="legend-item">
|
|
975
|
+
<div class="legend-dot terminal"></div>
|
|
976
|
+
<span>Terminal</span>
|
|
977
|
+
</div>
|
|
978
|
+
<div class="legend-item">
|
|
979
|
+
<div class="legend-dot error"></div>
|
|
980
|
+
<span>Error</span>
|
|
981
|
+
</div>
|
|
982
|
+
<div class="legend-item">
|
|
983
|
+
<div class="legend-line inter-machine"></div>
|
|
984
|
+
<span>Inter-machine</span>
|
|
985
|
+
</div>
|
|
673
986
|
</div>
|
|
674
|
-
|
|
987
|
+
</div>
|
|
988
|
+
<div class="diagram-container">
|
|
989
|
+
<div class="mermaid-wrapper" id="diagram-container">
|
|
675
990
|
<div class="empty-state">
|
|
676
|
-
<div class="empty-state-icon"
|
|
991
|
+
<div class="empty-state-icon">📊</div>
|
|
677
992
|
<div>Select a state machine to view its diagram</div>
|
|
678
993
|
</div>
|
|
679
994
|
</div>
|
|
680
995
|
</div>
|
|
996
|
+
</div>
|
|
681
997
|
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
998
|
+
<!-- Component View -->
|
|
999
|
+
<div id="component-view" class="hidden">
|
|
1000
|
+
<div class="diagram-header">
|
|
1001
|
+
<div class="diagram-title" id="component-title">
|
|
1002
|
+
Component Overview
|
|
1003
|
+
</div>
|
|
1004
|
+
<div class="diagram-legend">
|
|
1005
|
+
<div class="legend-item">
|
|
1006
|
+
<div class="legend-dot entry"></div>
|
|
1007
|
+
<span>Entry Machine</span>
|
|
1008
|
+
</div>
|
|
1009
|
+
<div class="legend-item">
|
|
1010
|
+
<div class="legend-line inter-machine"></div>
|
|
1011
|
+
<span>Inter-machine Transition</span>
|
|
690
1012
|
</div>
|
|
691
1013
|
</div>
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
<div
|
|
696
|
-
<div class="empty-state"
|
|
697
|
-
|
|
698
|
-
<div>No instances for this state machine</div>
|
|
699
|
-
</div>
|
|
1014
|
+
</div>
|
|
1015
|
+
<div class="component-view">
|
|
1016
|
+
<div class="component-graph" id="component-graph">
|
|
1017
|
+
<div class="empty-state">
|
|
1018
|
+
<div class="empty-state-icon">🏗</div>
|
|
1019
|
+
<div>Select a component to view its machines</div>
|
|
700
1020
|
</div>
|
|
701
1021
|
</div>
|
|
702
1022
|
</div>
|
|
703
1023
|
</div>
|
|
704
|
-
</
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
<
|
|
710
|
-
|
|
711
|
-
<
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
<
|
|
1024
|
+
</main>
|
|
1025
|
+
|
|
1026
|
+
<!-- History Panel -->
|
|
1027
|
+
<section class="history-panel">
|
|
1028
|
+
<div class="history-header">
|
|
1029
|
+
<div class="history-title">
|
|
1030
|
+
Transition History
|
|
1031
|
+
<span class="history-instance-info" id="history-instance-info"></span>
|
|
1032
|
+
</div>
|
|
1033
|
+
<div class="history-actions" id="history-actions"></div>
|
|
1034
|
+
</div>
|
|
1035
|
+
<div class="history-content">
|
|
1036
|
+
<div class="history-timeline" id="history-timeline">
|
|
1037
|
+
<div class="history-empty">Select an instance to view its transition history</div>
|
|
1038
|
+
</div>
|
|
717
1039
|
</div>
|
|
718
|
-
|
|
719
|
-
|
|
1040
|
+
</section>
|
|
1041
|
+
</div>
|
|
1042
|
+
|
|
1043
|
+
<!-- Create Instance Modal -->
|
|
1044
|
+
<div class="modal-overlay" id="create-modal">
|
|
1045
|
+
<div class="modal">
|
|
1046
|
+
<div class="modal-header">
|
|
1047
|
+
<div class="modal-title">Create New Instance</div>
|
|
1048
|
+
<button class="modal-close" onclick="closeCreateModal()">×</button>
|
|
1049
|
+
</div>
|
|
1050
|
+
<div id="create-form-content">
|
|
1051
|
+
<div class="form-group">
|
|
1052
|
+
<label>Context (JSON)</label>
|
|
1053
|
+
<textarea id="create-context" placeholder='{"property": "value"}'></textarea>
|
|
1054
|
+
<div class="form-help">Enter a JSON object with context properties</div>
|
|
1055
|
+
</div>
|
|
1056
|
+
</div>
|
|
1057
|
+
<div class="modal-actions">
|
|
1058
|
+
<button class="btn btn-secondary" onclick="closeCreateModal()">Cancel</button>
|
|
1059
|
+
<button class="btn btn-primary" onclick="createInstance()">Create</button>
|
|
720
1060
|
</div>
|
|
721
1061
|
</div>
|
|
722
|
-
|
|
723
|
-
|
|
1062
|
+
</div>
|
|
1063
|
+
|
|
1064
|
+
<!-- Send Event Modal -->
|
|
1065
|
+
<div class="modal-overlay" id="event-modal">
|
|
1066
|
+
<div class="modal">
|
|
1067
|
+
<div class="modal-header">
|
|
1068
|
+
<div class="modal-title">Send Event</div>
|
|
1069
|
+
<button class="modal-close" onclick="closeEventModal()">×</button>
|
|
1070
|
+
</div>
|
|
724
1071
|
<div class="form-group">
|
|
725
|
-
<label
|
|
726
|
-
<
|
|
727
|
-
<option value="">Select an instance...</option>
|
|
728
|
-
</select>
|
|
1072
|
+
<label>Event Type</label>
|
|
1073
|
+
<input type="text" id="event-type" placeholder="event_name">
|
|
729
1074
|
</div>
|
|
730
|
-
<div class="
|
|
731
|
-
<
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
1075
|
+
<div class="form-group">
|
|
1076
|
+
<label>Payload (JSON)</label>
|
|
1077
|
+
<textarea id="event-payload" placeholder='{"key": "value"}'></textarea>
|
|
1078
|
+
<div class="form-help">Optional JSON payload for the event</div>
|
|
1079
|
+
</div>
|
|
1080
|
+
<div class="modal-actions">
|
|
1081
|
+
<button class="btn btn-secondary" onclick="closeEventModal()">Cancel</button>
|
|
1082
|
+
<button class="btn btn-primary" onclick="sendEvent()">Send</button>
|
|
735
1083
|
</div>
|
|
736
1084
|
</div>
|
|
737
|
-
|
|
738
1085
|
</div>
|
|
739
|
-
|
|
1086
|
+
|
|
1087
|
+
<!-- Toast Container -->
|
|
1088
|
+
<div class="toast-container" id="toast-container"></div>
|
|
1089
|
+
|
|
740
1090
|
<script>
|
|
1091
|
+
// State
|
|
741
1092
|
const socket = io();
|
|
742
1093
|
let componentsData = [];
|
|
743
1094
|
let selectedComponentName = null;
|
|
1095
|
+
let selectedMachineName = null;
|
|
1096
|
+
let selectedInstanceId = null;
|
|
744
1097
|
let instances = [];
|
|
745
|
-
let
|
|
746
|
-
let
|
|
747
|
-
|
|
748
|
-
//
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
1098
|
+
let allInstances = [];
|
|
1099
|
+
let terminalStates = new Set();
|
|
1100
|
+
let currentTransitions = [];
|
|
1101
|
+
let currentView = 'machine'; // 'machine' or 'component'
|
|
1102
|
+
|
|
1103
|
+
// Initialize Mermaid
|
|
1104
|
+
mermaid.initialize({
|
|
1105
|
+
startOnLoad: false,
|
|
1106
|
+
theme: 'default',
|
|
1107
|
+
securityLevel: 'loose'
|
|
1108
|
+
});
|
|
755
1109
|
|
|
756
|
-
// WebSocket
|
|
1110
|
+
// WebSocket Events
|
|
757
1111
|
socket.on('connect', () => {
|
|
758
1112
|
document.getElementById('ws-status').className = 'status-dot connected';
|
|
1113
|
+
document.getElementById('connection-text').textContent = 'Connected';
|
|
759
1114
|
});
|
|
760
1115
|
|
|
761
1116
|
socket.on('disconnect', () => {
|
|
762
|
-
document.getElementById('ws-status').className = 'status-dot
|
|
1117
|
+
document.getElementById('ws-status').className = 'status-dot';
|
|
1118
|
+
document.getElementById('connection-text').textContent = 'Disconnected';
|
|
763
1119
|
});
|
|
764
1120
|
|
|
765
1121
|
socket.on('components_data', (data) => {
|
|
766
1122
|
componentsData = data.components || [];
|
|
767
1123
|
if (componentsData.length > 0) {
|
|
768
1124
|
selectedComponentName = componentsData[0].name;
|
|
769
|
-
|
|
770
|
-
|
|
1125
|
+
populateComponentSelector();
|
|
1126
|
+
populateMachineSelector();
|
|
1127
|
+
if (currentView === 'component') {
|
|
1128
|
+
renderComponentView();
|
|
1129
|
+
}
|
|
771
1130
|
}
|
|
772
1131
|
});
|
|
773
|
-
|
|
774
|
-
socket.on('instance_created', (
|
|
775
|
-
addEvent({type: 'created', timestamp: Date.now(), message: `Instance ${data.instanceId} created (${data.machineName})`, instanceId: data.instanceId});
|
|
1132
|
+
|
|
1133
|
+
socket.on('instance_created', () => {
|
|
776
1134
|
loadInstances();
|
|
1135
|
+
showToast('Instance created', 'success');
|
|
777
1136
|
});
|
|
778
|
-
|
|
1137
|
+
|
|
779
1138
|
socket.on('state_change', (data) => {
|
|
780
|
-
addEvent({type: 'state-change', timestamp: Date.now(), message: `${data.instanceId}: ${data.previousState} → ${data.newState} (${data.event.type})`, instanceId: data.instanceId, source: data.componentName});
|
|
781
1139
|
loadInstances();
|
|
782
|
-
if (
|
|
1140
|
+
if (selectedInstanceId === data.instanceId) {
|
|
1141
|
+
loadInstanceHistory(selectedInstanceId);
|
|
1142
|
+
renderDiagram();
|
|
1143
|
+
loadTransitionsForInstance(selectedInstanceId);
|
|
1144
|
+
}
|
|
783
1145
|
});
|
|
784
|
-
|
|
1146
|
+
|
|
785
1147
|
socket.on('instance_error', (data) => {
|
|
786
|
-
addEvent({type: 'error', timestamp: Date.now(), message: `ERROR in ${data.instanceId}: ${data.error}`, instanceId: data.instanceId});
|
|
787
1148
|
loadInstances();
|
|
1149
|
+
showToast(`Error: ${data.error}`, 'error');
|
|
788
1150
|
});
|
|
789
|
-
|
|
790
|
-
// Tab Switching
|
|
791
|
-
function switchTab(tabName) {
|
|
792
|
-
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
|
|
793
|
-
document.querySelectorAll('.tab-content').forEach(t => t.classList.remove('active'));
|
|
794
|
-
|
|
795
|
-
// Find the tab element by matching the onclick content
|
|
796
|
-
document.querySelectorAll('.tab').forEach(tab => {
|
|
797
|
-
if (tab.getAttribute('onclick') === `switchTab('${tabName}')`) {
|
|
798
|
-
tab.classList.add('active');
|
|
799
|
-
}
|
|
800
|
-
});
|
|
801
1151
|
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
// Load Instances
|
|
806
|
-
async function loadInstances() {
|
|
807
|
-
const res = await fetch('/api/instances');
|
|
808
|
-
const data = await res.json();
|
|
809
|
-
instances = data.instances || [];
|
|
810
|
-
updateStats();
|
|
811
|
-
renderInstances();
|
|
812
|
-
updateTraceSelector();
|
|
1152
|
+
socket.on('instance_disposed', () => {
|
|
1153
|
+
loadInstances();
|
|
1154
|
+
});
|
|
813
1155
|
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
1156
|
+
// View Toggle
|
|
1157
|
+
function setView(view) {
|
|
1158
|
+
currentView = view;
|
|
1159
|
+
document.getElementById('btn-view-machine').classList.toggle('active', view === 'machine');
|
|
1160
|
+
document.getElementById('btn-view-component').classList.toggle('active', view === 'component');
|
|
1161
|
+
document.getElementById('machine-view').classList.toggle('hidden', view !== 'machine');
|
|
1162
|
+
document.getElementById('component-view').classList.toggle('hidden', view !== 'component');
|
|
1163
|
+
document.getElementById('machine-selector-section').classList.toggle('hidden', view === 'component');
|
|
1164
|
+
|
|
1165
|
+
if (view === 'component') {
|
|
1166
|
+
renderComponentView();
|
|
1167
|
+
} else if (selectedMachineName) {
|
|
1168
|
+
renderDiagram();
|
|
818
1169
|
}
|
|
819
1170
|
}
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
1171
|
+
|
|
1172
|
+
// Component/Machine Selection
|
|
1173
|
+
function getCurrentComponent() {
|
|
1174
|
+
return componentsData.find(c => c.name === selectedComponentName) || componentsData[0];
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
function getCurrentMachine() {
|
|
1178
|
+
const component = getCurrentComponent();
|
|
1179
|
+
if (!component || !selectedMachineName) return null;
|
|
1180
|
+
return component.stateMachines.find(m => m.name === selectedMachineName);
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
function populateComponentSelector() {
|
|
1184
|
+
const select = document.getElementById('component-select');
|
|
1185
|
+
select.innerHTML = componentsData.map(c =>
|
|
1186
|
+
`<option value="${c.name}" ${c.name === selectedComponentName ? 'selected' : ''}>${c.name}</option>`
|
|
1187
|
+
).join('');
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
function populateMachineSelector() {
|
|
1191
|
+
const component = getCurrentComponent();
|
|
1192
|
+
const select = document.getElementById('machine-select');
|
|
1193
|
+
|
|
1194
|
+
if (!component || !component.stateMachines) {
|
|
1195
|
+
select.innerHTML = '<option value="">No machines available</option>';
|
|
833
1196
|
return;
|
|
834
1197
|
}
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
<span class="badge ${inst.status}">${inst.currentState}</span>
|
|
841
|
-
</div>
|
|
842
|
-
<div class="instance-meta">
|
|
843
|
-
Machine: ${inst.machineName}<br>
|
|
844
|
-
Status: ${inst.status}
|
|
845
|
-
</div>
|
|
846
|
-
</div>
|
|
847
|
-
`).join('');
|
|
1198
|
+
|
|
1199
|
+
select.innerHTML = '<option value="">Select a machine...</option>' +
|
|
1200
|
+
component.stateMachines.map(m =>
|
|
1201
|
+
`<option value="${m.name}">${m.name}${component.entryMachine === m.name ? ' (entry)' : ''}</option>`
|
|
1202
|
+
).join('');
|
|
848
1203
|
}
|
|
849
|
-
|
|
850
|
-
function
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
const machine = item.querySelector('.instance-meta').textContent.includes(machineFilter);
|
|
865
|
-
const state = item.querySelector('.badge').textContent.includes(stateFilter);
|
|
866
|
-
|
|
867
|
-
const show = id.includes(idFilter) && (!machineFilter || machine) && (!stateFilter || state);
|
|
868
|
-
item.style.display = show ? '' : 'none';
|
|
869
|
-
});
|
|
1204
|
+
|
|
1205
|
+
function selectComponent() {
|
|
1206
|
+
selectedComponentName = document.getElementById('component-select').value;
|
|
1207
|
+
selectedMachineName = null;
|
|
1208
|
+
selectedInstanceId = null;
|
|
1209
|
+
populateMachineSelector();
|
|
1210
|
+
clearDiagram();
|
|
1211
|
+
clearHistory();
|
|
1212
|
+
clearInstanceList();
|
|
1213
|
+
clearTransitions();
|
|
1214
|
+
loadInstances();
|
|
1215
|
+
|
|
1216
|
+
if (currentView === 'component') {
|
|
1217
|
+
renderComponentView();
|
|
1218
|
+
}
|
|
870
1219
|
}
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
1220
|
+
|
|
1221
|
+
function selectMachine() {
|
|
1222
|
+
selectedMachineName = document.getElementById('machine-select').value;
|
|
1223
|
+
selectedInstanceId = null;
|
|
1224
|
+
|
|
1225
|
+
if (selectedMachineName) {
|
|
1226
|
+
renderDiagram();
|
|
1227
|
+
loadInstances();
|
|
1228
|
+
} else {
|
|
1229
|
+
clearDiagram();
|
|
1230
|
+
clearInstanceList();
|
|
1231
|
+
}
|
|
1232
|
+
clearHistory();
|
|
1233
|
+
clearTransitions();
|
|
878
1234
|
}
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
1235
|
+
|
|
1236
|
+
// Component View - Show all machines with inter-machine connections
|
|
1237
|
+
function renderComponentView() {
|
|
1238
|
+
const component = getCurrentComponent();
|
|
1239
|
+
if (!component) {
|
|
1240
|
+
document.getElementById('component-graph').innerHTML = `
|
|
1241
|
+
<div class="empty-state">
|
|
1242
|
+
<div class="empty-state-icon">🏗</div>
|
|
1243
|
+
<div>No component loaded</div>
|
|
1244
|
+
</div>`;
|
|
884
1245
|
return;
|
|
885
1246
|
}
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
1247
|
+
|
|
1248
|
+
document.getElementById('component-title').innerHTML = `<span>${component.name}</span> - Component Overview`;
|
|
1249
|
+
|
|
1250
|
+
const machines = component.stateMachines || [];
|
|
1251
|
+
const container = document.getElementById('component-graph');
|
|
1252
|
+
|
|
1253
|
+
// Collect inter-machine transitions
|
|
1254
|
+
const interMachineLinks = [];
|
|
1255
|
+
machines.forEach(machine => {
|
|
1256
|
+
(machine.transitions || []).forEach(transition => {
|
|
1257
|
+
if (transition.type === 'inter_machine' && transition.targetMachine) {
|
|
1258
|
+
interMachineLinks.push({
|
|
1259
|
+
from: machine.name,
|
|
1260
|
+
to: transition.targetMachine,
|
|
1261
|
+
event: transition.event,
|
|
1262
|
+
fromState: transition.from
|
|
1263
|
+
});
|
|
1264
|
+
}
|
|
1265
|
+
});
|
|
1266
|
+
});
|
|
1267
|
+
|
|
1268
|
+
// Layout machines in a grid
|
|
1269
|
+
const cols = Math.ceil(Math.sqrt(machines.length));
|
|
1270
|
+
const cardWidth = 200;
|
|
1271
|
+
const cardHeight = 140;
|
|
1272
|
+
const spacingX = 120;
|
|
1273
|
+
const spacingY = 100;
|
|
1274
|
+
|
|
1275
|
+
// Calculate positions
|
|
1276
|
+
const positions = {};
|
|
1277
|
+
machines.forEach((machine, idx) => {
|
|
1278
|
+
const row = Math.floor(idx / cols);
|
|
1279
|
+
const col = idx % cols;
|
|
1280
|
+
positions[machine.name] = {
|
|
1281
|
+
x: col * (cardWidth + spacingX) + 40,
|
|
1282
|
+
y: row * (cardHeight + spacingY) + 40
|
|
1283
|
+
};
|
|
1284
|
+
});
|
|
1285
|
+
|
|
1286
|
+
// Build SVG for arrows
|
|
1287
|
+
const svgWidth = cols * (cardWidth + spacingX) + 100;
|
|
1288
|
+
const svgHeight = Math.ceil(machines.length / cols) * (cardHeight + spacingY) + 100;
|
|
1289
|
+
|
|
1290
|
+
let svgHtml = `<svg width="${svgWidth}" height="${svgHeight}" style="position: absolute; top: 0; left: 0; pointer-events: none; overflow: visible;">
|
|
1291
|
+
<defs>
|
|
1292
|
+
<marker id="arrowhead" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto">
|
|
1293
|
+
<polygon points="0 0, 10 3, 0 6" fill="#10b981" />
|
|
1294
|
+
</marker>
|
|
1295
|
+
</defs>`;
|
|
1296
|
+
|
|
1297
|
+
// Draw inter-machine arrows
|
|
1298
|
+
interMachineLinks.forEach((link, idx) => {
|
|
1299
|
+
const fromPos = positions[link.from];
|
|
1300
|
+
const toPos = positions[link.to];
|
|
1301
|
+
if (fromPos && toPos) {
|
|
1302
|
+
const x1 = fromPos.x + cardWidth / 2;
|
|
1303
|
+
const y1 = fromPos.y + cardHeight / 2;
|
|
1304
|
+
const x2 = toPos.x + cardWidth / 2;
|
|
1305
|
+
const y2 = toPos.y + cardHeight / 2;
|
|
1306
|
+
|
|
1307
|
+
// Calculate curve control point
|
|
1308
|
+
const midX = (x1 + x2) / 2;
|
|
1309
|
+
const midY = (y1 + y2) / 2;
|
|
1310
|
+
const offset = 30;
|
|
1311
|
+
|
|
1312
|
+
svgHtml += `
|
|
1313
|
+
<path d="M ${x1} ${y1} Q ${midX} ${midY - offset} ${x2} ${y2}"
|
|
1314
|
+
fill="none" stroke="#10b981" stroke-width="3"
|
|
1315
|
+
marker-end="url(#arrowhead)" />
|
|
1316
|
+
<text x="${midX}" y="${midY - offset - 8}" fill="#10b981" font-size="11"
|
|
1317
|
+
text-anchor="middle" font-weight="600">${link.event}</text>`;
|
|
1318
|
+
}
|
|
904
1319
|
});
|
|
1320
|
+
|
|
1321
|
+
svgHtml += '</svg>';
|
|
1322
|
+
|
|
1323
|
+
// Build machine cards
|
|
1324
|
+
let cardsHtml = svgHtml;
|
|
1325
|
+
machines.forEach(machine => {
|
|
1326
|
+
const pos = positions[machine.name];
|
|
1327
|
+
const isEntry = component.entryMachine === machine.name;
|
|
1328
|
+
const machineInstances = allInstances.filter(i => i.machineName === machine.name);
|
|
1329
|
+
|
|
1330
|
+
cardsHtml += `
|
|
1331
|
+
<div class="machine-card ${isEntry ? 'entry-machine' : ''}"
|
|
1332
|
+
style="left: ${pos.x}px; top: ${pos.y}px; width: ${cardWidth}px;"
|
|
1333
|
+
onclick="selectMachineFromComponent('${machine.name}')">
|
|
1334
|
+
<div class="machine-card-header">
|
|
1335
|
+
<span class="machine-card-name">${machine.name}</span>
|
|
1336
|
+
${isEntry ? '<span class="machine-card-badge">Entry</span>' : ''}
|
|
1337
|
+
</div>
|
|
1338
|
+
<div class="machine-card-stats">
|
|
1339
|
+
${machine.states?.length || 0} states<br>
|
|
1340
|
+
${machine.transitions?.length || 0} transitions
|
|
1341
|
+
</div>
|
|
1342
|
+
<div class="machine-card-instances">
|
|
1343
|
+
<strong>${machineInstances.length}</strong> instance${machineInstances.length !== 1 ? 's' : ''}
|
|
1344
|
+
</div>
|
|
1345
|
+
</div>`;
|
|
1346
|
+
});
|
|
1347
|
+
|
|
1348
|
+
container.innerHTML = cardsHtml;
|
|
1349
|
+
container.style.minHeight = `${svgHeight}px`;
|
|
905
1350
|
}
|
|
906
|
-
|
|
907
|
-
function
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
1351
|
+
|
|
1352
|
+
function selectMachineFromComponent(machineName) {
|
|
1353
|
+
selectedMachineName = machineName;
|
|
1354
|
+
document.getElementById('machine-select').value = machineName;
|
|
1355
|
+
setView('machine');
|
|
1356
|
+
renderDiagram();
|
|
1357
|
+
loadInstances();
|
|
911
1358
|
}
|
|
912
|
-
|
|
913
|
-
// Diagram
|
|
1359
|
+
|
|
1360
|
+
// Diagram Rendering
|
|
914
1361
|
async function renderDiagram() {
|
|
915
|
-
const machineName = document.getElementById('diagram-machine').value;
|
|
916
1362
|
const component = getCurrentComponent();
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
1363
|
+
const machine = getCurrentMachine();
|
|
1364
|
+
|
|
1365
|
+
if (!component || !machine) return;
|
|
1366
|
+
|
|
1367
|
+
const container = document.getElementById('diagram-container');
|
|
1368
|
+
document.getElementById('diagram-title').innerHTML =
|
|
1369
|
+
`<span>${component.name}</span> / ${machine.name}`;
|
|
1370
|
+
|
|
1371
|
+
// Get current state if an instance is selected
|
|
1372
|
+
let currentState = null;
|
|
1373
|
+
if (selectedInstanceId) {
|
|
1374
|
+
const instance = instances.find(i => i.id === selectedInstanceId);
|
|
1375
|
+
if (instance) {
|
|
1376
|
+
currentState = instance.currentState;
|
|
1377
|
+
}
|
|
921
1378
|
}
|
|
922
1379
|
|
|
923
|
-
|
|
924
|
-
|
|
1380
|
+
// Fetch diagram with current state highlighting
|
|
1381
|
+
const url = `/api/components/${component.name}/diagrams/${machine.name}` +
|
|
1382
|
+
(currentState ? `?currentState=${encodeURIComponent(currentState)}` : '');
|
|
925
1383
|
|
|
926
|
-
|
|
927
|
-
const res = await fetch(`/api/components/${component.name}/diagrams/${machineName}`);
|
|
1384
|
+
const res = await fetch(url);
|
|
928
1385
|
const data = await res.json();
|
|
929
1386
|
|
|
930
1387
|
if (data.diagram) {
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
const container = document.getElementById('diagram-container');
|
|
934
|
-
container.innerHTML = '<div class="mermaid">' + data.diagram + '</div>';
|
|
1388
|
+
terminalStates = new Set(data.terminalStates || []);
|
|
1389
|
+
currentTransitions = data.transitions || [];
|
|
935
1390
|
|
|
936
|
-
|
|
1391
|
+
container.innerHTML = `<div class="mermaid">${data.diagram}</div>`;
|
|
1392
|
+
|
|
1393
|
+
try {
|
|
937
1394
|
await mermaid.run({
|
|
938
1395
|
querySelector: '#diagram-container .mermaid'
|
|
939
1396
|
});
|
|
940
1397
|
} catch (error) {
|
|
941
|
-
console.error('Mermaid
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
<div
|
|
945
|
-
|
|
946
|
-
<details style="margin-top: 10px;">
|
|
947
|
-
<summary style="cursor: pointer;">Show diagram source</summary>
|
|
948
|
-
<pre style="margin-top: 10px; padding: 10px; background: rgba(0,0,0,0.3); border-radius: 4px; overflow-x: auto;">${data.diagram}</pre>
|
|
949
|
-
</details>
|
|
950
|
-
</div>
|
|
951
|
-
`;
|
|
1398
|
+
console.error('Mermaid error:', error);
|
|
1399
|
+
container.innerHTML = `<div class="empty-state">
|
|
1400
|
+
<div class="empty-state-icon">⚠</div>
|
|
1401
|
+
<div>Error rendering diagram</div>
|
|
1402
|
+
</div>`;
|
|
952
1403
|
}
|
|
953
1404
|
}
|
|
1405
|
+
}
|
|
954
1406
|
|
|
955
|
-
|
|
956
|
-
|
|
1407
|
+
function clearDiagram() {
|
|
1408
|
+
document.getElementById('diagram-container').innerHTML = `
|
|
1409
|
+
<div class="empty-state">
|
|
1410
|
+
<div class="empty-state-icon">📊</div>
|
|
1411
|
+
<div>Select a state machine to view its diagram</div>
|
|
1412
|
+
</div>`;
|
|
1413
|
+
document.getElementById('diagram-title').textContent =
|
|
1414
|
+
'Select a state machine to view its diagram';
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
// Transitions Panel
|
|
1418
|
+
function loadTransitionsForInstance(instanceId) {
|
|
1419
|
+
const instance = instances.find(i => i.id === instanceId);
|
|
1420
|
+
if (!instance) {
|
|
1421
|
+
clearTransitions();
|
|
1422
|
+
return;
|
|
1423
|
+
}
|
|
957
1424
|
|
|
958
|
-
//
|
|
959
|
-
|
|
1425
|
+
// Filter transitions that start from current state
|
|
1426
|
+
const availableTransitions = currentTransitions.filter(t => t.from === instance.currentState);
|
|
1427
|
+
renderTransitions(availableTransitions, instance);
|
|
960
1428
|
}
|
|
961
1429
|
|
|
962
|
-
function
|
|
963
|
-
|
|
1430
|
+
function renderTransitions(transitions, instance) {
|
|
1431
|
+
const container = document.getElementById('transitions-list');
|
|
964
1432
|
|
|
965
|
-
if (!
|
|
966
|
-
|
|
967
|
-
<div class="
|
|
968
|
-
<
|
|
969
|
-
|
|
970
|
-
|
|
1433
|
+
if (!transitions || transitions.length === 0) {
|
|
1434
|
+
container.innerHTML = `
|
|
1435
|
+
<div class="empty-state" style="padding: 20px 0;">
|
|
1436
|
+
<div style="font-size: 12px;">No transitions available from current state</div>
|
|
1437
|
+
</div>`;
|
|
1438
|
+
return;
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
container.innerHTML = transitions.map(t => `
|
|
1442
|
+
<div class="transition-item ${t.type === 'inter_machine' ? 'inter-machine' : ''}"
|
|
1443
|
+
onclick="sendTransitionEvent('${instance.id}', '${t.event}')">
|
|
1444
|
+
<div class="transition-info">
|
|
1445
|
+
<div class="transition-event">${t.event}</div>
|
|
1446
|
+
<div class="transition-path">${t.from} → ${t.to}</div>
|
|
1447
|
+
${t.targetMachine ? `<div class="transition-target">Creates instance in: ${t.targetMachine}</div>` : ''}
|
|
971
1448
|
</div>
|
|
972
|
-
<button class="btn
|
|
973
|
-
|
|
1449
|
+
<button class="transition-send-btn" onclick="event.stopPropagation(); sendTransitionEvent('${instance.id}', '${t.event}')">
|
|
1450
|
+
Send
|
|
1451
|
+
</button>
|
|
1452
|
+
</div>
|
|
1453
|
+
`).join('');
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
function clearTransitions() {
|
|
1457
|
+
document.getElementById('transitions-list').innerHTML = `
|
|
1458
|
+
<div class="empty-state" style="padding: 20px 0;">
|
|
1459
|
+
<div style="font-size: 12px;">Select an instance to see transitions</div>
|
|
1460
|
+
</div>`;
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
async function sendTransitionEvent(instanceId, eventType) {
|
|
1464
|
+
const component = getCurrentComponent();
|
|
1465
|
+
if (!component) return;
|
|
1466
|
+
|
|
1467
|
+
try {
|
|
1468
|
+
await fetch(`/api/components/${component.name}/instances/${instanceId}/events`, {
|
|
1469
|
+
method: 'POST',
|
|
1470
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1471
|
+
body: JSON.stringify({ type: eventType, payload: {} })
|
|
1472
|
+
});
|
|
1473
|
+
showToast(`Event "${eventType}" sent`, 'success');
|
|
1474
|
+
} catch (error) {
|
|
1475
|
+
showToast('Failed to send event', 'error');
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
// Instance Management
|
|
1480
|
+
async function loadInstances() {
|
|
1481
|
+
const res = await fetch('/api/instances');
|
|
1482
|
+
const data = await res.json();
|
|
1483
|
+
allInstances = data.instances || [];
|
|
1484
|
+
|
|
1485
|
+
if (selectedMachineName) {
|
|
1486
|
+
instances = allInstances.filter(i => i.machineName === selectedMachineName);
|
|
974
1487
|
} else {
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
for (const [key, field] of Object.entries(machine.contextSchema)) {
|
|
978
|
-
html += `<div class="form-group">`;
|
|
979
|
-
html += `<label for="diagram_ctx_${key}">${field.label || key}${field.required ? ' *' : ''}</label>`;
|
|
980
|
-
if (field.type === 'select') {
|
|
981
|
-
html += `<select id="diagram_ctx_${key}">`;
|
|
982
|
-
field.options.forEach(opt => html += `<option value="${opt.value}">${opt.label}</option>`);
|
|
983
|
-
html += `</select>`;
|
|
984
|
-
} else {
|
|
985
|
-
html += `<input type="${field.type || 'text'}" id="diagram_ctx_${key}" placeholder="${field.placeholder || ''}" ${field.required ? 'required' : ''}>`;
|
|
986
|
-
}
|
|
987
|
-
if (field.description) html += `<div class="form-help">${field.description}</div>`;
|
|
988
|
-
html += `</div>`;
|
|
989
|
-
}
|
|
990
|
-
html += `<button class="btn btn-primary" onclick="createInstanceFromDiagram()">Create Instance</button>`;
|
|
1488
|
+
instances = [];
|
|
991
1489
|
}
|
|
992
1490
|
|
|
993
|
-
|
|
1491
|
+
renderInstanceList();
|
|
1492
|
+
updateStats();
|
|
1493
|
+
|
|
1494
|
+
if (currentView === 'component') {
|
|
1495
|
+
renderComponentView();
|
|
1496
|
+
}
|
|
994
1497
|
}
|
|
995
1498
|
|
|
996
|
-
function
|
|
997
|
-
const
|
|
1499
|
+
function renderInstanceList() {
|
|
1500
|
+
const container = document.getElementById('instance-list');
|
|
1501
|
+
const countEl = document.getElementById('instance-count');
|
|
998
1502
|
|
|
999
|
-
|
|
1000
|
-
|
|
1503
|
+
countEl.textContent = `${instances.length} instance${instances.length !== 1 ? 's' : ''}`;
|
|
1504
|
+
|
|
1505
|
+
if (instances.length === 0) {
|
|
1506
|
+
container.innerHTML = `
|
|
1507
|
+
<div class="empty-state">
|
|
1508
|
+
<div class="empty-state-icon">📦</div>
|
|
1509
|
+
<div>No instances yet</div>
|
|
1510
|
+
</div>`;
|
|
1001
1511
|
return;
|
|
1002
1512
|
}
|
|
1003
1513
|
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1514
|
+
container.innerHTML = instances.map(inst => {
|
|
1515
|
+
const isTerminal = terminalStates.has(inst.currentState);
|
|
1516
|
+
const stateClass = inst.status === 'error' ? 'error' :
|
|
1517
|
+
(isTerminal ? 'terminal' : 'active');
|
|
1518
|
+
|
|
1519
|
+
return `
|
|
1520
|
+
<div class="instance-item ${selectedInstanceId === inst.id ? 'selected' : ''}"
|
|
1521
|
+
onclick="selectInstance('${inst.id}')">
|
|
1522
|
+
<div class="instance-header">
|
|
1523
|
+
<span class="instance-id">${inst.id.substring(0, 8)}</span>
|
|
1524
|
+
<span class="instance-state ${stateClass}">${inst.currentState}</span>
|
|
1525
|
+
</div>
|
|
1526
|
+
<div class="instance-meta">
|
|
1527
|
+
${inst.status} • ${new Date(inst.updatedAt).toLocaleTimeString()}
|
|
1528
|
+
</div>
|
|
1529
|
+
</div>`;
|
|
1530
|
+
}).join('');
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
function clearInstanceList() {
|
|
1534
|
+
document.getElementById('instance-list').innerHTML = `
|
|
1535
|
+
<div class="empty-state">
|
|
1536
|
+
<div class="empty-state-icon">📦</div>
|
|
1537
|
+
<div>Select a machine to see instances</div>
|
|
1538
|
+
</div>`;
|
|
1539
|
+
document.getElementById('instance-count').textContent = '0 instances';
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
async function selectInstance(id) {
|
|
1543
|
+
selectedInstanceId = id;
|
|
1544
|
+
renderInstanceList();
|
|
1545
|
+
renderDiagram();
|
|
1546
|
+
loadInstanceHistory(id);
|
|
1547
|
+
loadTransitionsForInstance(id);
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
function updateStats() {
|
|
1551
|
+
const total = instances.length;
|
|
1552
|
+
const terminalCount = instances.filter(i => terminalStates.has(i.currentState)).length;
|
|
1553
|
+
const activeCount = instances.filter(i => i.status === 'active' && !terminalStates.has(i.currentState)).length;
|
|
1554
|
+
|
|
1555
|
+
document.getElementById('stat-instances').textContent = total;
|
|
1556
|
+
document.getElementById('stat-active').textContent = activeCount;
|
|
1557
|
+
document.getElementById('stat-terminal').textContent = terminalCount;
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
// Instance History (Traceability)
|
|
1561
|
+
async function loadInstanceHistory(instanceId) {
|
|
1562
|
+
const component = getCurrentComponent();
|
|
1563
|
+
if (!component) return;
|
|
1564
|
+
|
|
1565
|
+
const infoEl = document.getElementById('history-instance-info');
|
|
1566
|
+
infoEl.textContent = `- ${instanceId.substring(0, 8)}`;
|
|
1567
|
+
|
|
1568
|
+
try {
|
|
1569
|
+
const res = await fetch(`/api/instances/${instanceId}/history`);
|
|
1570
|
+
const data = await res.json();
|
|
1571
|
+
|
|
1572
|
+
if (data.history && data.history.length > 0) {
|
|
1573
|
+
renderHistory(data.history);
|
|
1574
|
+
} else {
|
|
1575
|
+
renderHistoryFromInstance(instanceId);
|
|
1576
|
+
}
|
|
1577
|
+
} catch (error) {
|
|
1578
|
+
renderHistoryFromInstance(instanceId);
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
function renderHistory(history) {
|
|
1583
|
+
const container = document.getElementById('history-timeline');
|
|
1584
|
+
|
|
1585
|
+
if (!history || history.length === 0) {
|
|
1586
|
+
container.innerHTML = '<div class="history-empty">No history available</div>';
|
|
1587
|
+
return;
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
let html = '';
|
|
1591
|
+
|
|
1592
|
+
// Add initial state
|
|
1593
|
+
const firstEvent = history[0];
|
|
1594
|
+
if (firstEvent.stateBefore) {
|
|
1595
|
+
html += `
|
|
1596
|
+
<div class="history-node">
|
|
1597
|
+
<div class="history-state">${firstEvent.stateBefore}</div>
|
|
1598
|
+
<div class="history-time">Initial</div>
|
|
1599
|
+
</div>`;
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
history.forEach((event, index) => {
|
|
1603
|
+
const isLast = index === history.length - 1;
|
|
1604
|
+
const isTerminal = terminalStates.has(event.stateAfter);
|
|
1605
|
+
|
|
1606
|
+
html += `
|
|
1607
|
+
<div class="history-arrow">
|
|
1608
|
+
<div class="history-arrow-line"></div>
|
|
1609
|
+
<div class="history-event">${event.event?.type || 'transition'}</div>
|
|
1009
1610
|
</div>
|
|
1611
|
+
<div class="history-node">
|
|
1612
|
+
<div class="history-state ${isLast ? (isTerminal ? 'terminal' : 'current') : ''}">
|
|
1613
|
+
${event.stateAfter}
|
|
1614
|
+
</div>
|
|
1615
|
+
<div class="history-time">${new Date(event.persistedAt).toLocaleTimeString()}</div>
|
|
1616
|
+
</div>`;
|
|
1617
|
+
});
|
|
1618
|
+
|
|
1619
|
+
container.innerHTML = html;
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
function renderHistoryFromInstance(instanceId) {
|
|
1623
|
+
const instance = instances.find(i => i.id === instanceId);
|
|
1624
|
+
if (!instance) {
|
|
1625
|
+
document.getElementById('history-timeline').innerHTML =
|
|
1626
|
+
'<div class="history-empty">Instance not found</div>';
|
|
1627
|
+
return;
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
const machine = getCurrentMachine();
|
|
1631
|
+
const isTerminal = terminalStates.has(instance.currentState);
|
|
1632
|
+
|
|
1633
|
+
document.getElementById('history-timeline').innerHTML = `
|
|
1634
|
+
<div class="history-node">
|
|
1635
|
+
<div class="history-state">${machine?.initialState || 'initial'}</div>
|
|
1636
|
+
<div class="history-time">Created</div>
|
|
1010
1637
|
</div>
|
|
1011
|
-
|
|
1638
|
+
<div class="history-arrow">
|
|
1639
|
+
<div class="history-arrow-line"></div>
|
|
1640
|
+
<div class="history-event">...</div>
|
|
1641
|
+
</div>
|
|
1642
|
+
<div class="history-node">
|
|
1643
|
+
<div class="history-state ${isTerminal ? 'terminal' : 'current'}">${instance.currentState}</div>
|
|
1644
|
+
<div class="history-time">${new Date(instance.updatedAt).toLocaleTimeString()}</div>
|
|
1645
|
+
</div>`;
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
function clearHistory() {
|
|
1649
|
+
document.getElementById('history-timeline').innerHTML =
|
|
1650
|
+
'<div class="history-empty">Select an instance to view its transition history</div>';
|
|
1651
|
+
document.getElementById('history-instance-info').textContent = '';
|
|
1652
|
+
document.getElementById('history-actions').innerHTML = '';
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
// Create Instance Modal
|
|
1656
|
+
function openCreateModal() {
|
|
1657
|
+
if (!selectedMachineName) {
|
|
1658
|
+
showToast('Please select a machine first', 'error');
|
|
1659
|
+
return;
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
const machine = getCurrentMachine();
|
|
1663
|
+
if (machine?.contextSchema) {
|
|
1664
|
+
renderSchemaForm(machine.contextSchema);
|
|
1665
|
+
} else {
|
|
1666
|
+
document.getElementById('create-form-content').innerHTML = `
|
|
1667
|
+
<div class="form-group">
|
|
1668
|
+
<label>Context (JSON)</label>
|
|
1669
|
+
<textarea id="create-context" placeholder='{"property": "value"}'></textarea>
|
|
1670
|
+
<div class="form-help">Enter a JSON object with context properties</div>
|
|
1671
|
+
</div>`;
|
|
1672
|
+
}
|
|
1012
1673
|
|
|
1013
|
-
document.getElementById('
|
|
1674
|
+
document.getElementById('create-modal').classList.add('active');
|
|
1014
1675
|
}
|
|
1015
1676
|
|
|
1016
|
-
|
|
1017
|
-
|
|
1677
|
+
function renderSchemaForm(schema) {
|
|
1678
|
+
let html = '';
|
|
1679
|
+
for (const [key, field] of Object.entries(schema)) {
|
|
1680
|
+
html += `<div class="form-group">
|
|
1681
|
+
<label>${field.label || key}${field.required ? ' *' : ''}</label>`;
|
|
1682
|
+
|
|
1683
|
+
if (field.type === 'select') {
|
|
1684
|
+
html += `<select class="selector" id="ctx_${key}">
|
|
1685
|
+
${field.options.map(o => `<option value="${o.value}">${o.label}</option>`).join('')}
|
|
1686
|
+
</select>`;
|
|
1687
|
+
} else {
|
|
1688
|
+
html += `<input type="${field.type || 'text'}" id="ctx_${key}"
|
|
1689
|
+
placeholder="${field.placeholder || ''}" ${field.required ? 'required' : ''}>`;
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
if (field.description) {
|
|
1693
|
+
html += `<div class="form-help">${field.description}</div>`;
|
|
1694
|
+
}
|
|
1695
|
+
html += '</div>';
|
|
1696
|
+
}
|
|
1697
|
+
document.getElementById('create-form-content').innerHTML = html;
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
function closeCreateModal() {
|
|
1701
|
+
document.getElementById('create-modal').classList.remove('active');
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
async function createInstance() {
|
|
1018
1705
|
const component = getCurrentComponent();
|
|
1019
|
-
|
|
1706
|
+
const machine = getCurrentMachine();
|
|
1707
|
+
if (!component || !machine) return;
|
|
1020
1708
|
|
|
1021
|
-
const machine = component.stateMachines.find(m => m.name === machineName);
|
|
1022
1709
|
let context = {};
|
|
1023
1710
|
|
|
1024
|
-
if (machine
|
|
1711
|
+
if (machine.contextSchema) {
|
|
1025
1712
|
for (const key of Object.keys(machine.contextSchema)) {
|
|
1026
|
-
const input = document.getElementById('
|
|
1713
|
+
const input = document.getElementById('ctx_' + key);
|
|
1027
1714
|
if (input && input.value) {
|
|
1028
1715
|
context[key] = input.type === 'number' ? parseFloat(input.value) : input.value;
|
|
1029
1716
|
}
|
|
1030
1717
|
}
|
|
1031
1718
|
} else {
|
|
1032
|
-
const
|
|
1033
|
-
if (
|
|
1719
|
+
const jsonInput = document.getElementById('create-context');
|
|
1720
|
+
if (jsonInput && jsonInput.value) {
|
|
1034
1721
|
try {
|
|
1035
|
-
context = JSON.parse(
|
|
1722
|
+
context = JSON.parse(jsonInput.value);
|
|
1036
1723
|
} catch (e) {
|
|
1037
|
-
|
|
1724
|
+
showToast('Invalid JSON: ' + e.message, 'error');
|
|
1725
|
+
return;
|
|
1038
1726
|
}
|
|
1039
1727
|
}
|
|
1040
1728
|
}
|
|
1041
1729
|
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
const input = document.getElementById('diagram_ctx_' + key);
|
|
1052
|
-
if (input) input.value = '';
|
|
1053
|
-
}
|
|
1054
|
-
} else {
|
|
1055
|
-
const textarea = document.getElementById('diagram-context-json');
|
|
1056
|
-
if (textarea) textarea.value = '';
|
|
1730
|
+
try {
|
|
1731
|
+
await fetch(`/api/components/${component.name}/instances`, {
|
|
1732
|
+
method: 'POST',
|
|
1733
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1734
|
+
body: JSON.stringify({ machineName: machine.name, context })
|
|
1735
|
+
});
|
|
1736
|
+
closeCreateModal();
|
|
1737
|
+
} catch (error) {
|
|
1738
|
+
showToast('Failed to create instance', 'error');
|
|
1057
1739
|
}
|
|
1740
|
+
}
|
|
1058
1741
|
|
|
1059
|
-
|
|
1060
|
-
|
|
1742
|
+
// Send Event Modal
|
|
1743
|
+
let eventTargetInstanceId = null;
|
|
1744
|
+
|
|
1745
|
+
function openEventModal(instanceId) {
|
|
1746
|
+
eventTargetInstanceId = instanceId;
|
|
1747
|
+
document.getElementById('event-type').value = '';
|
|
1748
|
+
document.getElementById('event-payload').value = '';
|
|
1749
|
+
document.getElementById('event-modal').classList.add('active');
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
function closeEventModal() {
|
|
1753
|
+
document.getElementById('event-modal').classList.remove('active');
|
|
1754
|
+
eventTargetInstanceId = null;
|
|
1061
1755
|
}
|
|
1062
|
-
|
|
1063
|
-
// Traceability
|
|
1064
|
-
async function loadTrace() {
|
|
1065
|
-
if (!selectedInstance) return;
|
|
1066
|
-
|
|
1067
|
-
const container = document.getElementById('trace-container');
|
|
1068
|
-
// In real version, fetch from /api/instances/:id/history
|
|
1069
|
-
container.innerHTML = '<div class="empty-state"><div class="empty-state-icon">🔍</div><div>Traceability history would appear here</div></div>';
|
|
1070
|
-
}
|
|
1071
|
-
|
|
1072
|
-
function updateTraceSelector() {
|
|
1073
|
-
const select = document.getElementById('trace-instance');
|
|
1074
|
-
select.innerHTML = '<option value="">Select an instance...</option>' +
|
|
1075
|
-
instances.map(i => `<option value="${i.id}">${i.id.substring(0, 8)} - ${i.machineName}</option>`).join('');
|
|
1076
|
-
}
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
function populateSelectors() {
|
|
1080
|
-
if (!componentsData || componentsData.length === 0) return;
|
|
1081
|
-
|
|
1082
|
-
// Populate component selector with all loaded components
|
|
1083
|
-
const componentSelect = document.getElementById('component-select');
|
|
1084
|
-
componentSelect.innerHTML = componentsData.map(c =>
|
|
1085
|
-
`<option value="${c.name}" ${c.name === selectedComponentName ? 'selected' : ''}>${c.name}</option>`
|
|
1086
|
-
).join('');
|
|
1087
1756
|
|
|
1088
|
-
|
|
1757
|
+
async function sendEvent() {
|
|
1758
|
+
if (!eventTargetInstanceId) return;
|
|
1759
|
+
|
|
1089
1760
|
const component = getCurrentComponent();
|
|
1090
|
-
if (component
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1761
|
+
if (!component) return;
|
|
1762
|
+
|
|
1763
|
+
const eventType = document.getElementById('event-type').value;
|
|
1764
|
+
if (!eventType) {
|
|
1765
|
+
showToast('Event type is required', 'error');
|
|
1766
|
+
return;
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
let payload = {};
|
|
1770
|
+
const payloadInput = document.getElementById('event-payload').value;
|
|
1771
|
+
if (payloadInput) {
|
|
1772
|
+
try {
|
|
1773
|
+
payload = JSON.parse(payloadInput);
|
|
1774
|
+
} catch (e) {
|
|
1775
|
+
showToast('Invalid JSON payload', 'error');
|
|
1776
|
+
return;
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1780
|
+
try {
|
|
1781
|
+
await fetch(`/api/components/${component.name}/instances/${eventTargetInstanceId}/events`, {
|
|
1782
|
+
method: 'POST',
|
|
1783
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1784
|
+
body: JSON.stringify({ type: eventType, payload })
|
|
1785
|
+
});
|
|
1786
|
+
closeEventModal();
|
|
1787
|
+
showToast(`Event "${eventType}" sent`, 'success');
|
|
1788
|
+
} catch (error) {
|
|
1789
|
+
showToast('Failed to send event', 'error');
|
|
1094
1790
|
}
|
|
1095
1791
|
}
|
|
1096
1792
|
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
const a = document.createElement('a');
|
|
1112
|
-
a.href = url;
|
|
1113
|
-
a.download = `xcomponent-state-${Date.now()}.json`;
|
|
1114
|
-
a.click();
|
|
1115
|
-
}
|
|
1116
|
-
|
|
1793
|
+
// Toast Notifications
|
|
1794
|
+
function showToast(message, type = 'info') {
|
|
1795
|
+
const container = document.getElementById('toast-container');
|
|
1796
|
+
const toast = document.createElement('div');
|
|
1797
|
+
toast.className = `toast ${type}`;
|
|
1798
|
+
toast.textContent = message;
|
|
1799
|
+
container.appendChild(toast);
|
|
1800
|
+
|
|
1801
|
+
setTimeout(() => {
|
|
1802
|
+
toast.style.opacity = '0';
|
|
1803
|
+
setTimeout(() => toast.remove(), 300);
|
|
1804
|
+
}, 3000);
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1117
1807
|
// Initialize
|
|
1118
1808
|
loadInstances();
|
|
1119
|
-
mermaid.initialize({startOnLoad: true, theme: 'default'});
|
|
1120
1809
|
</script>
|
|
1121
1810
|
</body>
|
|
1122
1811
|
</html>
|