pinokiod 7.3.14 → 7.4.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/kernel/api/index.js +111 -15
- package/kernel/api/script/index.js +10 -0
- package/kernel/autolaunch.js +111 -0
- package/kernel/environment.js +89 -1
- package/kernel/git.js +9 -19
- package/kernel/index.js +142 -43
- package/kernel/launch_requirements.js +1115 -0
- package/kernel/ready.js +231 -0
- package/kernel/script.js +16 -0
- package/kernel/shells.js +9 -1
- package/kernel/workspace_status.js +111 -45
- package/package.json +1 -1
- package/server/autolaunch.js +725 -0
- package/server/index.js +244 -411
- package/server/public/logs.js +15 -1
- package/server/public/style.css +99 -31
- package/server/views/app.ejs +263 -159
- package/server/views/autolaunch.ejs +363 -75
- package/server/views/index.ejs +550 -26
- package/server/views/logs.ejs +14 -12
- package/server/views/partials/app_autolaunch_dependency_events.ejs +99 -0
- package/server/views/partials/app_autolaunch_dependency_save.ejs +10 -0
- package/server/views/partials/app_autolaunch_dependency_styles.ejs +357 -0
- package/server/views/partials/app_autolaunch_modal_helpers.ejs +347 -0
- package/server/views/partials/autolaunch_dependency_helpers.ejs +204 -0
- package/server/views/partials/autolaunch_dependency_save.ejs +13 -0
- package/server/views/partials/autolaunch_dependency_styles.ejs +347 -0
- package/server/views/partials/home_action_modal.ejs +4 -1
- package/server/views/partials/launch_requirements_status_client.ejs +271 -0
- package/server/views/partials/launch_requirements_status_styles.ejs +171 -0
- package/server/views/partials/launch_settings_dependency_save_factory.ejs +45 -0
- package/server/views/partials/launch_settings_dependency_script_loader_factory.ejs +25 -0
- package/server/views/terminal.ejs +196 -2
- package/test/home-autolaunch-live-ui.test.js +455 -0
- package/test/launch-requirements-browser.test.js +579 -0
- package/test/launch-requirements-contract-coverage.test.js +627 -0
- package/test/launch-requirements-real-browser.js +625 -0
- package/test/launch-requirements-status-client.test.js +132 -0
- package/test/launch-requirements.test.js +1806 -0
- package/test/launch-settings-ui.test.js +370 -0
- package/test/ready-state.test.js +49 -0
- package/test/server-autolaunch.test.js +1052 -0
- package/test/startup-git-index-benchmark.js +409 -0
- package/test/startup-git-index-browser.js +320 -0
- package/test/startup-git-index-performance.test.js +380 -0
- package/test/startup-git-index-refactor.test.js +450 -0
- package/test/startup-git-index-route.test.js +588 -0
- package/test/universal-launcher.smoke.spec.js +10 -9
- package/test/workspace-gitignore-benchmark.js +815 -0
- package/test/workspace-gitignore-path-scoped.test.js +256 -0
- package/spec/INSTRUCTION_SYNC.md +0 -432
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
body.autolaunch-page .autolaunch-dependency-list {
|
|
2
|
+
display: grid;
|
|
3
|
+
gap: 8px;
|
|
4
|
+
}
|
|
5
|
+
body.autolaunch-page .autolaunch-dependency-stack {
|
|
6
|
+
border: 1px solid var(--task-border);
|
|
7
|
+
border-radius: 8px;
|
|
8
|
+
background: color-mix(in srgb, var(--task-panel) 98%, var(--task-soft));
|
|
9
|
+
overflow: hidden;
|
|
10
|
+
}
|
|
11
|
+
body.autolaunch-page .autolaunch-dependency-selected {
|
|
12
|
+
display: grid;
|
|
13
|
+
}
|
|
14
|
+
body.autolaunch-page .autolaunch-dependency-row {
|
|
15
|
+
border-bottom: 1px solid var(--task-border);
|
|
16
|
+
color: var(--task-text);
|
|
17
|
+
display: grid;
|
|
18
|
+
grid-template-columns: 30px minmax(0, 1fr) auto;
|
|
19
|
+
align-items: center;
|
|
20
|
+
gap: 9px;
|
|
21
|
+
min-height: 48px;
|
|
22
|
+
padding: 8px 10px;
|
|
23
|
+
}
|
|
24
|
+
body.autolaunch-page .autolaunch-dependency-row:last-child {
|
|
25
|
+
border-bottom: 0;
|
|
26
|
+
}
|
|
27
|
+
body.autolaunch-page .autolaunch-dependency-row img {
|
|
28
|
+
width: 30px;
|
|
29
|
+
height: 30px;
|
|
30
|
+
border-radius: 7px;
|
|
31
|
+
object-fit: cover;
|
|
32
|
+
}
|
|
33
|
+
body.autolaunch-page .autolaunch-dependency-row-empty {
|
|
34
|
+
min-height: 42px;
|
|
35
|
+
display: flex;
|
|
36
|
+
align-items: center;
|
|
37
|
+
color: var(--task-muted);
|
|
38
|
+
font-size: 12px;
|
|
39
|
+
padding: 0 10px;
|
|
40
|
+
}
|
|
41
|
+
body.autolaunch-page .autolaunch-dependency-remove {
|
|
42
|
+
width: 26px;
|
|
43
|
+
height: 26px;
|
|
44
|
+
border: 0;
|
|
45
|
+
border-radius: 6px;
|
|
46
|
+
background: transparent;
|
|
47
|
+
color: var(--task-muted);
|
|
48
|
+
display: flex;
|
|
49
|
+
align-items: center;
|
|
50
|
+
justify-content: center;
|
|
51
|
+
padding: 0;
|
|
52
|
+
cursor: pointer;
|
|
53
|
+
}
|
|
54
|
+
body.autolaunch-page .autolaunch-dependency-remove:hover {
|
|
55
|
+
background: color-mix(in srgb, var(--task-border) 70%, transparent);
|
|
56
|
+
color: var(--task-text);
|
|
57
|
+
}
|
|
58
|
+
body.autolaunch-page .autolaunch-dependency-picker {
|
|
59
|
+
display: grid;
|
|
60
|
+
gap: 0;
|
|
61
|
+
}
|
|
62
|
+
body.autolaunch-page .autolaunch-dependency-trigger {
|
|
63
|
+
width: 100%;
|
|
64
|
+
min-height: 40px;
|
|
65
|
+
border: 0;
|
|
66
|
+
border-top: 1px solid var(--task-border);
|
|
67
|
+
border-radius: 0;
|
|
68
|
+
background: transparent;
|
|
69
|
+
color: var(--task-text);
|
|
70
|
+
padding: 0 10px;
|
|
71
|
+
display: flex;
|
|
72
|
+
align-items: center;
|
|
73
|
+
gap: 8px;
|
|
74
|
+
text-align: left;
|
|
75
|
+
font-size: 12px;
|
|
76
|
+
font-weight: 650;
|
|
77
|
+
cursor: pointer;
|
|
78
|
+
}
|
|
79
|
+
body.autolaunch-page .autolaunch-dependency-trigger:hover {
|
|
80
|
+
background: color-mix(in srgb, var(--task-soft) 70%, var(--task-panel));
|
|
81
|
+
}
|
|
82
|
+
body.autolaunch-page .autolaunch-dependency-trigger:disabled {
|
|
83
|
+
cursor: not-allowed;
|
|
84
|
+
opacity: 0.5;
|
|
85
|
+
}
|
|
86
|
+
body.autolaunch-page .autolaunch-dependency-trigger span {
|
|
87
|
+
flex: 1;
|
|
88
|
+
min-width: 0;
|
|
89
|
+
}
|
|
90
|
+
body.autolaunch-page .autolaunch-dependency-menu {
|
|
91
|
+
border-top: 1px solid var(--task-border);
|
|
92
|
+
background: transparent;
|
|
93
|
+
overflow: hidden;
|
|
94
|
+
}
|
|
95
|
+
body.autolaunch-page .autolaunch-dependency-filter {
|
|
96
|
+
min-height: 34px;
|
|
97
|
+
border-bottom: 1px solid var(--task-border);
|
|
98
|
+
display: flex;
|
|
99
|
+
align-items: center;
|
|
100
|
+
gap: 8px;
|
|
101
|
+
padding: 0 10px;
|
|
102
|
+
color: var(--task-muted);
|
|
103
|
+
}
|
|
104
|
+
body.autolaunch-page .autolaunch-dependency-filter input {
|
|
105
|
+
width: 100%;
|
|
106
|
+
min-width: 0;
|
|
107
|
+
border: 0;
|
|
108
|
+
outline: none;
|
|
109
|
+
background: transparent;
|
|
110
|
+
color: var(--task-text);
|
|
111
|
+
font: inherit;
|
|
112
|
+
font-size: 12px;
|
|
113
|
+
}
|
|
114
|
+
body.autolaunch-page .autolaunch-dependency-options {
|
|
115
|
+
max-height: 320px;
|
|
116
|
+
overflow: auto;
|
|
117
|
+
}
|
|
118
|
+
body.autolaunch-page .autolaunch-dependency-option {
|
|
119
|
+
width: 100%;
|
|
120
|
+
border: 0;
|
|
121
|
+
border-bottom: 1px solid var(--task-border);
|
|
122
|
+
border-radius: 0;
|
|
123
|
+
background: transparent;
|
|
124
|
+
color: var(--task-text);
|
|
125
|
+
display: grid;
|
|
126
|
+
grid-template-columns: 28px minmax(0, 1fr);
|
|
127
|
+
gap: 9px;
|
|
128
|
+
align-items: center;
|
|
129
|
+
padding: 8px 10px;
|
|
130
|
+
text-align: left;
|
|
131
|
+
cursor: pointer;
|
|
132
|
+
}
|
|
133
|
+
body.autolaunch-page .autolaunch-dependency-option:last-child {
|
|
134
|
+
border-bottom: 0;
|
|
135
|
+
}
|
|
136
|
+
body.autolaunch-page .autolaunch-dependency-option:hover,
|
|
137
|
+
body.autolaunch-page .autolaunch-dependency-option:focus-visible {
|
|
138
|
+
background: color-mix(in srgb, var(--task-accent) 8%, transparent);
|
|
139
|
+
outline: none;
|
|
140
|
+
}
|
|
141
|
+
body.autolaunch-page .autolaunch-dependency-option[hidden] {
|
|
142
|
+
display: none;
|
|
143
|
+
}
|
|
144
|
+
body.autolaunch-page .autolaunch-dependency-option img {
|
|
145
|
+
width: 28px;
|
|
146
|
+
height: 28px;
|
|
147
|
+
border-radius: 6px;
|
|
148
|
+
object-fit: cover;
|
|
149
|
+
}
|
|
150
|
+
body.autolaunch-page .autolaunch-dependency-option-meta {
|
|
151
|
+
min-width: 0;
|
|
152
|
+
display: grid;
|
|
153
|
+
gap: 2px;
|
|
154
|
+
}
|
|
155
|
+
body.autolaunch-page .autolaunch-dependency-option-title {
|
|
156
|
+
font-size: 12px;
|
|
157
|
+
font-weight: 700;
|
|
158
|
+
overflow: hidden;
|
|
159
|
+
text-overflow: ellipsis;
|
|
160
|
+
white-space: nowrap;
|
|
161
|
+
}
|
|
162
|
+
body.autolaunch-page .autolaunch-dependency-option-path {
|
|
163
|
+
color: var(--task-muted);
|
|
164
|
+
direction: rtl;
|
|
165
|
+
font-size: 11px;
|
|
166
|
+
line-height: 1.25;
|
|
167
|
+
overflow: hidden;
|
|
168
|
+
text-align: left;
|
|
169
|
+
text-overflow: ellipsis;
|
|
170
|
+
unicode-bidi: plaintext;
|
|
171
|
+
white-space: nowrap;
|
|
172
|
+
}
|
|
173
|
+
body.autolaunch-page .autolaunch-dependency-picker-empty {
|
|
174
|
+
color: var(--task-muted);
|
|
175
|
+
font-size: 11px;
|
|
176
|
+
padding: 10px;
|
|
177
|
+
}
|
|
178
|
+
body.autolaunch-page .autolaunch-dependency-configure {
|
|
179
|
+
width: fit-content;
|
|
180
|
+
border: 1px solid color-mix(in srgb, #b45309 24%, var(--task-border));
|
|
181
|
+
border-radius: 6px;
|
|
182
|
+
background: color-mix(in srgb, #b45309 8%, var(--task-panel));
|
|
183
|
+
color: var(--task-text);
|
|
184
|
+
display: inline-flex;
|
|
185
|
+
align-items: center;
|
|
186
|
+
gap: 5px;
|
|
187
|
+
min-height: 24px;
|
|
188
|
+
padding: 0 7px;
|
|
189
|
+
font-size: 11px;
|
|
190
|
+
font-weight: 700;
|
|
191
|
+
cursor: pointer;
|
|
192
|
+
white-space: nowrap;
|
|
193
|
+
}
|
|
194
|
+
body.autolaunch-page .autolaunch-dependency-configure:disabled {
|
|
195
|
+
cursor: wait;
|
|
196
|
+
opacity: 0.6;
|
|
197
|
+
}
|
|
198
|
+
body.autolaunch-page .autolaunch-icon-button {
|
|
199
|
+
width: 34px;
|
|
200
|
+
height: 34px;
|
|
201
|
+
border: 1px solid var(--task-border-strong);
|
|
202
|
+
border-radius: 7px;
|
|
203
|
+
background: color-mix(in srgb, var(--task-panel) 94%, white);
|
|
204
|
+
color: var(--task-muted);
|
|
205
|
+
display: inline-flex;
|
|
206
|
+
align-items: center;
|
|
207
|
+
justify-content: center;
|
|
208
|
+
cursor: pointer;
|
|
209
|
+
}
|
|
210
|
+
body.dark.autolaunch-page .autolaunch-icon-button {
|
|
211
|
+
background: color-mix(in srgb, var(--task-panel) 92%, black);
|
|
212
|
+
}
|
|
213
|
+
body.autolaunch-page .autolaunch-icon-button:hover {
|
|
214
|
+
color: var(--task-text);
|
|
215
|
+
border-color: var(--task-border-strong);
|
|
216
|
+
}
|
|
217
|
+
body.autolaunch-page .autolaunch-icon-button:disabled {
|
|
218
|
+
cursor: wait;
|
|
219
|
+
opacity: 0.6;
|
|
220
|
+
}
|
|
221
|
+
body.autolaunch-page .autolaunch-dependency-script-modal {
|
|
222
|
+
position: fixed;
|
|
223
|
+
inset: 0;
|
|
224
|
+
z-index: 10000003;
|
|
225
|
+
display: flex;
|
|
226
|
+
align-items: center;
|
|
227
|
+
justify-content: center;
|
|
228
|
+
padding: 24px;
|
|
229
|
+
background: rgba(2, 6, 23, 0.55);
|
|
230
|
+
box-sizing: border-box;
|
|
231
|
+
}
|
|
232
|
+
body.autolaunch-page .autolaunch-dependency-script-panel {
|
|
233
|
+
width: min(520px, calc(100vw - 32px));
|
|
234
|
+
max-height: min(620px, calc(100vh - 48px));
|
|
235
|
+
overflow: auto;
|
|
236
|
+
border: 1px solid var(--task-border-strong);
|
|
237
|
+
border-radius: 8px;
|
|
238
|
+
background: color-mix(in srgb, var(--task-panel) 98%, white);
|
|
239
|
+
color: var(--task-text);
|
|
240
|
+
box-shadow: 0 24px 72px rgba(2, 6, 23, 0.42);
|
|
241
|
+
box-sizing: border-box;
|
|
242
|
+
}
|
|
243
|
+
body.dark.autolaunch-page .autolaunch-dependency-script-panel {
|
|
244
|
+
background: color-mix(in srgb, var(--task-panel) 96%, black);
|
|
245
|
+
box-shadow: 0 28px 80px rgba(0, 0, 0, 0.64);
|
|
246
|
+
}
|
|
247
|
+
body.autolaunch-page .autolaunch-dependency-script-head {
|
|
248
|
+
display: flex;
|
|
249
|
+
align-items: flex-start;
|
|
250
|
+
justify-content: space-between;
|
|
251
|
+
gap: 12px;
|
|
252
|
+
padding: 16px;
|
|
253
|
+
border-bottom: 1px solid var(--task-border);
|
|
254
|
+
}
|
|
255
|
+
body.autolaunch-page .autolaunch-dependency-script-app {
|
|
256
|
+
display: grid;
|
|
257
|
+
grid-template-columns: 42px minmax(0, 1fr);
|
|
258
|
+
align-items: center;
|
|
259
|
+
gap: 12px;
|
|
260
|
+
padding: 14px 16px;
|
|
261
|
+
border-bottom: 1px solid var(--task-border);
|
|
262
|
+
}
|
|
263
|
+
body.autolaunch-page .autolaunch-dependency-script-app img {
|
|
264
|
+
width: 42px;
|
|
265
|
+
height: 42px;
|
|
266
|
+
border-radius: 8px;
|
|
267
|
+
object-fit: cover;
|
|
268
|
+
}
|
|
269
|
+
body.autolaunch-page .autolaunch-dependency-script-body {
|
|
270
|
+
padding: 14px 16px 16px;
|
|
271
|
+
}
|
|
272
|
+
body.autolaunch-page .autolaunch-dependency-script-actions {
|
|
273
|
+
position: sticky;
|
|
274
|
+
bottom: 0;
|
|
275
|
+
padding: 12px 16px;
|
|
276
|
+
border-top: 1px solid var(--task-border);
|
|
277
|
+
background: color-mix(in srgb, var(--task-panel) 98%, white);
|
|
278
|
+
}
|
|
279
|
+
body.dark.autolaunch-page .autolaunch-dependency-script-actions {
|
|
280
|
+
background: color-mix(in srgb, var(--task-panel) 96%, black);
|
|
281
|
+
}
|
|
282
|
+
body.autolaunch-page .autolaunch-primary {
|
|
283
|
+
appearance: none;
|
|
284
|
+
width: 100%;
|
|
285
|
+
min-height: 38px;
|
|
286
|
+
border: 1px solid color-mix(in srgb, var(--task-accent) 72%, var(--task-border));
|
|
287
|
+
border-radius: 7px;
|
|
288
|
+
background: var(--task-accent);
|
|
289
|
+
color: #fff;
|
|
290
|
+
display: inline-flex;
|
|
291
|
+
align-items: center;
|
|
292
|
+
justify-content: center;
|
|
293
|
+
gap: 8px;
|
|
294
|
+
padding: 0 12px;
|
|
295
|
+
font: inherit;
|
|
296
|
+
font-size: 12px;
|
|
297
|
+
font-weight: 800;
|
|
298
|
+
line-height: 1;
|
|
299
|
+
cursor: pointer;
|
|
300
|
+
white-space: nowrap;
|
|
301
|
+
}
|
|
302
|
+
body.autolaunch-page .autolaunch-primary:hover {
|
|
303
|
+
filter: brightness(0.95);
|
|
304
|
+
}
|
|
305
|
+
body.autolaunch-page .autolaunch-primary:focus-visible {
|
|
306
|
+
outline: 2px solid color-mix(in srgb, var(--task-accent) 36%, transparent);
|
|
307
|
+
outline-offset: 2px;
|
|
308
|
+
}
|
|
309
|
+
body.autolaunch-page .autolaunch-primary:disabled {
|
|
310
|
+
border-color: var(--task-border);
|
|
311
|
+
background: color-mix(in srgb, var(--task-soft) 80%, var(--task-panel));
|
|
312
|
+
color: var(--task-muted);
|
|
313
|
+
cursor: not-allowed;
|
|
314
|
+
filter: none;
|
|
315
|
+
}
|
|
316
|
+
body.autolaunch-page .autolaunch-primary-script {
|
|
317
|
+
max-width: 180px;
|
|
318
|
+
overflow: hidden;
|
|
319
|
+
text-overflow: ellipsis;
|
|
320
|
+
padding: 3px 6px;
|
|
321
|
+
border-radius: 5px;
|
|
322
|
+
background: rgba(255, 255, 255, 0.2);
|
|
323
|
+
font-weight: 800;
|
|
324
|
+
}
|
|
325
|
+
body.autolaunch-page .autolaunch-primary:disabled .autolaunch-primary-script {
|
|
326
|
+
background: transparent;
|
|
327
|
+
}
|
|
328
|
+
body.autolaunch-page .autolaunch-manual {
|
|
329
|
+
padding: 16px;
|
|
330
|
+
border-top: 1px solid var(--task-border);
|
|
331
|
+
}
|
|
332
|
+
body.autolaunch-page .autolaunch-manual-row {
|
|
333
|
+
display: flex;
|
|
334
|
+
align-items: center;
|
|
335
|
+
gap: 8px;
|
|
336
|
+
}
|
|
337
|
+
body.autolaunch-page .autolaunch-manual input {
|
|
338
|
+
flex: 1 1 auto;
|
|
339
|
+
min-height: 32px;
|
|
340
|
+
padding: 0 10px;
|
|
341
|
+
border: 1px solid var(--task-border-strong);
|
|
342
|
+
border-radius: 7px;
|
|
343
|
+
background: color-mix(in srgb, var(--task-panel) 98%, white);
|
|
344
|
+
}
|
|
345
|
+
body.dark.autolaunch-page .autolaunch-manual input {
|
|
346
|
+
background: color-mix(in srgb, var(--task-panel) 95%, black);
|
|
347
|
+
}
|
|
@@ -11,7 +11,10 @@
|
|
|
11
11
|
<div class='home-actions-heading'>
|
|
12
12
|
<div class='home-actions-title-row'>
|
|
13
13
|
<h2 id='<%= id %>-title'><%= item.name %></h2>
|
|
14
|
-
<% if (item.
|
|
14
|
+
<% if (item.autolaunch_starting) { %>
|
|
15
|
+
<% const homeActionsAutolaunchLabel = item.autolaunch_status_label || (item.autolaunch_blocked ? "Blocked" : item.autolaunch_waiting ? "Waiting" : "Starting") %>
|
|
16
|
+
<span class='home-actions-status home-actions-status--starting' title='<%= homeActionsAutolaunchLabel %>'><%= homeActionsAutolaunchLabel %></span>
|
|
17
|
+
<% } else if (item.running) { %>
|
|
15
18
|
<span class='home-actions-status'>Running</span>
|
|
16
19
|
<% } %>
|
|
17
20
|
</div>
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
;(function() {
|
|
2
|
+
const root = document.querySelector("[data-launch-requirements-status]")
|
|
3
|
+
const appId = <%- JSON.stringify((typeof name !== "undefined" && name) ? name : "") %>
|
|
4
|
+
const appTitle = <%- JSON.stringify((typeof config !== "undefined" && config && config.title) || (typeof name !== "undefined" && name) || "app") %>
|
|
5
|
+
const initialEnabled = <%- JSON.stringify(!!(typeof launch_requirements_status_enabled !== "undefined" && launch_requirements_status_enabled)) %>
|
|
6
|
+
if (!root || !appId) return
|
|
7
|
+
const shouldDeferToParentLaunchRequirements = () => {
|
|
8
|
+
try {
|
|
9
|
+
if (window.self === window.top) return false
|
|
10
|
+
const parentDocument = window.parent && window.parent.document
|
|
11
|
+
return !!(parentDocument && parentDocument.querySelector("[data-launch-requirements-status]"))
|
|
12
|
+
} catch (_) {
|
|
13
|
+
return false
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
if (shouldDeferToParentLaunchRequirements()) return
|
|
17
|
+
let cancelling = false
|
|
18
|
+
const escapeHtml = (value) => {
|
|
19
|
+
if (value === null || value === undefined) return ""
|
|
20
|
+
return String(value).replace(/[&<>"']/g, (match) => {
|
|
21
|
+
switch (match) {
|
|
22
|
+
case "&": return "&"
|
|
23
|
+
case "<": return "<"
|
|
24
|
+
case ">": return ">"
|
|
25
|
+
case "\"": return """
|
|
26
|
+
case "'": return "'"
|
|
27
|
+
default: return match
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
const stepLabel = (row) => {
|
|
32
|
+
const current = Number(row && row.step_current)
|
|
33
|
+
const total = Number(row && row.step_total)
|
|
34
|
+
return Number.isInteger(current) && Number.isInteger(total) && total > 1 ? ` (${current}/${total})` : ""
|
|
35
|
+
}
|
|
36
|
+
const titleForId = (id, titleById) => {
|
|
37
|
+
if (!id) return ""
|
|
38
|
+
return titleById[id] || id
|
|
39
|
+
}
|
|
40
|
+
const waitingForLabel = (row, titleById) => {
|
|
41
|
+
const waitingFor = row && Array.isArray(row.waiting_for) ? row.waiting_for : []
|
|
42
|
+
const names = waitingFor
|
|
43
|
+
.map((id) => titleForId(id, titleById))
|
|
44
|
+
.filter(Boolean)
|
|
45
|
+
return names.length > 0 ? `Waiting for ${names.join(", ")}` : "Waiting"
|
|
46
|
+
}
|
|
47
|
+
const stateLabel = (row, titleById) => {
|
|
48
|
+
const script = row && row.script ? row.script : "script"
|
|
49
|
+
if (row && row.state === "ready") return "Ready"
|
|
50
|
+
if (row && row.state === "blocked") return row.blocked_reason || "Needs setup"
|
|
51
|
+
if (row && row.state === "waiting") return waitingForLabel(row, titleById)
|
|
52
|
+
if (row && row.state === "pending") return "Pending"
|
|
53
|
+
return `Starting ${script}${stepLabel(row)}`
|
|
54
|
+
}
|
|
55
|
+
const isActiveRow = (row) => {
|
|
56
|
+
if (!row) return false
|
|
57
|
+
return row.state === "starting"
|
|
58
|
+
}
|
|
59
|
+
const isReadyRow = (row) => {
|
|
60
|
+
if (!row) return false
|
|
61
|
+
return row.state === "ready"
|
|
62
|
+
}
|
|
63
|
+
const stateClass = (row) => {
|
|
64
|
+
if (isActiveRow(row)) return " is-active"
|
|
65
|
+
if (isReadyRow(row)) return " is-ready"
|
|
66
|
+
return ""
|
|
67
|
+
}
|
|
68
|
+
const stateIcon = (row) => {
|
|
69
|
+
if (isActiveRow(row)) {
|
|
70
|
+
return '<i class="fa-solid fa-circle-notch fa-spin" aria-hidden="true"></i>'
|
|
71
|
+
}
|
|
72
|
+
if (isReadyRow(row)) {
|
|
73
|
+
return '<i class="fa-solid fa-check" aria-hidden="true"></i>'
|
|
74
|
+
}
|
|
75
|
+
return ""
|
|
76
|
+
}
|
|
77
|
+
const render = (status) => {
|
|
78
|
+
if (!status) {
|
|
79
|
+
root.hidden = true
|
|
80
|
+
root.innerHTML = ""
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
const rawRequirements = Array.isArray(status.requirements) ? status.requirements : []
|
|
84
|
+
const requirements = rawRequirements
|
|
85
|
+
if (requirements.length === 0 && status.state !== "blocked") {
|
|
86
|
+
root.hidden = true
|
|
87
|
+
root.innerHTML = ""
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
const waiting = requirements.filter((row) => row && row.state !== "ready")
|
|
91
|
+
if (waiting.length === 0 && status.state !== "blocked") {
|
|
92
|
+
root.hidden = true
|
|
93
|
+
root.innerHTML = ""
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
const title = status.state === "blocked" ? "Launch needs setup" : "Preparing required apps"
|
|
97
|
+
const detail = status.state === "blocked" && status.blocked_reason
|
|
98
|
+
? status.blocked_reason
|
|
99
|
+
: waiting.length > 0
|
|
100
|
+
? `Waiting for ${waiting.map((row) => row.title || row.id).join(", ")}.`
|
|
101
|
+
: (status.blocked_reason || `${status.title || appTitle} will start after its requirements are ready.`)
|
|
102
|
+
const titleById = requirements.reduce((acc, row) => {
|
|
103
|
+
if (row && row.id) {
|
|
104
|
+
acc[row.id] = row.title || row.id
|
|
105
|
+
}
|
|
106
|
+
return acc
|
|
107
|
+
}, {})
|
|
108
|
+
const canCancel = true
|
|
109
|
+
root.innerHTML = `
|
|
110
|
+
<div class="launch-requirements-card">
|
|
111
|
+
<div class="launch-requirements-title">
|
|
112
|
+
<span>${escapeHtml(title)}</span>
|
|
113
|
+
</div>
|
|
114
|
+
<div class="launch-requirements-detail">${escapeHtml(detail)}</div>
|
|
115
|
+
${requirements.length > 0 ? `
|
|
116
|
+
<div class="launch-requirements-list">
|
|
117
|
+
${requirements.map((row) => `
|
|
118
|
+
<div class="launch-requirements-row">
|
|
119
|
+
<img src="${escapeHtml(row.icon || "/pinokio-black.png")}" alt="" onerror="this.onerror=null; this.src='/pinokio-black.png'">
|
|
120
|
+
<div class="launch-requirements-name">${escapeHtml(row.title || row.id)}</div>
|
|
121
|
+
<div class="launch-requirements-state${stateClass(row)}">
|
|
122
|
+
${stateIcon(row)}
|
|
123
|
+
<span>${escapeHtml(stateLabel(row, titleById))}</span>
|
|
124
|
+
${row && row.state === "blocked" ? `
|
|
125
|
+
<button type="button" class="launch-requirements-choose-script" data-launch-requirements-choose-script="${escapeHtml(row.id || "")}">
|
|
126
|
+
<i class="fa-solid fa-file-code" aria-hidden="true"></i>
|
|
127
|
+
<span>Choose script</span>
|
|
128
|
+
</button>
|
|
129
|
+
` : ""}
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
`).join("")}
|
|
133
|
+
</div>
|
|
134
|
+
` : ""}
|
|
135
|
+
${canCancel ? `
|
|
136
|
+
<div class="launch-requirements-actions">
|
|
137
|
+
<button type="button" class="launch-requirements-stop" data-launch-requirements-stop ${cancelling ? "disabled" : ""}>
|
|
138
|
+
<i class="fa-solid ${cancelling ? "fa-circle-notch fa-spin" : "fa-stop"}" aria-hidden="true"></i>
|
|
139
|
+
<span>${cancelling ? "Stopping launch..." : "Stop launch"}</span>
|
|
140
|
+
</button>
|
|
141
|
+
</div>
|
|
142
|
+
` : ""}
|
|
143
|
+
</div>
|
|
144
|
+
`
|
|
145
|
+
root.hidden = false
|
|
146
|
+
}
|
|
147
|
+
const notifyLaunchRequirementsControl = (detail = {}) => {
|
|
148
|
+
const payload = {
|
|
149
|
+
type: "pinokio:launch-requirements-control",
|
|
150
|
+
detail
|
|
151
|
+
}
|
|
152
|
+
try {
|
|
153
|
+
window.dispatchEvent(new CustomEvent(payload.type, { detail }))
|
|
154
|
+
} catch (_) {}
|
|
155
|
+
try {
|
|
156
|
+
for (let i = 0; i < window.frames.length; i++) {
|
|
157
|
+
window.frames[i].postMessage(payload, "*")
|
|
158
|
+
}
|
|
159
|
+
} catch (_) {}
|
|
160
|
+
}
|
|
161
|
+
const cancelLaunch = async () => {
|
|
162
|
+
if (cancelling) return
|
|
163
|
+
cancelling = true
|
|
164
|
+
const button = root.querySelector("[data-launch-requirements-stop]")
|
|
165
|
+
if (button) {
|
|
166
|
+
button.disabled = true
|
|
167
|
+
button.innerHTML = '<i class="fa-solid fa-circle-notch fa-spin" aria-hidden="true"></i><span>Stopping launch...</span>'
|
|
168
|
+
}
|
|
169
|
+
try {
|
|
170
|
+
const response = await fetch(`/pinokio/launch-requirements/${encodeURIComponent(appId)}/cancel`, {
|
|
171
|
+
method: "POST",
|
|
172
|
+
headers: { "Accept": "application/json" },
|
|
173
|
+
cache: "no-store"
|
|
174
|
+
})
|
|
175
|
+
const data = await response.json()
|
|
176
|
+
if (!response.ok || !data || !data.ok) {
|
|
177
|
+
throw new Error(data && data.error ? data.error : "Failed to stop launch.")
|
|
178
|
+
}
|
|
179
|
+
notifyLaunchRequirementsControl({
|
|
180
|
+
action: "cancelled",
|
|
181
|
+
app_id: appId
|
|
182
|
+
})
|
|
183
|
+
cancelling = false
|
|
184
|
+
render(null)
|
|
185
|
+
try {
|
|
186
|
+
if (typeof refresh === "function") {
|
|
187
|
+
refresh(true, { nodelay: true })
|
|
188
|
+
}
|
|
189
|
+
} catch (_) {}
|
|
190
|
+
} catch (_) {
|
|
191
|
+
cancelling = false
|
|
192
|
+
fetchInitialStatus()
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
root.addEventListener("click", (event) => {
|
|
196
|
+
const chooseScript = event.target && event.target.closest("[data-launch-requirements-choose-script]")
|
|
197
|
+
if (chooseScript) {
|
|
198
|
+
event.preventDefault()
|
|
199
|
+
const dependencyApp = chooseScript.getAttribute("data-launch-requirements-choose-script") || ""
|
|
200
|
+
if (!dependencyApp) return
|
|
201
|
+
const chooseEvent = new CustomEvent("pinokio:launch-requirements-choose-script", {
|
|
202
|
+
cancelable: true,
|
|
203
|
+
detail: {
|
|
204
|
+
app: dependencyApp
|
|
205
|
+
}
|
|
206
|
+
})
|
|
207
|
+
window.dispatchEvent(chooseEvent)
|
|
208
|
+
if (!chooseEvent.defaultPrevented) {
|
|
209
|
+
window.location.href = `/v/${encodeURIComponent(dependencyApp)}?autolaunch=1`
|
|
210
|
+
}
|
|
211
|
+
return
|
|
212
|
+
}
|
|
213
|
+
const stop = event.target && event.target.closest("[data-launch-requirements-stop]")
|
|
214
|
+
if (!stop) return
|
|
215
|
+
event.preventDefault()
|
|
216
|
+
cancelLaunch()
|
|
217
|
+
})
|
|
218
|
+
const renderStatusPayload = (data) => {
|
|
219
|
+
const status = data && data.ok ? data.status : null
|
|
220
|
+
if (status) {
|
|
221
|
+
cancelling = false
|
|
222
|
+
render(status)
|
|
223
|
+
return
|
|
224
|
+
}
|
|
225
|
+
render(null)
|
|
226
|
+
}
|
|
227
|
+
const fetchInitialStatus = async () => {
|
|
228
|
+
try {
|
|
229
|
+
const response = await fetch(`/pinokio/launch-requirements/${encodeURIComponent(appId)}`, {
|
|
230
|
+
headers: { "Accept": "application/json" },
|
|
231
|
+
cache: "no-store"
|
|
232
|
+
})
|
|
233
|
+
const data = await response.json()
|
|
234
|
+
renderStatusPayload(response.ok && data ? data : null)
|
|
235
|
+
} catch (_) {
|
|
236
|
+
renderStatusPayload(null)
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
const startStatusClient = () => {
|
|
240
|
+
if (root.dataset.launchRequirementsClient === "started") return
|
|
241
|
+
root.dataset.launchRequirementsClient = "started"
|
|
242
|
+
fetchInitialStatus()
|
|
243
|
+
const channel = `kernel.launch_requirements:${appId}`
|
|
244
|
+
let socket = null
|
|
245
|
+
try {
|
|
246
|
+
socket = new Socket()
|
|
247
|
+
socket.run({
|
|
248
|
+
method: channel,
|
|
249
|
+
mode: "listen"
|
|
250
|
+
}, (packet) => {
|
|
251
|
+
if (!packet || packet.type !== "launch.requirements") {
|
|
252
|
+
return
|
|
253
|
+
}
|
|
254
|
+
renderStatusPayload({
|
|
255
|
+
ok: true,
|
|
256
|
+
status: packet.data ? packet.data.status : null
|
|
257
|
+
})
|
|
258
|
+
}).catch(() => {})
|
|
259
|
+
window.addEventListener("beforeunload", () => {
|
|
260
|
+
if (socket && typeof socket.close === "function") {
|
|
261
|
+
socket.close()
|
|
262
|
+
}
|
|
263
|
+
}, { once: true })
|
|
264
|
+
} catch (_) {}
|
|
265
|
+
}
|
|
266
|
+
if (initialEnabled) {
|
|
267
|
+
startStatusClient()
|
|
268
|
+
} else {
|
|
269
|
+
window.addEventListener("pinokio:launch-requirements-configured", startStatusClient, { once: true })
|
|
270
|
+
}
|
|
271
|
+
})()
|