pinokiod 7.3.15 → 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/views/app.ejs +256 -152
- package/server/views/autolaunch.ejs +363 -75
- package/server/views/index.ejs +550 -26
- 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
package/server/views/index.ejs
CHANGED
|
@@ -215,6 +215,42 @@ body.dark .home-app-line .menu-btns > .btn:focus-visible {
|
|
|
215
215
|
background: rgba(255, 255, 255, 0.07) !important;
|
|
216
216
|
color: rgba(248, 250, 252, 0.9) !important;
|
|
217
217
|
}
|
|
218
|
+
.home-autolaunch-status {
|
|
219
|
+
min-height: 22px;
|
|
220
|
+
box-sizing: border-box;
|
|
221
|
+
display: inline-flex;
|
|
222
|
+
align-items: center;
|
|
223
|
+
gap: 6px;
|
|
224
|
+
padding: 0 7px;
|
|
225
|
+
border: 1px solid rgba(180, 83, 9, 0.18);
|
|
226
|
+
border-radius: 6px;
|
|
227
|
+
background: rgba(180, 83, 9, 0.07);
|
|
228
|
+
color: rgba(120, 53, 15, 0.9);
|
|
229
|
+
font-size: 12px;
|
|
230
|
+
font-weight: 600;
|
|
231
|
+
line-height: 1;
|
|
232
|
+
}
|
|
233
|
+
.home-autolaunch-status i {
|
|
234
|
+
color: inherit !important;
|
|
235
|
+
font-size: 11px;
|
|
236
|
+
}
|
|
237
|
+
body.dark .home-autolaunch-status {
|
|
238
|
+
border-color: rgba(214, 164, 81, 0.22);
|
|
239
|
+
background: rgba(214, 164, 81, 0.1);
|
|
240
|
+
color: rgba(252, 211, 146, 0.92);
|
|
241
|
+
}
|
|
242
|
+
.home-app-line.autolaunch-starting .title i {
|
|
243
|
+
color: #b45309 !important;
|
|
244
|
+
}
|
|
245
|
+
body.dark .home-app-line.autolaunch-starting .title i {
|
|
246
|
+
color: #d6a451 !important;
|
|
247
|
+
}
|
|
248
|
+
.home-app-line.autolaunch-blocked .title i {
|
|
249
|
+
color: #dc2626 !important;
|
|
250
|
+
}
|
|
251
|
+
body.dark .home-app-line.autolaunch-blocked .title i {
|
|
252
|
+
color: #f87171 !important;
|
|
253
|
+
}
|
|
218
254
|
.context-menu {
|
|
219
255
|
position: absolute;
|
|
220
256
|
right: auto;
|
|
@@ -902,6 +938,7 @@ body.dark .home-actions-path {
|
|
|
902
938
|
display: inline-flex;
|
|
903
939
|
align-items: center;
|
|
904
940
|
flex: 0 0 auto;
|
|
941
|
+
max-width: min(260px, 45vw);
|
|
905
942
|
height: 20px;
|
|
906
943
|
padding: 0 7px;
|
|
907
944
|
border-radius: 999px;
|
|
@@ -910,6 +947,9 @@ body.dark .home-actions-path {
|
|
|
910
947
|
font-size: 11px;
|
|
911
948
|
font-weight: 700;
|
|
912
949
|
line-height: 1;
|
|
950
|
+
overflow: hidden;
|
|
951
|
+
text-overflow: ellipsis;
|
|
952
|
+
white-space: nowrap;
|
|
913
953
|
}
|
|
914
954
|
body.dark .home-actions-status,
|
|
915
955
|
body.dark .home-run-menu-status,
|
|
@@ -917,6 +957,14 @@ body.dark .home-actions-count {
|
|
|
917
957
|
background: var(--pinokio-chrome-accent-bg-dark);
|
|
918
958
|
color: var(--pinokio-chrome-accent-fg-dark);
|
|
919
959
|
}
|
|
960
|
+
.home-actions-status--starting {
|
|
961
|
+
background: rgba(180, 83, 9, 0.08);
|
|
962
|
+
color: rgba(120, 53, 15, 0.92);
|
|
963
|
+
}
|
|
964
|
+
body.dark .home-actions-status--starting {
|
|
965
|
+
background: rgba(214, 164, 81, 0.12);
|
|
966
|
+
color: rgba(252, 211, 146, 0.95);
|
|
967
|
+
}
|
|
920
968
|
.home-actions-close {
|
|
921
969
|
width: 30px;
|
|
922
970
|
height: 30px;
|
|
@@ -1373,7 +1421,7 @@ body.dark aside .current.selected {
|
|
|
1373
1421
|
<% if (running.length > 0) { %>
|
|
1374
1422
|
<div class='running-apps'>
|
|
1375
1423
|
<% running.forEach((item, index) => { %>
|
|
1376
|
-
<div data-href="<%=item.browser_url%>" data-target="<%=item.target || '_self'%>" data-description="<%=item.description%>" data-index="<%=item.index%>" data-name="<%=item.name%>" data-title="<%=item.name%>" data-uri="<%=item.uri%>" data-icon="<%=item.icon%>" data-path="<%=item.path%>" data-iconpath="<%=item.iconpath ? item.iconpath : ''%>" data-starred="<%=item.starred ? "1" : "0"%>" data-launch-count-total="<%=item.launch_count_total || 0%>" data-last-launch-at="<%=item.last_launch_at || ''%>" class='line align-top home-app-line' role='link' tabindex='0'>
|
|
1424
|
+
<div data-href="<%=item.browser_url%>" data-target="<%=item.target || '_self'%>" data-description="<%=item.description%>" data-index="<%=item.index%>" data-name="<%=item.name%>" data-title="<%=item.name%>" data-uri="<%=item.uri%>" data-icon="<%=item.icon%>" data-path="<%=item.path%>" data-iconpath="<%=item.iconpath ? item.iconpath : ''%>" data-starred="<%=item.starred ? "1" : "0"%>" data-launch-count-total="<%=item.launch_count_total || 0%>" data-last-launch-at="<%=item.last_launch_at || ''%>" data-autolaunch-starting="<%=item.autolaunch_starting ? "1" : "0"%>" data-autolaunch-app="<%=item.uri%>" data-autolaunch-script="<%=item.autolaunch_script || ''%>" class='line align-top home-app-line <%=item.autolaunch_starting ? "autolaunch-starting" : ""%> <%=item.autolaunch_waiting ? "autolaunch-waiting" : ""%> <%=item.autolaunch_blocked ? "autolaunch-blocked" : ""%>' role='link' tabindex='0'>
|
|
1377
1425
|
<h3>
|
|
1378
1426
|
<div class='item-icon'>
|
|
1379
1427
|
<% if (item.icon) { %>
|
|
@@ -1407,6 +1455,14 @@ body.dark aside .current.selected {
|
|
|
1407
1455
|
<button class='btn open-menu open-actions-modal' type='button' data-dialog-id="home-actions-running-<%=index%>" aria-haspopup="dialog" title="Actions" aria-label="Actions">
|
|
1408
1456
|
<i class="fa-solid fa-ellipsis" aria-hidden="true"></i><span> menu</span>
|
|
1409
1457
|
</button>
|
|
1458
|
+
<% const autolaunchRunningScript = !!(item.autolaunch_starting && item.autolaunch_script && item.running_scripts && item.running_scripts.some((s) => { const scriptName = s && (s.name || s.script_path || s.path || ""); return scriptName === item.autolaunch_script || String(scriptName).endsWith(`/${item.autolaunch_script}`) })) %>
|
|
1459
|
+
<% if (item.autolaunch_starting && !autolaunchRunningScript) { %>
|
|
1460
|
+
<% const autolaunchStatusLabel = item.autolaunch_status_label || "Starting automatically" %>
|
|
1461
|
+
<span class='home-autolaunch-status' title='<%= autolaunchStatusLabel %>'>
|
|
1462
|
+
<i class='fa-solid <%= item.autolaunch_blocked ? "fa-triangle-exclamation" : "fa-circle-notch fa-spin" %>' aria-hidden='true'></i>
|
|
1463
|
+
<span><%= autolaunchStatusLabel %></span>
|
|
1464
|
+
</span>
|
|
1465
|
+
<% } %>
|
|
1410
1466
|
<% const runningTerminalOnlineCount = Number(item.terminal_online_count || 0) %>
|
|
1411
1467
|
<% const runningTerminalLabel = runningTerminalOnlineCount > 0 ? `${runningTerminalOnlineCount} terminal${runningTerminalOnlineCount === 1 ? "" : "s"} online` : "Terminals" %>
|
|
1412
1468
|
<% if (runningTerminalOnlineCount > 0) { %>
|
|
@@ -1419,17 +1475,26 @@ body.dark aside .current.selected {
|
|
|
1419
1475
|
<% if (item.running_scripts) { %>
|
|
1420
1476
|
<% item.running_scripts.forEach((s) => { %>
|
|
1421
1477
|
<% const runningScriptName = s.name || "script" %>
|
|
1478
|
+
<% const isStartingAutolaunchScript = !!(item.autolaunch_starting && !item.autolaunch_waiting && !item.autolaunch_blocked && item.autolaunch_script && (runningScriptName === item.autolaunch_script || String(runningScriptName).endsWith(`/${item.autolaunch_script}`))) %>
|
|
1479
|
+
<% const stepCurrent = Number(isStartingAutolaunchScript && (s.step_current || item.autolaunch_step_current)) %>
|
|
1480
|
+
<% const stepTotal = Number(isStartingAutolaunchScript && (s.step_total || item.autolaunch_step_total)) %>
|
|
1481
|
+
<% const stepLabel = Number.isFinite(stepCurrent) && Number.isFinite(stepTotal) && stepCurrent > 0 && stepTotal > 1 ? ` (${stepCurrent}/${stepTotal})` : "" %>
|
|
1482
|
+
<% const runningScriptButtonLabel = isStartingAutolaunchScript ? `Starting ${runningScriptName}${stepLabel}` : `Stop ${runningScriptName}` %>
|
|
1422
1483
|
<% if (s.id) { %>
|
|
1423
1484
|
<button class='btn shutdown' type='button' data-id="<%=s.id%>" data-type="<%=s.type ? s.type : ''%>" title="Stop <%=runningScriptName%>" aria-label="Stop <%=runningScriptName%>">
|
|
1424
1485
|
<i class="fa-solid fa-stop" aria-hidden="true"></i>
|
|
1425
|
-
<span
|
|
1426
|
-
|
|
1486
|
+
<span><%= runningScriptButtonLabel %></span>
|
|
1487
|
+
<% if (isStartingAutolaunchScript) { %>
|
|
1488
|
+
<i class='home-stop-spinner fa-solid fa-spin fa-circle-notch' aria-hidden='true'></i>
|
|
1489
|
+
<% } %>
|
|
1427
1490
|
</button>
|
|
1428
1491
|
<% } else { %>
|
|
1429
1492
|
<button class='btn shutdown' type='button' data-src="<%=s.path%>" title="Stop <%=runningScriptName%>" aria-label="Stop <%=runningScriptName%>">
|
|
1430
1493
|
<i class="fa-solid fa-stop" aria-hidden="true"></i>
|
|
1431
|
-
<span
|
|
1432
|
-
|
|
1494
|
+
<span><%= runningScriptButtonLabel %></span>
|
|
1495
|
+
<% if (isStartingAutolaunchScript) { %>
|
|
1496
|
+
<i class='home-stop-spinner fa-solid fa-spin fa-circle-notch' aria-hidden='true'></i>
|
|
1497
|
+
<% } %>
|
|
1433
1498
|
</button>
|
|
1434
1499
|
<% } %>
|
|
1435
1500
|
<% }) %>
|
|
@@ -1454,7 +1519,7 @@ body.dark aside .current.selected {
|
|
|
1454
1519
|
<% if (notRunning.length > 0) { %>
|
|
1455
1520
|
<div class='not-running-apps'>
|
|
1456
1521
|
<% notRunning.forEach((item, index) => { %>
|
|
1457
|
-
<div data-href="<%=item.browser_url%>" data-target="<%=item.target || '_self'%>" data-description="<%=item.description%>" data-index="<%=item.index%>" data-name="<%=item.name%>" data-title="<%=item.name%>" data-uri="<%=item.uri%>" data-icon="<%=item.icon%>" data-path="<%=item.path%>" data-iconpath="<%=item.iconpath ? item.iconpath : ''%>" data-starred="<%=item.starred ? "1" : "0"%>" data-launch-count-total="<%=item.launch_count_total || 0%>" data-last-launch-at="<%=item.last_launch_at || ''%>" class='line align-top home-app-line' role='link' tabindex='0'>
|
|
1522
|
+
<div data-href="<%=item.browser_url%>" data-target="<%=item.target || '_self'%>" data-description="<%=item.description%>" data-index="<%=item.index%>" data-name="<%=item.name%>" data-title="<%=item.name%>" data-uri="<%=item.uri%>" data-icon="<%=item.icon%>" data-path="<%=item.path%>" data-iconpath="<%=item.iconpath ? item.iconpath : ''%>" data-starred="<%=item.starred ? "1" : "0"%>" data-launch-count-total="<%=item.launch_count_total || 0%>" data-last-launch-at="<%=item.last_launch_at || ''%>" data-autolaunch-starting="<%=item.autolaunch_starting ? "1" : "0"%>" data-autolaunch-app="<%=item.uri%>" data-autolaunch-script="<%=item.autolaunch_script || ''%>" class='line align-top home-app-line <%=item.autolaunch_starting ? "autolaunch-starting" : ""%> <%=item.autolaunch_waiting ? "autolaunch-waiting" : ""%> <%=item.autolaunch_blocked ? "autolaunch-blocked" : ""%>' role='link' tabindex='0'>
|
|
1458
1523
|
<h3>
|
|
1459
1524
|
<div class='item-icon'>
|
|
1460
1525
|
<% if (item.icon) { %>
|
|
@@ -1960,23 +2025,467 @@ if (homeSortSelect) {
|
|
|
1960
2025
|
})
|
|
1961
2026
|
}
|
|
1962
2027
|
<% if (ishome) { %>
|
|
1963
|
-
//
|
|
1964
|
-
//
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
2028
|
+
// Autolaunch display update. It is always installed on home because startup
|
|
2029
|
+
// status can appear after the initial HTML render.
|
|
2030
|
+
let refreshHomeStatusOnce = null
|
|
2031
|
+
const startHomeAutolaunchPolling = () => {
|
|
2032
|
+
const getStartingLines = () => Array.from(document.querySelectorAll('[data-autolaunch-starting="1"]'))
|
|
2033
|
+
const hasVisibleStartupRows = () => getStartingLines().length > 0
|
|
2034
|
+
const initialLaunchComplete = <%= launch_complete ? "true" : "false" %>
|
|
2035
|
+
const getHomeAppLines = () => Array.from(document.querySelectorAll(".home-app-line[data-autolaunch-app]"))
|
|
2036
|
+
const activeHomeAutolaunchApps = new Set()
|
|
2037
|
+
const isPreparingAutolaunchState = (status) => {
|
|
2038
|
+
return !!(status && status.state !== "ready")
|
|
2039
|
+
}
|
|
2040
|
+
const ownerIds = (status) => {
|
|
2041
|
+
if (!status) {
|
|
2042
|
+
return []
|
|
2043
|
+
}
|
|
2044
|
+
const ids = []
|
|
2045
|
+
const add = (value) => {
|
|
2046
|
+
const id = String(value || "").trim()
|
|
2047
|
+
if (id && !ids.includes(id)) {
|
|
2048
|
+
ids.push(id)
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
if (Array.isArray(status.owner_app_ids)) {
|
|
2052
|
+
status.owner_app_ids.forEach(add)
|
|
2053
|
+
}
|
|
2054
|
+
add(status.owner_app_id)
|
|
2055
|
+
return ids
|
|
2056
|
+
}
|
|
2057
|
+
const hasActiveOwner = (status, apps, runningApps) => {
|
|
2058
|
+
const owners = ownerIds(status)
|
|
2059
|
+
return owners.length === 0 || owners.some((ownerId) => !!apps[ownerId] || runningApps.has(ownerId))
|
|
2060
|
+
}
|
|
2061
|
+
const shouldDisplayAutolaunchStatus = (app, status, apps, runningApps) => {
|
|
2062
|
+
if (!status) {
|
|
2063
|
+
return false
|
|
2064
|
+
}
|
|
2065
|
+
if (runningApps.has(app)) {
|
|
2066
|
+
return true
|
|
2067
|
+
}
|
|
2068
|
+
return isPreparingAutolaunchState(status) && hasActiveOwner(status, apps, runningApps)
|
|
2069
|
+
}
|
|
2070
|
+
const getAutolaunchLines = (state) => {
|
|
2071
|
+
const lines = new Set(getStartingLines())
|
|
2072
|
+
const apps = state && state.autolaunch && state.autolaunch.apps ? state.autolaunch.apps : {}
|
|
2073
|
+
const runningApps = new Set(Array.isArray(state && state.running_apps) ? state.running_apps : [])
|
|
2074
|
+
const trackedApps = new Set(Object.entries(apps)
|
|
2075
|
+
.filter(([app, status]) => shouldDisplayAutolaunchStatus(app, status, apps, runningApps))
|
|
2076
|
+
.map(([app]) => app))
|
|
2077
|
+
for (const app of runningApps) {
|
|
2078
|
+
trackedApps.add(app)
|
|
2079
|
+
}
|
|
2080
|
+
for (const app of activeHomeAutolaunchApps) {
|
|
2081
|
+
trackedApps.add(app)
|
|
2082
|
+
}
|
|
2083
|
+
if (trackedApps.size > 0) {
|
|
2084
|
+
for (const line of getHomeAppLines()) {
|
|
2085
|
+
if (trackedApps.has(line.getAttribute("data-autolaunch-app"))) {
|
|
2086
|
+
lines.add(line)
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
2089
|
+
}
|
|
2090
|
+
return Array.from(lines)
|
|
2091
|
+
}
|
|
2092
|
+
let attempts = 0
|
|
2093
|
+
const maxPollAttempts = 240
|
|
2094
|
+
let interval = null
|
|
2095
|
+
let hadStartingLines = getStartingLines().length > 0
|
|
2096
|
+
const stopPolling = () => {
|
|
2097
|
+
if (interval) {
|
|
2098
|
+
clearInterval(interval)
|
|
2099
|
+
interval = null
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
2102
|
+
const scriptIdentity = (script) => {
|
|
2103
|
+
if (!script) {
|
|
2104
|
+
return ""
|
|
2105
|
+
}
|
|
2106
|
+
return script.home_path || script.id || script.path || script.script_path || ""
|
|
2107
|
+
}
|
|
2108
|
+
const scriptName = (script) => {
|
|
2109
|
+
return (script && (script.script_path || script.name || script.path)) || "script"
|
|
2110
|
+
}
|
|
2111
|
+
const scriptStepLabel = (source) => {
|
|
2112
|
+
const stepCurrent = Number(source && source.step_current)
|
|
2113
|
+
const stepTotal = Number(source && source.step_total)
|
|
2114
|
+
if (!Number.isFinite(stepCurrent) || !Number.isFinite(stepTotal) || stepCurrent <= 0 || stepTotal <= 1) {
|
|
2115
|
+
return ""
|
|
2116
|
+
}
|
|
2117
|
+
return ` (${stepCurrent}/${stepTotal})`
|
|
2118
|
+
}
|
|
2119
|
+
const scriptMatches = (script, expected) => {
|
|
2120
|
+
const name = scriptName(script)
|
|
2121
|
+
return !!expected && (name === expected || String(name).endsWith(`/${expected}`))
|
|
2122
|
+
}
|
|
2123
|
+
const syncStopButtonSpinner = (button, show) => {
|
|
2124
|
+
let spinner = button.querySelector(".home-stop-spinner")
|
|
2125
|
+
if (show) {
|
|
2126
|
+
if (!spinner) {
|
|
2127
|
+
spinner = document.createElement("i")
|
|
2128
|
+
spinner.className = "home-stop-spinner fa-solid fa-spin fa-circle-notch"
|
|
2129
|
+
spinner.setAttribute("aria-hidden", "true")
|
|
2130
|
+
button.appendChild(spinner)
|
|
2131
|
+
}
|
|
2132
|
+
} else if (spinner) {
|
|
2133
|
+
spinner.remove()
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
const createStopButton = (script, displayLabel = "") => {
|
|
2137
|
+
const label = (script && (script.script_path || script.path)) || "script"
|
|
2138
|
+
const button = document.createElement("button")
|
|
2139
|
+
button.type = "button"
|
|
2140
|
+
button.className = "btn shutdown"
|
|
2141
|
+
button.title = `Stop ${label}`
|
|
2142
|
+
button.setAttribute("aria-label", `Stop ${label}`)
|
|
2143
|
+
if (script && script.home_path) {
|
|
2144
|
+
button.setAttribute("data-src", script.home_path)
|
|
2145
|
+
} else if (script && script.id) {
|
|
2146
|
+
button.setAttribute("data-id", script.id)
|
|
2147
|
+
}
|
|
2148
|
+
|
|
2149
|
+
const stopIcon = document.createElement("i")
|
|
2150
|
+
stopIcon.className = "fa-solid fa-stop"
|
|
2151
|
+
stopIcon.setAttribute("aria-hidden", "true")
|
|
2152
|
+
button.appendChild(stopIcon)
|
|
2153
|
+
|
|
2154
|
+
const text = document.createElement("span")
|
|
2155
|
+
text.textContent = displayLabel || `Stop ${label}`
|
|
2156
|
+
button.appendChild(text)
|
|
2157
|
+
|
|
2158
|
+
syncStopButtonSpinner(button, !!displayLabel)
|
|
2159
|
+
return button
|
|
2160
|
+
}
|
|
2161
|
+
const updateStopButton = (button, script, displayLabel = "") => {
|
|
2162
|
+
const label = (script && (script.script_path || script.path)) || "script"
|
|
2163
|
+
button.title = `Stop ${label}`
|
|
2164
|
+
button.setAttribute("aria-label", `Stop ${label}`)
|
|
2165
|
+
const text = button.querySelector("span")
|
|
2166
|
+
if (text) {
|
|
2167
|
+
text.textContent = displayLabel || `Stop ${label}`
|
|
2168
|
+
}
|
|
2169
|
+
syncStopButtonSpinner(button, !!displayLabel)
|
|
2170
|
+
}
|
|
2171
|
+
const syncStopButtons = (line, scripts, displayLabelForScript = null) => {
|
|
2172
|
+
const menuButtons = line.querySelector(".menu-btns")
|
|
2173
|
+
if (!menuButtons || !Array.isArray(scripts)) {
|
|
2174
|
+
return
|
|
2175
|
+
}
|
|
2176
|
+
const existing = new Map(Array.from(menuButtons.querySelectorAll(".shutdown")).map((button) => {
|
|
2177
|
+
return [button.getAttribute("data-src") || button.getAttribute("data-id") || "", button]
|
|
2178
|
+
}))
|
|
2179
|
+
const active = new Set()
|
|
2180
|
+
for (const script of scripts) {
|
|
2181
|
+
const identity = scriptIdentity(script)
|
|
2182
|
+
if (!identity) {
|
|
2183
|
+
continue
|
|
2184
|
+
}
|
|
2185
|
+
active.add(identity)
|
|
2186
|
+
const displayLabel = typeof displayLabelForScript === "function"
|
|
2187
|
+
? displayLabelForScript(script)
|
|
2188
|
+
: displayLabelForScript
|
|
2189
|
+
const button = existing.get(identity)
|
|
2190
|
+
if (button) {
|
|
2191
|
+
updateStopButton(button, script, displayLabel || "")
|
|
2192
|
+
} else {
|
|
2193
|
+
menuButtons.appendChild(createStopButton(script, displayLabel || ""))
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
for (const [identity, button] of existing) {
|
|
2197
|
+
if (!active.has(identity)) {
|
|
2198
|
+
button.remove()
|
|
2199
|
+
}
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
2202
|
+
const updateHomeActionStatus = (line, label, starting = false) => {
|
|
2203
|
+
const menuButton = line.querySelector(".open-actions-modal")
|
|
2204
|
+
const dialogId = menuButton ? menuButton.getAttribute("data-dialog-id") : ""
|
|
2205
|
+
const dialog = dialogId ? document.getElementById(dialogId) : null
|
|
2206
|
+
if (!dialog) {
|
|
2207
|
+
return
|
|
2208
|
+
}
|
|
2209
|
+
let status = dialog.querySelector(".home-actions-status")
|
|
2210
|
+
if (!label) {
|
|
2211
|
+
if (status) {
|
|
2212
|
+
status.remove()
|
|
2213
|
+
}
|
|
2214
|
+
dialog.querySelectorAll("[data-disable]").forEach((control) => control.removeAttribute("data-disable"))
|
|
2215
|
+
return
|
|
2216
|
+
}
|
|
2217
|
+
if (!status) {
|
|
2218
|
+
status = document.createElement("span")
|
|
2219
|
+
status.className = "home-actions-status"
|
|
2220
|
+
const titleRow = dialog.querySelector(".home-actions-title-row")
|
|
2221
|
+
if (titleRow) {
|
|
2222
|
+
titleRow.appendChild(status)
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
status.textContent = label
|
|
2226
|
+
status.title = label
|
|
2227
|
+
status.classList.toggle("home-actions-status--starting", starting)
|
|
2228
|
+
}
|
|
2229
|
+
const clearStartingVisuals = (line) => {
|
|
2230
|
+
line.setAttribute("data-autolaunch-starting", "0")
|
|
2231
|
+
line.classList.remove("autolaunch-starting")
|
|
2232
|
+
line.classList.remove("autolaunch-waiting")
|
|
2233
|
+
line.classList.remove("autolaunch-blocked")
|
|
2234
|
+
const titleIcon = line.querySelector(".title > i")
|
|
2235
|
+
if (titleIcon) {
|
|
2236
|
+
titleIcon.className = "fa-solid fa-circle"
|
|
2237
|
+
titleIcon.removeAttribute("title")
|
|
2238
|
+
}
|
|
2239
|
+
const status = line.querySelector(".home-autolaunch-status")
|
|
2240
|
+
if (status) {
|
|
2241
|
+
status.remove()
|
|
2242
|
+
}
|
|
2243
|
+
}
|
|
2244
|
+
const getOrCreateRunningSection = () => {
|
|
2245
|
+
let section = document.querySelector(".running-apps")
|
|
2246
|
+
if (section) {
|
|
2247
|
+
return section
|
|
2248
|
+
}
|
|
2249
|
+
section = document.createElement("div")
|
|
2250
|
+
section.className = "running-apps"
|
|
2251
|
+
const notRunningSection = document.querySelector(".not-running-apps")
|
|
2252
|
+
if (notRunningSection) {
|
|
2253
|
+
notRunningSection.insertAdjacentElement("beforebegin", section)
|
|
2254
|
+
} else {
|
|
2255
|
+
const placeholder = document.querySelector(".placeholder")
|
|
2256
|
+
if (placeholder) {
|
|
2257
|
+
placeholder.insertAdjacentElement("beforebegin", section)
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
return section
|
|
2261
|
+
}
|
|
2262
|
+
const markAutolaunchRunning = (line, scripts) => {
|
|
2263
|
+
activeHomeAutolaunchApps.add(line.getAttribute("data-autolaunch-app"))
|
|
2264
|
+
clearStartingVisuals(line)
|
|
2265
|
+
updateHomeActionStatus(line, "Running")
|
|
2266
|
+
syncStopButtons(line, scripts)
|
|
2267
|
+
const runningSection = getOrCreateRunningSection()
|
|
2268
|
+
if (runningSection) {
|
|
2269
|
+
runningSection.appendChild(line)
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2272
|
+
const getOrCreateNotRunningSection = () => {
|
|
2273
|
+
let section = document.querySelector(".not-running-apps")
|
|
2274
|
+
if (section) {
|
|
2275
|
+
return section
|
|
2276
|
+
}
|
|
2277
|
+
section = document.createElement("div")
|
|
2278
|
+
section.className = "not-running-apps"
|
|
2279
|
+
const runningSection = document.querySelector(".running-apps")
|
|
2280
|
+
if (runningSection) {
|
|
2281
|
+
runningSection.insertAdjacentElement("afterend", section)
|
|
2282
|
+
} else {
|
|
2283
|
+
const placeholder = document.querySelector(".placeholder")
|
|
2284
|
+
if (placeholder) {
|
|
2285
|
+
placeholder.insertAdjacentElement("beforebegin", section)
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
return section
|
|
2289
|
+
}
|
|
2290
|
+
const markAutolaunchIdle = (line) => {
|
|
2291
|
+
clearStartingVisuals(line)
|
|
2292
|
+
updateHomeActionStatus(line, "")
|
|
2293
|
+
syncStopButtons(line, [])
|
|
2294
|
+
activeHomeAutolaunchApps.delete(line.getAttribute("data-autolaunch-app"))
|
|
2295
|
+
const notRunningSection = getOrCreateNotRunningSection()
|
|
2296
|
+
if (notRunningSection) {
|
|
2297
|
+
notRunningSection.appendChild(line)
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
const autolaunchDependencyLabel = (autolaunch, dependencyId) => {
|
|
2301
|
+
const apps = autolaunch && autolaunch.apps ? autolaunch.apps : {}
|
|
2302
|
+
const status = apps[dependencyId]
|
|
2303
|
+
return status && status.title ? status.title : dependencyId
|
|
2304
|
+
}
|
|
2305
|
+
const updateAutolaunchWaitingVisuals = (line, status, autolaunch, runningScripts = []) => {
|
|
2306
|
+
if (!status) return
|
|
2307
|
+
hadStartingLines = true
|
|
2308
|
+
activeHomeAutolaunchApps.add(line.getAttribute("data-autolaunch-app"))
|
|
2309
|
+
let label = "Starting automatically"
|
|
2310
|
+
let iconClass = "fa-solid fa-circle-notch fa-spin"
|
|
2311
|
+
let showStatusChip = true
|
|
2312
|
+
line.setAttribute("data-autolaunch-starting", "1")
|
|
2313
|
+
line.classList.add("autolaunch-starting")
|
|
2314
|
+
if (status.script) {
|
|
2315
|
+
line.setAttribute("data-autolaunch-script", status.script)
|
|
2316
|
+
}
|
|
2317
|
+
const waitingFor = Array.isArray(status.waiting_for) ? status.waiting_for : []
|
|
2318
|
+
if (waitingFor.length > 0 && !["ready", "blocked"].includes(status.state || "")) {
|
|
2319
|
+
label = `Waiting for ${waitingFor.map((id) => autolaunchDependencyLabel(autolaunch, id)).join(", ")}`
|
|
2320
|
+
updateHomeActionStatus(line, label, true)
|
|
2321
|
+
line.classList.add("autolaunch-waiting")
|
|
2322
|
+
line.classList.remove("autolaunch-blocked")
|
|
2323
|
+
} else if (status.state === "blocked") {
|
|
2324
|
+
label = status.blocked_reason || "Startup blocked"
|
|
2325
|
+
iconClass = "fa-solid fa-triangle-exclamation"
|
|
2326
|
+
updateHomeActionStatus(line, label, true)
|
|
2327
|
+
line.classList.add("autolaunch-blocked")
|
|
2328
|
+
line.classList.remove("autolaunch-waiting")
|
|
2329
|
+
} else {
|
|
2330
|
+
const expectedScript = status.script || line.getAttribute("data-autolaunch-script") || ""
|
|
2331
|
+
const matchingScript = Array.isArray(runningScripts)
|
|
2332
|
+
? runningScripts.find((script) => scriptMatches(script, expectedScript))
|
|
2333
|
+
: null
|
|
2334
|
+
const statusHasProgress = status && Number.isFinite(Number(status.step_current)) && Number.isFinite(Number(status.step_total))
|
|
2335
|
+
const progressSource = statusHasProgress ? status : matchingScript
|
|
2336
|
+
label = expectedScript ? `Starting ${expectedScript}${scriptStepLabel(progressSource)}` : "Starting automatically"
|
|
2337
|
+
updateHomeActionStatus(line, label, true)
|
|
2338
|
+
line.classList.remove("autolaunch-waiting")
|
|
2339
|
+
line.classList.remove("autolaunch-blocked")
|
|
2340
|
+
if (matchingScript) {
|
|
2341
|
+
syncStopButtons(line, runningScripts, (script) => {
|
|
2342
|
+
return scriptMatches(script, expectedScript) ? label : ""
|
|
2343
|
+
})
|
|
2344
|
+
showStatusChip = false
|
|
2345
|
+
}
|
|
2346
|
+
}
|
|
2347
|
+
let chip = line.querySelector(".home-autolaunch-status")
|
|
2348
|
+
if (!showStatusChip) {
|
|
2349
|
+
if (chip) {
|
|
2350
|
+
chip.remove()
|
|
2351
|
+
}
|
|
2352
|
+
return
|
|
2353
|
+
}
|
|
2354
|
+
if (!chip) {
|
|
2355
|
+
const menuButtons = line.querySelector(".menu-btns")
|
|
2356
|
+
if (!menuButtons) {
|
|
2357
|
+
return
|
|
2358
|
+
}
|
|
2359
|
+
chip = document.createElement("span")
|
|
2360
|
+
chip.className = "home-autolaunch-status"
|
|
2361
|
+
chip.innerHTML = '<i class="fa-solid" aria-hidden="true"></i><span></span>'
|
|
2362
|
+
menuButtons.appendChild(chip)
|
|
2363
|
+
}
|
|
2364
|
+
if (chip) {
|
|
2365
|
+
chip.title = label
|
|
2366
|
+
const icon = chip.querySelector("i")
|
|
2367
|
+
if (icon) {
|
|
2368
|
+
icon.className = iconClass
|
|
2369
|
+
}
|
|
2370
|
+
const text = chip.querySelector("span")
|
|
2371
|
+
if (text) {
|
|
2372
|
+
text.textContent = label
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
2375
|
+
}
|
|
2376
|
+
const applyHomeStatus = (state) => {
|
|
2377
|
+
const lines = getAutolaunchLines(state)
|
|
2378
|
+
if (lines.length === 0) {
|
|
2379
|
+
return
|
|
2380
|
+
}
|
|
2381
|
+
const runningApps = new Set(Array.isArray(state.running_apps) ? state.running_apps : [])
|
|
2382
|
+
const scriptsByApp = new Map()
|
|
2383
|
+
for (const script of Array.isArray(state.running_scripts) ? state.running_scripts : []) {
|
|
2384
|
+
if (!script || !script.app) {
|
|
2385
|
+
continue
|
|
2386
|
+
}
|
|
2387
|
+
if (!scriptsByApp.has(script.app)) {
|
|
2388
|
+
scriptsByApp.set(script.app, [])
|
|
2389
|
+
}
|
|
2390
|
+
scriptsByApp.get(script.app).push(script)
|
|
2391
|
+
}
|
|
2392
|
+
const autolaunch = state.autolaunch || {}
|
|
2393
|
+
const apps = autolaunch.apps || {}
|
|
2394
|
+
for (const line of lines) {
|
|
2395
|
+
const app = line.getAttribute("data-autolaunch-app")
|
|
2396
|
+
const autolaunchState = apps[app]
|
|
2397
|
+
const runningScripts = scriptsByApp.get(app) || []
|
|
2398
|
+
if (autolaunchState && !shouldDisplayAutolaunchStatus(app, autolaunchState, apps, runningApps)) {
|
|
2399
|
+
markAutolaunchIdle(line)
|
|
2400
|
+
} else if (isPreparingAutolaunchState(autolaunchState)) {
|
|
2401
|
+
updateAutolaunchWaitingVisuals(line, autolaunchState, autolaunch, runningScripts)
|
|
2402
|
+
} else if (runningApps.has(app)) {
|
|
2403
|
+
markAutolaunchRunning(line, runningScripts)
|
|
2404
|
+
} else if (state.launch_complete) {
|
|
2405
|
+
markAutolaunchIdle(line)
|
|
2406
|
+
} else {
|
|
2407
|
+
updateAutolaunchWaitingVisuals(line, autolaunchState, autolaunch, runningScripts)
|
|
2408
|
+
}
|
|
2409
|
+
}
|
|
2410
|
+
if (getStartingLines().length === 0) {
|
|
2411
|
+
if (hadStartingLines) {
|
|
2412
|
+
hadStartingLines = false
|
|
2413
|
+
reorderHomeSectionsByPreference()
|
|
2414
|
+
}
|
|
2415
|
+
}
|
|
2416
|
+
}
|
|
2417
|
+
const requestHomeStatus = () => {
|
|
2418
|
+
return new Promise((resolve, reject) => {
|
|
2419
|
+
const xhr = new XMLHttpRequest()
|
|
2420
|
+
xhr.open("GET", `/pinokio/home_status?t=${Date.now()}`, true)
|
|
2421
|
+
xhr.setRequestHeader("Cache-Control", "no-cache")
|
|
2422
|
+
xhr.onreadystatechange = () => {
|
|
2423
|
+
if (xhr.readyState !== 4) {
|
|
2424
|
+
return
|
|
2425
|
+
}
|
|
2426
|
+
if (xhr.status >= 200 && xhr.status < 300) {
|
|
2427
|
+
try {
|
|
2428
|
+
resolve(JSON.parse(xhr.responseText))
|
|
2429
|
+
} catch (error) {
|
|
2430
|
+
reject(error)
|
|
2431
|
+
}
|
|
2432
|
+
} else {
|
|
2433
|
+
reject(new Error(`home_status ${xhr.status}`))
|
|
2434
|
+
}
|
|
2435
|
+
}
|
|
2436
|
+
xhr.onerror = () => reject(new Error("home_status request failed"))
|
|
2437
|
+
xhr.send()
|
|
2438
|
+
})
|
|
2439
|
+
}
|
|
2440
|
+
refreshHomeStatusOnce = (appId = "") => {
|
|
2441
|
+
const trackedApp = String(appId || "").trim()
|
|
2442
|
+
if (trackedApp) {
|
|
2443
|
+
activeHomeAutolaunchApps.add(trackedApp)
|
|
2444
|
+
}
|
|
2445
|
+
return requestHomeStatus().then((state) => {
|
|
2446
|
+
applyHomeStatus(state)
|
|
2447
|
+
return state
|
|
2448
|
+
})
|
|
2449
|
+
}
|
|
2450
|
+
window.refreshHomeStatusOnce = refreshHomeStatusOnce
|
|
2451
|
+
if (initialLaunchComplete && !hasVisibleStartupRows()) {
|
|
2452
|
+
return
|
|
2453
|
+
}
|
|
2454
|
+
const shouldContinueHomeStatusPolling = (state) => {
|
|
2455
|
+
if (attempts >= maxPollAttempts) {
|
|
2456
|
+
return false
|
|
2457
|
+
}
|
|
2458
|
+
if (hasVisibleStartupRows()) {
|
|
2459
|
+
return true
|
|
2460
|
+
}
|
|
2461
|
+
return !(state && state.launch_complete)
|
|
2462
|
+
}
|
|
2463
|
+
const poll = async () => {
|
|
2464
|
+
attempts += 1
|
|
2465
|
+
try {
|
|
2466
|
+
const state = await refreshHomeStatusOnce()
|
|
2467
|
+
if (!shouldContinueHomeStatusPolling(state)) {
|
|
2468
|
+
stopPolling()
|
|
2469
|
+
}
|
|
2470
|
+
} catch (error) {
|
|
2471
|
+
console.warn("[home] autolaunch status update failed", error)
|
|
2472
|
+
if (attempts >= maxPollAttempts) {
|
|
2473
|
+
stopPolling()
|
|
2474
|
+
}
|
|
2475
|
+
}
|
|
2476
|
+
}
|
|
2477
|
+
interval = setInterval(poll, 500)
|
|
2478
|
+
setTimeout(poll, 100)
|
|
2479
|
+
document.addEventListener("visibilitychange", () => {
|
|
2480
|
+
if (document.visibilityState === "visible") {
|
|
2481
|
+
poll()
|
|
2482
|
+
}
|
|
2483
|
+
})
|
|
2484
|
+
window.addEventListener("beforeunload", () => {
|
|
2485
|
+
stopPolling()
|
|
2486
|
+
})
|
|
2487
|
+
}
|
|
2488
|
+
startHomeAutolaunchPolling()
|
|
1980
2489
|
document.addEventListener("keydown", (e) => {
|
|
1981
2490
|
e = e || window.event;
|
|
1982
2491
|
if (e.key === "ArrowUp") {
|
|
@@ -2507,7 +3016,10 @@ document.addEventListener("click", async (e) => {
|
|
|
2507
3016
|
if (packet.type === "result") {
|
|
2508
3017
|
socket.close()
|
|
2509
3018
|
target.querySelector("i.fa-stop").className = "fa-solid fa-check"
|
|
2510
|
-
target.querySelector("i.fa-spin")
|
|
3019
|
+
const resultSpinIcon = target.querySelector("i.fa-spin")
|
|
3020
|
+
if (resultSpinIcon) {
|
|
3021
|
+
resultSpinIcon.classList.add("hidden")
|
|
3022
|
+
}
|
|
2511
3023
|
n.Noty({
|
|
2512
3024
|
text: `stopped`,
|
|
2513
3025
|
silent: true
|
|
@@ -2526,13 +3038,25 @@ document.addEventListener("click", async (e) => {
|
|
|
2526
3038
|
if (packet.type === "result") {
|
|
2527
3039
|
socket.close()
|
|
2528
3040
|
target.querySelector("i.fa-stop").className = "fa-solid fa-check"
|
|
2529
|
-
target.querySelector("i.fa-spin")
|
|
3041
|
+
const resultSpinIcon = target.querySelector("i.fa-spin")
|
|
3042
|
+
if (resultSpinIcon) {
|
|
3043
|
+
resultSpinIcon.classList.add("hidden")
|
|
3044
|
+
}
|
|
2530
3045
|
n.Noty({
|
|
2531
3046
|
text: `stopped ${src}`,
|
|
2532
3047
|
silent: true
|
|
2533
3048
|
})
|
|
2534
3049
|
setTimeout(() => {
|
|
2535
|
-
|
|
3050
|
+
if (typeof refreshHomeStatusOnce === "function") {
|
|
3051
|
+
const stoppedLine = target.closest(".home-app-line")
|
|
3052
|
+
const stoppedApp = stoppedLine ? stoppedLine.getAttribute("data-autolaunch-app") : ""
|
|
3053
|
+
refreshHomeStatusOnce(stoppedApp).catch((error) => {
|
|
3054
|
+
console.warn("[home] stop status refresh failed", error)
|
|
3055
|
+
location.href = location.href
|
|
3056
|
+
})
|
|
3057
|
+
} else {
|
|
3058
|
+
location.href = location.href
|
|
3059
|
+
}
|
|
2536
3060
|
}, 500)
|
|
2537
3061
|
}
|
|
2538
3062
|
})
|