@firstpick/pi-package-webui 0.4.6 → 0.4.8
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 +9 -9
- package/bin/pi-webui.mjs +204 -65
- package/package.json +10 -3
- package/public/app.js +268 -113
- package/public/index.html +9 -4
- package/public/styles.css +39 -0
- package/tests/http-endpoints-harness.test.mjs +105 -0
- package/tests/mobile-static.test.mjs +23 -11
package/public/app.js
CHANGED
|
@@ -24,6 +24,7 @@ const elements = {
|
|
|
24
24
|
updateNotificationMessage: $("#updateNotificationMessage"),
|
|
25
25
|
updateNotificationDetail: $("#updateNotificationDetail"),
|
|
26
26
|
updateNotificationUpdateButton: $("#updateNotificationUpdateButton"),
|
|
27
|
+
updateNotificationUpdateAllButton: $("#updateNotificationUpdateAllButton"),
|
|
27
28
|
updateNotificationDismissButton: $("#updateNotificationDismissButton"),
|
|
28
29
|
serverOfflineCommand: $("#serverOfflineCommand"),
|
|
29
30
|
serverOfflineSlashCommand: $("#serverOfflineSlashCommand"),
|
|
@@ -114,6 +115,7 @@ const elements = {
|
|
|
114
115
|
gitChangesStatus: $("#gitChangesStatus"),
|
|
115
116
|
gitChangesBody: $("#gitChangesBody"),
|
|
116
117
|
gitChangesRefreshButton: $("#gitChangesRefreshButton"),
|
|
118
|
+
gitChangesPullButton: $("#gitChangesPullButton"),
|
|
117
119
|
gitChangesCloseButton: $("#gitChangesCloseButton"),
|
|
118
120
|
modelSelect: $("#modelSelect"),
|
|
119
121
|
setModelButton: $("#setModelButton"),
|
|
@@ -128,6 +130,7 @@ const elements = {
|
|
|
128
130
|
backgroundChooseButton: $("#backgroundChooseButton"),
|
|
129
131
|
backgroundClearButton: $("#backgroundClearButton"),
|
|
130
132
|
backgroundStatus: $("#backgroundStatus"),
|
|
133
|
+
networkControlField: $("#networkControlField"),
|
|
131
134
|
networkStatus: $("#networkStatus"),
|
|
132
135
|
remoteAuthToggle: $("#remoteAuthToggle"),
|
|
133
136
|
remoteAuthStatus: $("#remoteAuthStatus"),
|
|
@@ -265,7 +268,7 @@ let foregroundReconcileTimer = null;
|
|
|
265
268
|
let eventSource = null;
|
|
266
269
|
let activeDialog = null;
|
|
267
270
|
let activeGitPrDialogResolve = null;
|
|
268
|
-
let gitChangesState = { loading: false, error: "", data: null, tabId: null };
|
|
271
|
+
let gitChangesState = { loading: false, pulling: false, error: "", message: "", data: null, tabId: null };
|
|
269
272
|
let gitChangesRequestSerial = 0;
|
|
270
273
|
const gitChangesUntrackedContentRequests = new Set();
|
|
271
274
|
let nativeCommandTabId = null;
|
|
@@ -285,6 +288,7 @@ let appRunnerMenuOpen = false;
|
|
|
285
288
|
let busyPromptBehaviorMenuOpen = false;
|
|
286
289
|
const skillUsageByTab = new Map();
|
|
287
290
|
let appRunnerCustomDraft = { id: "", label: "", command: "./", path: "", args: "" };
|
|
291
|
+
let appRunnerCustomFeedback = { type: "", message: "" };
|
|
288
292
|
let appRunnerFileBrowserState = { open: false, loading: false, path: "", data: null, error: "" };
|
|
289
293
|
let optionsMenuOpen = false;
|
|
290
294
|
let availableCommands = [];
|
|
@@ -434,6 +438,9 @@ const GIT_INIT_STACK_STORAGE_KEY = "pi-webui-git-init-stack";
|
|
|
434
438
|
const STATS_WEBUI_STATUS_KEY = "stats-webui";
|
|
435
439
|
const STATS_WEBUI_PAYLOAD_TYPE = "firstpick.pi-extension-stats.overlay";
|
|
436
440
|
const STATS_WEBUI_PAYLOAD_VERSION = 1;
|
|
441
|
+
const REMOTE_WEBUI_CONTROLS_STATUS_KEY = "pi-remote-webui:controls";
|
|
442
|
+
const REMOTE_WEBUI_CONTROLS_PAYLOAD_TYPE = "firstpick.pi-package-remote-webui.controls";
|
|
443
|
+
const REMOTE_WEBUI_CONTROLS_PAYLOAD_VERSION = 1;
|
|
437
444
|
const BTW_WEBUI_STATUS_KEY = "btw-webui";
|
|
438
445
|
const BTW_OUTPUT_WIDGET_KEY = "btw:output";
|
|
439
446
|
const BTW_FOOTER_WIDGET_KEY = "btw:footer";
|
|
@@ -2823,22 +2830,34 @@ function renderUpdateNotification(status = latestUpdateStatus, { force = false }
|
|
|
2823
2830
|
}
|
|
2824
2831
|
|
|
2825
2832
|
const canRunUpdate = latestUpdateStatus.canRunUpdate !== false;
|
|
2833
|
+
const hasPiUpdate = !!latestUpdateStatus.pi?.updateAvailable;
|
|
2834
|
+
const hasPackageUpdate = !!latestUpdateStatus.webui?.updateAvailable;
|
|
2826
2835
|
if (elements.updateNotificationTitle) elements.updateNotificationTitle.textContent = items.length === 1 ? `${items[0]} available` : "Pi updates available";
|
|
2827
2836
|
if (elements.updateNotificationMessage) {
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2837
|
+
let message = "Updates are available. Direct Web UI updates are only enabled from localhost on the host machine.";
|
|
2838
|
+
if (canRunUpdate) {
|
|
2839
|
+
if (hasPiUpdate && hasPackageUpdate) message = "Run pi update for Pi only, or pi update --all to include Web UI/package updates, then restart this Web UI server automatically.";
|
|
2840
|
+
else if (hasPackageUpdate) message = "Run pi update --all to update Web UI/package entries, then restart this Web UI server automatically.";
|
|
2841
|
+
else message = "Run pi update for Pi only, then restart this Web UI server automatically.";
|
|
2842
|
+
}
|
|
2843
|
+
elements.updateNotificationMessage.textContent = message;
|
|
2831
2844
|
}
|
|
2832
2845
|
const details = [
|
|
2833
2846
|
items.join(" · "),
|
|
2834
|
-
latestUpdateStatus.webuiDev && latestUpdateStatus.webui?.updateAvailable ? "The current Web UI is a dev checkout; update
|
|
2847
|
+
latestUpdateStatus.webuiDev && latestUpdateStatus.webui?.updateAvailable ? "The current Web UI is a dev checkout; pi update --all refreshes configured package dependencies when possible." : "",
|
|
2835
2848
|
latestUpdateStatus.packages?.note || "",
|
|
2836
2849
|
].filter(Boolean).join(" ");
|
|
2837
2850
|
if (elements.updateNotificationDetail) elements.updateNotificationDetail.textContent = details;
|
|
2838
2851
|
if (elements.updateNotificationUpdateButton) {
|
|
2839
|
-
elements.updateNotificationUpdateButton.hidden = !canRunUpdate;
|
|
2852
|
+
elements.updateNotificationUpdateButton.hidden = !canRunUpdate || !hasPiUpdate;
|
|
2840
2853
|
elements.updateNotificationUpdateButton.disabled = updateRequestInProgress || latestUpdateStatus.updateInProgress;
|
|
2841
|
-
elements.updateNotificationUpdateButton.textContent = latestUpdateStatus.updateInProgress ? "Updating…" : "Update & restart";
|
|
2854
|
+
elements.updateNotificationUpdateButton.textContent = latestUpdateStatus.updateInProgress ? "Updating…" : "Update Pi & restart";
|
|
2855
|
+
}
|
|
2856
|
+
if (elements.updateNotificationUpdateAllButton) {
|
|
2857
|
+
elements.updateNotificationUpdateAllButton.hidden = !canRunUpdate || !hasPackageUpdate;
|
|
2858
|
+
elements.updateNotificationUpdateAllButton.disabled = updateRequestInProgress || latestUpdateStatus.updateInProgress;
|
|
2859
|
+
elements.updateNotificationUpdateAllButton.classList.toggle("primary", !hasPiUpdate);
|
|
2860
|
+
elements.updateNotificationUpdateAllButton.textContent = latestUpdateStatus.updateInProgress ? "Updating…" : "Update all & restart";
|
|
2842
2861
|
}
|
|
2843
2862
|
clearTimeout(updateNotificationHideTimer);
|
|
2844
2863
|
panel.hidden = false;
|
|
@@ -2869,30 +2888,33 @@ function initializeUpdateNotifications() {
|
|
|
2869
2888
|
}, UPDATE_STATUS_INITIAL_DELAY_MS);
|
|
2870
2889
|
}
|
|
2871
2890
|
|
|
2872
|
-
function piUpdateConfirmationText() {
|
|
2891
|
+
function piUpdateConfirmationText({ all = false } = {}) {
|
|
2873
2892
|
const items = updateNotificationItems();
|
|
2874
2893
|
const workingWarning = hasWorkingTab() ? "\n\nOne or more Pi tabs look busy or blocked. Finish or abort in-flight work before updating if you need to preserve it." : "";
|
|
2875
2894
|
const versionText = items.length ? `\n\nDetected update: ${items.join(" · ")}.` : "";
|
|
2876
|
-
|
|
2895
|
+
const command = all ? "pi update --all" : "pi update";
|
|
2896
|
+
const scope = all ? "Pi and configured package updates" : "Pi only";
|
|
2897
|
+
return `Run ${scope} now?${versionText}\n\nThis will run \"${command}\" on the Web UI host. After it finishes, Pi Web UI will restart itself. Browser clients will briefly disconnect, and managed Pi tabs/RPC processes will be restarted from saved session state when possible.${workingWarning}`;
|
|
2877
2898
|
}
|
|
2878
2899
|
|
|
2879
|
-
async function runPiUpdateAndRestart() {
|
|
2900
|
+
async function runPiUpdateAndRestart({ all = false } = {}) {
|
|
2880
2901
|
if (updateRequestInProgress) return;
|
|
2881
2902
|
if (latestUpdateStatus?.canRunUpdate === false) {
|
|
2882
|
-
addEvent("Pi
|
|
2903
|
+
addEvent("Pi updates can only be started from localhost on the Web UI host", "warn");
|
|
2883
2904
|
renderUpdateNotification(latestUpdateStatus, { force: true });
|
|
2884
2905
|
return;
|
|
2885
2906
|
}
|
|
2886
|
-
if (!confirm(piUpdateConfirmationText())) return;
|
|
2907
|
+
if (!confirm(piUpdateConfirmationText({ all }))) return;
|
|
2887
2908
|
|
|
2909
|
+
const updateLabel = all ? "Pi and package updates" : "Pi update";
|
|
2888
2910
|
updateRequestInProgress = true;
|
|
2889
2911
|
hideUpdateNotification();
|
|
2890
2912
|
setServerActionBusy("Updating…");
|
|
2891
|
-
setServerActionStatus(
|
|
2892
|
-
setServerRestartOverlay(true,
|
|
2913
|
+
setServerActionStatus(`Running ${updateLabel}. The server will restart after the update completes…`, "warn");
|
|
2914
|
+
setServerRestartOverlay(true, `Running ${updateLabel}. The server will restart after the update completes…`);
|
|
2893
2915
|
try {
|
|
2894
|
-
await api("/api/update", { method: "POST", scoped: false });
|
|
2895
|
-
addEvent(
|
|
2916
|
+
await api(all ? "/api/update?all=1" : "/api/update", { method: "POST", scoped: false });
|
|
2917
|
+
addEvent(`${updateLabel} completed; Pi Web UI server restart requested`, "warn");
|
|
2896
2918
|
} catch (error) {
|
|
2897
2919
|
if (!error?.backendOffline) {
|
|
2898
2920
|
updateRequestInProgress = false;
|
|
@@ -3809,6 +3831,11 @@ function setOptionalFeatureDisabled(featureId, disabled) {
|
|
|
3809
3831
|
btwWidgetComposerOpen = false;
|
|
3810
3832
|
btwWidgetInputDraft = "";
|
|
3811
3833
|
}
|
|
3834
|
+
if (featureId === "remoteWebui") {
|
|
3835
|
+
statusEntries.delete(REMOTE_WEBUI_CONTROLS_STATUS_KEY);
|
|
3836
|
+
statusEntries.delete("pi-remote-webui");
|
|
3837
|
+
widgets.delete("pi-remote-webui");
|
|
3838
|
+
}
|
|
3812
3839
|
storeDisabledOptionalFeatures();
|
|
3813
3840
|
renderOptionalFeatureDependentDisplays();
|
|
3814
3841
|
const tabContext = activeTabContext();
|
|
@@ -5976,6 +6003,57 @@ function parseGitFooterWebuiPayloadRaw(raw) {
|
|
|
5976
6003
|
}
|
|
5977
6004
|
}
|
|
5978
6005
|
|
|
6006
|
+
function parseRemoteWebuiControlsPayloadRaw(raw) {
|
|
6007
|
+
if (!raw) return null;
|
|
6008
|
+
try {
|
|
6009
|
+
const parsed = JSON.parse(raw);
|
|
6010
|
+
if (!parsed || parsed.type !== REMOTE_WEBUI_CONTROLS_PAYLOAD_TYPE || parsed.version !== REMOTE_WEBUI_CONTROLS_PAYLOAD_VERSION) return null;
|
|
6011
|
+
if (parsed.featureId !== "remoteWebui") return null;
|
|
6012
|
+
const commands = parsed.commands && typeof parsed.commands === "object" ? parsed.commands : {};
|
|
6013
|
+
return {
|
|
6014
|
+
title: cleanFooterPayloadText(parsed.title, "Remote WebUI", 80),
|
|
6015
|
+
description: cleanFooterPayloadText(parsed.description, "Trusted-LAN browser access controlled by the Remote WebUI package.", 240),
|
|
6016
|
+
commands: {
|
|
6017
|
+
open: typeof commands.open === "string" ? commands.open : "/remote",
|
|
6018
|
+
close: typeof commands.close === "string" ? commands.close : "/remote close",
|
|
6019
|
+
refresh: typeof commands.refresh === "string" ? commands.refresh : "/remote refresh",
|
|
6020
|
+
status: typeof commands.status === "string" ? commands.status : "/remote status",
|
|
6021
|
+
authOn: typeof commands.authOn === "string" ? commands.authOn : "/remote auth on",
|
|
6022
|
+
authOff: typeof commands.authOff === "string" ? commands.authOff : "/remote auth off",
|
|
6023
|
+
},
|
|
6024
|
+
};
|
|
6025
|
+
} catch {
|
|
6026
|
+
return null;
|
|
6027
|
+
}
|
|
6028
|
+
}
|
|
6029
|
+
|
|
6030
|
+
function remoteWebuiControlsPayload() {
|
|
6031
|
+
if (isOptionalFeatureDisabled("remoteWebui")) return null;
|
|
6032
|
+
return parseRemoteWebuiControlsPayloadRaw(statusEntries.get(REMOTE_WEBUI_CONTROLS_STATUS_KEY));
|
|
6033
|
+
}
|
|
6034
|
+
|
|
6035
|
+
function remoteWebuiDefaultPortArg() {
|
|
6036
|
+
const port = Number.parseInt(String(latestNetwork?.port || DEFAULT_WEBUI_PORT), 10);
|
|
6037
|
+
return Number.isFinite(port) && port > 0 && port <= 65535 && String(port) !== DEFAULT_WEBUI_PORT ? ` --port ${port}` : "";
|
|
6038
|
+
}
|
|
6039
|
+
|
|
6040
|
+
function remoteWebuiFallbackCommand(name, fallback) {
|
|
6041
|
+
const portArg = remoteWebuiDefaultPortArg();
|
|
6042
|
+
const commands = {
|
|
6043
|
+
open: `/remote${portArg}`,
|
|
6044
|
+
close: `/remote close${portArg}`,
|
|
6045
|
+
refresh: `/remote refresh${portArg}`,
|
|
6046
|
+
status: `/remote status${portArg}`,
|
|
6047
|
+
authOn: `/remote auth on${portArg}`,
|
|
6048
|
+
authOff: `/remote auth off${portArg}`,
|
|
6049
|
+
};
|
|
6050
|
+
return commands[name] || fallback;
|
|
6051
|
+
}
|
|
6052
|
+
|
|
6053
|
+
function remoteWebuiCommand(name, fallback) {
|
|
6054
|
+
return remoteWebuiControlsPayload()?.commands?.[name] || remoteWebuiFallbackCommand(name, fallback);
|
|
6055
|
+
}
|
|
6056
|
+
|
|
5979
6057
|
function readCachedGitFooterWebuiPayloadRaw() {
|
|
5980
6058
|
try {
|
|
5981
6059
|
const cached = JSON.parse(localStorage.getItem(GIT_FOOTER_WEBUI_PAYLOAD_CACHE_KEY) || "null");
|
|
@@ -6382,13 +6460,23 @@ function renderGitChangesOverview(data) {
|
|
|
6382
6460
|
return overview;
|
|
6383
6461
|
}
|
|
6384
6462
|
|
|
6463
|
+
function gitDiffDisplayLine(row, side) {
|
|
6464
|
+
const type = row.type || "context";
|
|
6465
|
+
if (side === "old") {
|
|
6466
|
+
const text = row.left ?? "";
|
|
6467
|
+
return row.oldNumber !== "" && (type === "removed" || type === "changed") ? `-${text}` : text;
|
|
6468
|
+
}
|
|
6469
|
+
const text = row.right ?? "";
|
|
6470
|
+
return row.newNumber !== "" && (type === "added" || type === "changed") ? `+${text}` : text;
|
|
6471
|
+
}
|
|
6472
|
+
|
|
6385
6473
|
function renderGitDiffRow(row) {
|
|
6386
6474
|
const node = make("div", `git-diff-row ${row.type || "context"}`.trim());
|
|
6387
6475
|
node.append(
|
|
6388
6476
|
make("span", "git-diff-line-number old", row.oldNumber === "" ? "" : String(row.oldNumber)),
|
|
6389
|
-
make("code", "git-diff-line old", row
|
|
6477
|
+
make("code", "git-diff-line old", gitDiffDisplayLine(row, "old")),
|
|
6390
6478
|
make("span", "git-diff-line-number new", row.newNumber === "" ? "" : String(row.newNumber)),
|
|
6391
|
-
make("code", "git-diff-line new", row
|
|
6479
|
+
make("code", "git-diff-line new", gitDiffDisplayLine(row, "new")),
|
|
6392
6480
|
);
|
|
6393
6481
|
return node;
|
|
6394
6482
|
}
|
|
@@ -6605,16 +6693,28 @@ function gitChangesGeneratedLabel(data) {
|
|
|
6605
6693
|
|
|
6606
6694
|
function renderGitChangesDialog() {
|
|
6607
6695
|
if (!elements.gitChangesDialog || !elements.gitChangesBody) return;
|
|
6608
|
-
const { loading, error, data } = gitChangesState;
|
|
6609
|
-
|
|
6610
|
-
|
|
6696
|
+
const { loading, pulling, error, message, data } = gitChangesState;
|
|
6697
|
+
const behind = Number(data?.remote?.behind ?? data?.summary?.behind ?? 0) || 0;
|
|
6698
|
+
const canPull = behind > 0 && data?.remote?.canPull !== false;
|
|
6699
|
+
const remoteNotice = !error && data?.remote?.error ? `Incoming diff unavailable: ${data.remote.error}` : "";
|
|
6700
|
+
if (elements.gitChangesTitle) elements.gitChangesTitle.textContent = "Git Changes";
|
|
6701
|
+
if (elements.gitChangesSubtitle) {
|
|
6702
|
+
const base = data?.root ? `${data.branch || "detached"} · ${data.root}` : "Current tab git diff";
|
|
6703
|
+
elements.gitChangesSubtitle.textContent = data?.remote?.upstream ? `${base} · upstream ${data.remote.upstream}` : base;
|
|
6704
|
+
}
|
|
6611
6705
|
if (elements.gitChangesRefreshButton) {
|
|
6612
|
-
elements.gitChangesRefreshButton.disabled = loading;
|
|
6706
|
+
elements.gitChangesRefreshButton.disabled = loading || pulling;
|
|
6613
6707
|
elements.gitChangesRefreshButton.textContent = loading ? "Refreshing…" : "Refresh";
|
|
6614
6708
|
}
|
|
6709
|
+
if (elements.gitChangesPullButton) {
|
|
6710
|
+
elements.gitChangesPullButton.disabled = loading || pulling || !canPull;
|
|
6711
|
+
elements.gitChangesPullButton.textContent = pulling ? "Pulling…" : behind > 0 ? `Pull ↓${behind}` : "Pull";
|
|
6712
|
+
elements.gitChangesPullButton.title = canPull ? "Run git pull --ff-only for the current repository" : "No remote commits to pull";
|
|
6713
|
+
}
|
|
6615
6714
|
if (elements.gitChangesStatus) {
|
|
6616
|
-
|
|
6617
|
-
elements.gitChangesStatus.
|
|
6715
|
+
const statusText = error || (pulling ? "Pulling changes…" : loading ? "Loading git diff…" : message || remoteNotice || (data ? gitChangesGeneratedLabel(data) : ""));
|
|
6716
|
+
elements.gitChangesStatus.className = `git-changes-status ${error || remoteNotice ? "error" : message ? "success" : "muted"}`;
|
|
6717
|
+
elements.gitChangesStatus.textContent = statusText;
|
|
6618
6718
|
elements.gitChangesStatus.hidden = !elements.gitChangesStatus.textContent;
|
|
6619
6719
|
}
|
|
6620
6720
|
|
|
@@ -6624,7 +6724,7 @@ function renderGitChangesDialog() {
|
|
|
6624
6724
|
body.append(make("div", "git-changes-empty", "Loading git diff…"));
|
|
6625
6725
|
return;
|
|
6626
6726
|
}
|
|
6627
|
-
if (error) {
|
|
6727
|
+
if (error && !data) {
|
|
6628
6728
|
body.append(make("div", "git-changes-empty error", error));
|
|
6629
6729
|
return;
|
|
6630
6730
|
}
|
|
@@ -6642,20 +6742,23 @@ function renderGitChangesDialog() {
|
|
|
6642
6742
|
if (hasVisibleFiles) body.append(renderGitCurrentFileHeader());
|
|
6643
6743
|
for (const entry of parsedSections) body.append(renderGitDiffSection(entry.section, entry.files));
|
|
6644
6744
|
if (untracked.length) body.append(renderGitUntrackedSection(untracked));
|
|
6645
|
-
if (!hasVisibleFiles)
|
|
6745
|
+
if (!hasVisibleFiles) {
|
|
6746
|
+
const emptyMessage = behind > 0 ? "No textual incoming diff was available for the remote commits." : "Working tree is clean. No staged, unstaged, untracked, or incoming diff.";
|
|
6747
|
+
body.append(make("div", "git-changes-empty success", emptyMessage));
|
|
6748
|
+
}
|
|
6646
6749
|
if (hasVisibleFiles) requestAnimationFrame(updateGitChangesCurrentFileHeader);
|
|
6647
6750
|
}
|
|
6648
6751
|
|
|
6649
6752
|
async function loadGitChangesDialog(tabContext = activeTabContext()) {
|
|
6650
6753
|
const requestSerial = ++gitChangesRequestSerial;
|
|
6651
6754
|
gitChangesUntrackedContentRequests.clear();
|
|
6652
|
-
gitChangesState = { ...gitChangesState, loading: true, error: "", tabId: tabContext.tabId || activeTabId };
|
|
6755
|
+
gitChangesState = { ...gitChangesState, loading: true, error: "", message: "", tabId: tabContext.tabId || activeTabId };
|
|
6653
6756
|
renderGitChangesDialog();
|
|
6654
6757
|
try {
|
|
6655
6758
|
const response = await api("/api/git-changes", { tabId: tabContext.tabId });
|
|
6656
6759
|
if (requestSerial !== gitChangesRequestSerial) return;
|
|
6657
6760
|
if (!response.ok) throw new Error(response.error || "Failed to load git changes");
|
|
6658
|
-
gitChangesState = { loading: false, error: "", data: response.data || null, tabId: tabContext.tabId || activeTabId };
|
|
6761
|
+
gitChangesState = { loading: false, pulling: false, error: "", message: "", data: response.data || null, tabId: tabContext.tabId || activeTabId };
|
|
6659
6762
|
} catch (error) {
|
|
6660
6763
|
if (requestSerial !== gitChangesRequestSerial) return;
|
|
6661
6764
|
gitChangesState = { ...gitChangesState, loading: false, error: error.message || String(error) };
|
|
@@ -6668,7 +6771,7 @@ function openGitChangesDialog() {
|
|
|
6668
6771
|
hideFooterTooltip();
|
|
6669
6772
|
const tabContext = activeTabContext();
|
|
6670
6773
|
const tabId = tabContext.tabId || activeTabId;
|
|
6671
|
-
gitChangesState = { loading: true, error: "", data: gitChangesState.tabId === tabId ? gitChangesState.data : null, tabId };
|
|
6774
|
+
gitChangesState = { loading: true, pulling: false, error: "", message: "", data: gitChangesState.tabId === tabId ? gitChangesState.data : null, tabId };
|
|
6672
6775
|
renderGitChangesDialog();
|
|
6673
6776
|
if (!elements.gitChangesDialog.open) elements.gitChangesDialog.showModal();
|
|
6674
6777
|
loadGitChangesDialog(tabContext).catch((error) => addEvent(error.message || String(error), "error"));
|
|
@@ -6679,10 +6782,46 @@ function refreshGitChangesDialog() {
|
|
|
6679
6782
|
loadGitChangesDialog(tabContext).catch((error) => addEvent(error.message || String(error), "error"));
|
|
6680
6783
|
}
|
|
6681
6784
|
|
|
6785
|
+
async function pullGitChangesDialog() {
|
|
6786
|
+
const tabContext = { tabId: gitChangesState.tabId || activeTabId };
|
|
6787
|
+
const behind = Number(gitChangesState.data?.remote?.behind ?? gitChangesState.data?.summary?.behind ?? 0) || 0;
|
|
6788
|
+
if (behind <= 0 || gitChangesState.pulling || gitChangesState.loading) return;
|
|
6789
|
+
const root = gitChangesState.data?.root || "the current repository";
|
|
6790
|
+
if (!window.confirm(`Run git pull --ff-only in ${root}?`)) return;
|
|
6791
|
+
|
|
6792
|
+
const requestSerial = ++gitChangesRequestSerial;
|
|
6793
|
+
gitChangesState = { ...gitChangesState, pulling: true, loading: false, error: "", message: "", tabId: tabContext.tabId };
|
|
6794
|
+
renderGitChangesDialog();
|
|
6795
|
+
try {
|
|
6796
|
+
const response = await api("/api/git-changes/pull", { method: "POST", body: {}, tabId: tabContext.tabId });
|
|
6797
|
+
if (requestSerial !== gitChangesRequestSerial) return;
|
|
6798
|
+
if (!response.ok) {
|
|
6799
|
+
const detail = [response.error, response.data?.stderr || response.data?.stdout].filter(Boolean).join("\n").trim();
|
|
6800
|
+
throw new Error(detail || "Failed to pull git changes");
|
|
6801
|
+
}
|
|
6802
|
+
const output = String(response.data?.stdout || response.data?.stderr || "").trim();
|
|
6803
|
+
gitChangesState = {
|
|
6804
|
+
loading: false,
|
|
6805
|
+
pulling: false,
|
|
6806
|
+
error: "",
|
|
6807
|
+
message: output || "Pulled remote changes successfully.",
|
|
6808
|
+
data: response.data?.changes || gitChangesState.data,
|
|
6809
|
+
tabId: tabContext.tabId,
|
|
6810
|
+
};
|
|
6811
|
+
addEvent("Pulled remote git changes.", "success");
|
|
6812
|
+
requestGitFooterWebuiPayload(tabContext, { force: true });
|
|
6813
|
+
} catch (error) {
|
|
6814
|
+
if (requestSerial !== gitChangesRequestSerial) return;
|
|
6815
|
+
gitChangesState = { ...gitChangesState, pulling: false, error: error.message || String(error) };
|
|
6816
|
+
addEvent(error.message || String(error), "error");
|
|
6817
|
+
}
|
|
6818
|
+
renderGitChangesDialog();
|
|
6819
|
+
}
|
|
6820
|
+
|
|
6682
6821
|
function closeGitChangesDialog() {
|
|
6683
6822
|
gitChangesRequestSerial += 1;
|
|
6684
6823
|
gitChangesUntrackedContentRequests.clear();
|
|
6685
|
-
gitChangesState = { ...gitChangesState, loading: false };
|
|
6824
|
+
gitChangesState = { ...gitChangesState, loading: false, pulling: false };
|
|
6686
6825
|
if (elements.gitChangesDialog?.open) elements.gitChangesDialog.close();
|
|
6687
6826
|
}
|
|
6688
6827
|
|
|
@@ -8477,6 +8616,29 @@ async function refreshAppRunners(tabContext = activeTabContext()) {
|
|
|
8477
8616
|
renderWidgets();
|
|
8478
8617
|
}
|
|
8479
8618
|
|
|
8619
|
+
function appRunnerFailureState(runnerId, error, data = activeAppRunnerData()) {
|
|
8620
|
+
const runners = Array.isArray(data.runners) ? data.runners : [];
|
|
8621
|
+
const runner = runners.find((item) => item.id === runnerId) || {};
|
|
8622
|
+
const message = cleanStatusText(error?.message || String(error) || "Unknown app runner error");
|
|
8623
|
+
const command = runner.displayCommand || runner.shortDisplayCommand || runner.label || runnerId || "app runner";
|
|
8624
|
+
const timestamp = new Date().toISOString();
|
|
8625
|
+
return {
|
|
8626
|
+
id: `start-error:${Date.now()}`,
|
|
8627
|
+
runnerId,
|
|
8628
|
+
kind: runner.kind || "custom",
|
|
8629
|
+
label: runner.label || "App runner failed",
|
|
8630
|
+
command: runner.command || "",
|
|
8631
|
+
args: Array.isArray(runner.args) ? runner.args : [],
|
|
8632
|
+
displayCommand: command,
|
|
8633
|
+
cwd: data.cwd || "",
|
|
8634
|
+
status: "error",
|
|
8635
|
+
startedAt: timestamp,
|
|
8636
|
+
endedAt: timestamp,
|
|
8637
|
+
lineCount: 3,
|
|
8638
|
+
lines: [`$ ${command}`, "# failed to start app runner", `# ${message}`],
|
|
8639
|
+
};
|
|
8640
|
+
}
|
|
8641
|
+
|
|
8480
8642
|
async function runAppRunner(runnerId) {
|
|
8481
8643
|
const tabContext = activeTabContext();
|
|
8482
8644
|
if (!tabContext.tabId || !runnerId) return;
|
|
@@ -8491,7 +8653,12 @@ async function runAppRunner(runnerId) {
|
|
|
8491
8653
|
const command = response.data?.activeRun?.displayCommand || "app runner";
|
|
8492
8654
|
addEvent(`started ${command}`, "info");
|
|
8493
8655
|
} catch (error) {
|
|
8494
|
-
if (isCurrentTabContext(tabContext))
|
|
8656
|
+
if (!isCurrentTabContext(tabContext)) return;
|
|
8657
|
+
const message = cleanStatusText(error.message || String(error));
|
|
8658
|
+
setAppRunnerData(tabContext.tabId, { activeRun: appRunnerFailureState(runnerId, error, activeAppRunnerData()) });
|
|
8659
|
+
renderAppRunnerControls();
|
|
8660
|
+
renderWidgets();
|
|
8661
|
+
addEvent(`app runner failed: ${message}`, "error");
|
|
8495
8662
|
}
|
|
8496
8663
|
}
|
|
8497
8664
|
|
|
@@ -8572,9 +8739,14 @@ function activeAppRunnerCustomConfig() {
|
|
|
8572
8739
|
return activeAppRunnerData().customRunnerConfig || { runners: [], projectRoot: "", displayProjectRoot: "", displayConfigFile: "" };
|
|
8573
8740
|
}
|
|
8574
8741
|
|
|
8575
|
-
function resetAppRunnerCustomDraft() {
|
|
8742
|
+
function resetAppRunnerCustomDraft({ clearFeedback = true } = {}) {
|
|
8576
8743
|
appRunnerCustomDraft = { id: "", label: "", command: "./", path: "", args: "" };
|
|
8577
8744
|
appRunnerFileBrowserState = { open: false, loading: false, path: "", data: null, error: "" };
|
|
8745
|
+
if (clearFeedback) appRunnerCustomFeedback = { type: "", message: "" };
|
|
8746
|
+
}
|
|
8747
|
+
|
|
8748
|
+
function setAppRunnerCustomFeedback(type, message) {
|
|
8749
|
+
appRunnerCustomFeedback = { type, message: cleanStatusText(message || "") };
|
|
8578
8750
|
}
|
|
8579
8751
|
|
|
8580
8752
|
function appRunnerRelativeDir(filePath) {
|
|
@@ -8634,8 +8806,10 @@ async function saveAppRunnerCustomRunner(form) {
|
|
|
8634
8806
|
updateAppRunnerCustomDraftFrom(form);
|
|
8635
8807
|
const payload = appRunnerCustomDraftPayload();
|
|
8636
8808
|
if (!payload.path) {
|
|
8809
|
+
setAppRunnerCustomFeedback("warning", "Custom app runner path is required.");
|
|
8810
|
+
renderAppRunnerInfoDialog();
|
|
8811
|
+
requestAnimationFrame(() => document.querySelector("#appRunnerCustomPathInput")?.focus());
|
|
8637
8812
|
addEvent("custom app runner path is required", "warn");
|
|
8638
|
-
form?.querySelector("#appRunnerCustomPathInput")?.focus();
|
|
8639
8813
|
return;
|
|
8640
8814
|
}
|
|
8641
8815
|
const tabContext = activeTabContext();
|
|
@@ -8643,13 +8817,18 @@ async function saveAppRunnerCustomRunner(form) {
|
|
|
8643
8817
|
const response = await api("/api/app-runner-config", { method: "POST", body: { runner: payload }, tabId: tabContext.tabId });
|
|
8644
8818
|
if (!isCurrentTabContext(tabContext)) return;
|
|
8645
8819
|
setAppRunnerData(tabContext.tabId, response.data || {});
|
|
8646
|
-
resetAppRunnerCustomDraft();
|
|
8820
|
+
resetAppRunnerCustomDraft({ clearFeedback: false });
|
|
8821
|
+
setAppRunnerCustomFeedback("success", "Saved custom app runner. It should now appear in the Run menu when available.");
|
|
8647
8822
|
renderAppRunnerControls();
|
|
8648
8823
|
renderWidgets();
|
|
8649
8824
|
renderAppRunnerInfoDialog();
|
|
8650
8825
|
addEvent("saved custom app runner", "info");
|
|
8651
8826
|
} catch (error) {
|
|
8652
|
-
if (isCurrentTabContext(tabContext))
|
|
8827
|
+
if (!isCurrentTabContext(tabContext)) return;
|
|
8828
|
+
const message = error.message || String(error);
|
|
8829
|
+
setAppRunnerCustomFeedback("error", `Custom app runner was not saved: ${message}`);
|
|
8830
|
+
renderAppRunnerInfoDialog();
|
|
8831
|
+
addEvent(`custom app runner was not saved: ${message}`, "error");
|
|
8653
8832
|
}
|
|
8654
8833
|
}
|
|
8655
8834
|
|
|
@@ -8659,13 +8838,18 @@ async function deleteAppRunnerCustomRunner(id) {
|
|
|
8659
8838
|
const response = await api("/api/app-runner-config", { method: "DELETE", body: { id }, tabId: tabContext.tabId });
|
|
8660
8839
|
if (!isCurrentTabContext(tabContext)) return;
|
|
8661
8840
|
setAppRunnerData(tabContext.tabId, response.data || {});
|
|
8662
|
-
if (appRunnerCustomDraft.id === id) resetAppRunnerCustomDraft();
|
|
8841
|
+
if (appRunnerCustomDraft.id === id) resetAppRunnerCustomDraft({ clearFeedback: false });
|
|
8842
|
+
setAppRunnerCustomFeedback("success", "Deleted custom app runner.");
|
|
8663
8843
|
renderAppRunnerControls();
|
|
8664
8844
|
renderWidgets();
|
|
8665
8845
|
renderAppRunnerInfoDialog();
|
|
8666
8846
|
addEvent("deleted custom app runner", "warn");
|
|
8667
8847
|
} catch (error) {
|
|
8668
|
-
if (isCurrentTabContext(tabContext))
|
|
8848
|
+
if (!isCurrentTabContext(tabContext)) return;
|
|
8849
|
+
const message = error.message || String(error);
|
|
8850
|
+
setAppRunnerCustomFeedback("error", `Custom app runner was not deleted: ${message}`);
|
|
8851
|
+
renderAppRunnerInfoDialog();
|
|
8852
|
+
addEvent(`custom app runner was not deleted: ${message}`, "error");
|
|
8669
8853
|
}
|
|
8670
8854
|
}
|
|
8671
8855
|
|
|
@@ -8750,9 +8934,10 @@ function renderAppRunnerCustomSection() {
|
|
|
8750
8934
|
existing.append(make("div", "app-runner-custom-empty muted", "No custom runners saved for this project yet."));
|
|
8751
8935
|
} else {
|
|
8752
8936
|
for (const runner of customRunners) {
|
|
8753
|
-
const row = make("div",
|
|
8937
|
+
const row = make("div", `app-runner-custom-item${runner.available === false ? " unavailable" : ""}`);
|
|
8754
8938
|
const details = make("div", "app-runner-custom-item-details");
|
|
8755
8939
|
details.append(make("strong", "", runner.label || runner.path || "custom runner"), make("code", "", runner.displayCommand || runner.path || ""));
|
|
8940
|
+
if (runner.unavailableReason) details.append(make("span", "app-runner-custom-warning", `Not available: ${runner.unavailableReason}`));
|
|
8756
8941
|
const actions = make("div", "app-runner-custom-item-actions");
|
|
8757
8942
|
const edit = make("button", "", "Edit");
|
|
8758
8943
|
edit.type = "button";
|
|
@@ -8774,6 +8959,13 @@ function renderAppRunnerCustomSection() {
|
|
|
8774
8959
|
}
|
|
8775
8960
|
section.append(existing);
|
|
8776
8961
|
|
|
8962
|
+
const diagnostics = Array.isArray(config.diagnostics) ? config.diagnostics.filter((item) => item?.message) : [];
|
|
8963
|
+
if (diagnostics.length) {
|
|
8964
|
+
const diagnosticList = make("div", "app-runner-custom-diagnostics");
|
|
8965
|
+
for (const item of diagnostics) diagnosticList.append(make("div", `app-runner-custom-feedback ${item.severity || "warning"}`, item.message));
|
|
8966
|
+
section.append(diagnosticList);
|
|
8967
|
+
}
|
|
8968
|
+
|
|
8777
8969
|
const form = make("div", "app-runner-custom-form");
|
|
8778
8970
|
const labelField = appRunnerInputField({ id: "appRunnerCustomLabelInput", label: "Label", value: appRunnerCustomDraft.label, placeholder: "My app" });
|
|
8779
8971
|
const commandField = appRunnerInputField({ id: "appRunnerCustomCommandInput", label: "Command", value: appRunnerCustomDraft.command || "./", placeholder: "./", hint: "Use ./ to execute the selected file directly, or use bash, python3, node, bun, uv run, etc." });
|
|
@@ -8798,6 +8990,7 @@ function renderAppRunnerCustomSection() {
|
|
|
8798
8990
|
reset.addEventListener("click", () => { resetAppRunnerCustomDraft(); renderAppRunnerInfoDialog(); });
|
|
8799
8991
|
formActions.append(save, reset);
|
|
8800
8992
|
form.append(formActions);
|
|
8993
|
+
if (appRunnerCustomFeedback.message) form.append(make("div", `app-runner-custom-feedback ${appRunnerCustomFeedback.type || "info"}`, appRunnerCustomFeedback.message));
|
|
8801
8994
|
const browser = renderAppRunnerFileBrowser();
|
|
8802
8995
|
if (browser) form.append(browser);
|
|
8803
8996
|
section.append(form);
|
|
@@ -14358,7 +14551,7 @@ function updateOptionalFeatureAvailability() {
|
|
|
14358
14551
|
optionalFeatureAvailability.tuiSkillsCommand = hasLoadedRpcCommand("skills");
|
|
14359
14552
|
optionalFeatureAvailability.todoProgressWidget = hasAvailableCommand("todo-progress-status") || optionalFeatureAvailability.todoProgressWidget || widgets.has("todo-progress");
|
|
14360
14553
|
optionalFeatureAvailability.tuiToolsCommand = hasLoadedRpcCommand("tools");
|
|
14361
|
-
optionalFeatureAvailability.remoteWebui = hasAvailableCommand("remote") || optionalFeatureAvailability.remoteWebui || statusEntries.has("pi-remote-webui") || widgets.has("pi-remote-webui");
|
|
14554
|
+
optionalFeatureAvailability.remoteWebui = hasAvailableCommand("remote") || optionalFeatureAvailability.remoteWebui || statusEntries.has("pi-remote-webui") || statusEntries.has(REMOTE_WEBUI_CONTROLS_STATUS_KEY) || widgets.has("pi-remote-webui");
|
|
14362
14555
|
optionalFeatureAvailability.themeBundle = availableThemes.length > 0;
|
|
14363
14556
|
requestGitFooterWebuiPayload();
|
|
14364
14557
|
renderOptionalFeatureControls();
|
|
@@ -14516,6 +14709,14 @@ function renderOptionalFeatureControls() {
|
|
|
14516
14709
|
optionalFeatureUnavailableMessage("remoteWebui"),
|
|
14517
14710
|
);
|
|
14518
14711
|
}
|
|
14712
|
+
if (elements.networkControlField) {
|
|
14713
|
+
elements.networkControlField.hidden = !hasRemoteWebuiCommand;
|
|
14714
|
+
elements.networkControlField.classList.toggle("feature-unavailable", !hasRemoteWebuiCommand);
|
|
14715
|
+
const label = elements.networkControlField.querySelector("label");
|
|
14716
|
+
const payload = remoteWebuiControlsPayload();
|
|
14717
|
+
if (label) label.textContent = payload?.title || "Remote WebUI";
|
|
14718
|
+
elements.networkControlField.title = hasRemoteWebuiCommand ? payload?.description || "Remote WebUI controls are provided by @firstpick/pi-package-remote-webui." : optionalFeatureUnavailableMessage("remoteWebui");
|
|
14719
|
+
}
|
|
14519
14720
|
|
|
14520
14721
|
renderOptionalFeaturePanel();
|
|
14521
14722
|
}
|
|
@@ -15947,21 +16148,25 @@ async function refreshNetworkStatus() {
|
|
|
15947
16148
|
renderNetworkStatus();
|
|
15948
16149
|
}
|
|
15949
16150
|
|
|
15950
|
-
async function
|
|
15951
|
-
const
|
|
15952
|
-
|
|
15953
|
-
|
|
15954
|
-
|
|
15955
|
-
|
|
15956
|
-
|
|
15957
|
-
return;
|
|
16151
|
+
async function runRemoteWebuiCommand(command) {
|
|
16152
|
+
const commandName = String(command || "").replace(/^\//, "").split(/\s+/, 1)[0] || "remote";
|
|
16153
|
+
if (!isOptionalFeatureEnabled("remoteWebui") || !hasAvailableCommand(commandName)) {
|
|
16154
|
+
const message = commandUnavailableMessage(commandName);
|
|
16155
|
+
addEvent(message, "warn");
|
|
16156
|
+
refreshCommands(activeTabContext()).catch((error) => addEvent(error.message || String(error), "error"));
|
|
16157
|
+
return false;
|
|
15958
16158
|
}
|
|
16159
|
+
await runNativeCommandMenu(command);
|
|
16160
|
+
return true;
|
|
16161
|
+
}
|
|
15959
16162
|
|
|
16163
|
+
async function toggleRemoteAuth() {
|
|
16164
|
+
const enable = !latestNetwork?.auth?.enabled;
|
|
15960
16165
|
elements.remoteAuthToggle.disabled = true;
|
|
15961
16166
|
try {
|
|
15962
|
-
|
|
15963
|
-
|
|
15964
|
-
|
|
16167
|
+
await runRemoteWebuiCommand(remoteWebuiCommand(enable ? "authOn" : "authOff", enable ? "/remote auth on" : "/remote auth off"));
|
|
16168
|
+
await delay(250);
|
|
16169
|
+
await refreshNetworkStatus();
|
|
15965
16170
|
} catch (error) {
|
|
15966
16171
|
addEvent(error.message || String(error), "error");
|
|
15967
16172
|
} finally {
|
|
@@ -16779,35 +16984,15 @@ function scheduleForegroundReconcile(reason = "resume", delay = FOREGROUND_RECON
|
|
|
16779
16984
|
}
|
|
16780
16985
|
|
|
16781
16986
|
async function openToNetwork() {
|
|
16782
|
-
|
|
16783
|
-
await closeNetworkAccess();
|
|
16784
|
-
return;
|
|
16785
|
-
}
|
|
16786
|
-
if (!confirm(`Open Pi Web UI to your local network?\n\nRemote PIN auth is ${latestNetwork?.auth?.enabled ? "ON" : "OFF"}. The Web UI can control Pi/tools, so only do this on a trusted LAN.`)) return;
|
|
16787
|
-
|
|
16987
|
+
const open = !!latestNetwork?.open;
|
|
16788
16988
|
elements.openNetworkButton.disabled = true;
|
|
16789
|
-
elements.openNetworkButton.textContent = "Opening…";
|
|
16989
|
+
elements.openNetworkButton.textContent = open ? "Closing…" : "Opening…";
|
|
16790
16990
|
try {
|
|
16791
|
-
await
|
|
16792
|
-
|
|
16793
|
-
renderNetworkStatus();
|
|
16794
|
-
addEvent("opening webui to local network", "warn");
|
|
16795
|
-
for (let attempt = 0; attempt < 20; attempt++) {
|
|
16796
|
-
await delay(350);
|
|
16797
|
-
try {
|
|
16798
|
-
await refreshNetworkStatus();
|
|
16799
|
-
if (latestNetwork?.open && !latestNetwork?.opening) {
|
|
16800
|
-
const url = latestNetwork.networkUrls?.[0];
|
|
16801
|
-
addEvent(`webui open to local network${url ? `: ${url}` : ""}`, "warn");
|
|
16802
|
-
return;
|
|
16803
|
-
}
|
|
16804
|
-
} catch {
|
|
16805
|
-
// The listener briefly drops while rebinding; retry.
|
|
16806
|
-
}
|
|
16807
|
-
}
|
|
16991
|
+
await runRemoteWebuiCommand(remoteWebuiCommand(open ? "close" : "open", open ? "/remote close" : "/remote"));
|
|
16992
|
+
await delay(350);
|
|
16808
16993
|
await refreshNetworkStatus();
|
|
16809
16994
|
} catch (error) {
|
|
16810
|
-
addEvent(error.message, "error");
|
|
16995
|
+
addEvent(error.message || String(error), "error");
|
|
16811
16996
|
} finally {
|
|
16812
16997
|
renderNetworkStatus();
|
|
16813
16998
|
}
|
|
@@ -16815,41 +17000,7 @@ async function openToNetwork() {
|
|
|
16815
17000
|
|
|
16816
17001
|
async function closeNetworkAccess() {
|
|
16817
17002
|
if (!latestNetwork?.open) return;
|
|
16818
|
-
|
|
16819
|
-
|
|
16820
|
-
elements.openNetworkButton.disabled = true;
|
|
16821
|
-
elements.openNetworkButton.textContent = "Closing…";
|
|
16822
|
-
try {
|
|
16823
|
-
await api("/api/network/close", { method: "POST", scoped: false });
|
|
16824
|
-
latestNetwork = { ...(latestNetwork || {}), opening: false, closing: true };
|
|
16825
|
-
renderNetworkStatus();
|
|
16826
|
-
addEvent("closing webui network access", "warn");
|
|
16827
|
-
let refreshFailed = false;
|
|
16828
|
-
for (let attempt = 0; attempt < 20; attempt++) {
|
|
16829
|
-
await delay(350);
|
|
16830
|
-
try {
|
|
16831
|
-
await refreshNetworkStatus();
|
|
16832
|
-
if (!latestNetwork?.open && !latestNetwork?.closing) {
|
|
16833
|
-
addEvent("webui closed to local-only access", "warn");
|
|
16834
|
-
return;
|
|
16835
|
-
}
|
|
16836
|
-
} catch {
|
|
16837
|
-
refreshFailed = true;
|
|
16838
|
-
// Remote tabs will lose access after the listener returns to localhost.
|
|
16839
|
-
}
|
|
16840
|
-
}
|
|
16841
|
-
if (refreshFailed) {
|
|
16842
|
-
latestNetwork = { ...(latestNetwork || {}), open: false, opening: false, closing: false, networkUrls: [] };
|
|
16843
|
-
renderNetworkStatus();
|
|
16844
|
-
addEvent("webui network access closed; reconnect from this machine if this tab loses access", "warn");
|
|
16845
|
-
return;
|
|
16846
|
-
}
|
|
16847
|
-
addEvent("network close requested, but the server still reports network access open", "warn");
|
|
16848
|
-
} catch (error) {
|
|
16849
|
-
addEvent(error.message, "error");
|
|
16850
|
-
} finally {
|
|
16851
|
-
renderNetworkStatus();
|
|
16852
|
-
}
|
|
17003
|
+
await openToNetwork();
|
|
16853
17004
|
}
|
|
16854
17005
|
|
|
16855
17006
|
function setServerActionStatus(message = "", level = "info") {
|
|
@@ -16865,10 +17016,11 @@ function updateServerActionButton() {
|
|
|
16865
17016
|
const button = elements.runServerActionButton;
|
|
16866
17017
|
if (!button) return;
|
|
16867
17018
|
button.disabled = !action;
|
|
16868
|
-
button.textContent = action === "restart" ? "Restart" : action === "update" ? "Update" : action === "stop" ? "Stop" : "Run";
|
|
17019
|
+
button.textContent = action === "restart" ? "Restart" : action === "update" || action === "update-all" ? "Update" : action === "stop" ? "Stop" : "Run";
|
|
16869
17020
|
button.classList.toggle("danger", action === "stop");
|
|
16870
17021
|
if (action === "restart") setServerActionStatus("Ready to restart the Web UI server.", "info");
|
|
16871
|
-
else if (action === "update") setServerActionStatus("Ready to run pi update, then restart the Web UI server.", "info");
|
|
17022
|
+
else if (action === "update") setServerActionStatus("Ready to run pi update for Pi only, then restart the Web UI server.", "info");
|
|
17023
|
+
else if (action === "update-all") setServerActionStatus("Ready to run pi update --all for Pi and configured packages, then restart the Web UI server.", "info");
|
|
16872
17024
|
else if (action === "stop") setServerActionStatus("Ready to stop the Web UI server.", "info");
|
|
16873
17025
|
else setServerActionStatus();
|
|
16874
17026
|
}
|
|
@@ -16976,6 +17128,7 @@ async function runSelectedServerAction() {
|
|
|
16976
17128
|
const action = elements.serverActionSelect?.value || "";
|
|
16977
17129
|
if (action === "restart") await restartServer();
|
|
16978
17130
|
else if (action === "update") await runPiUpdateAndRestart();
|
|
17131
|
+
else if (action === "update-all") await runPiUpdateAndRestart({ all: true });
|
|
16979
17132
|
else if (action === "stop") await stopServer();
|
|
16980
17133
|
}
|
|
16981
17134
|
|
|
@@ -18360,6 +18513,7 @@ elements.openNetworkButton.addEventListener("click", openToNetwork);
|
|
|
18360
18513
|
elements.serverActionSelect.addEventListener("change", updateServerActionButton);
|
|
18361
18514
|
elements.runServerActionButton.addEventListener("click", () => runSelectedServerAction().catch((error) => addEvent(error.message || String(error), "error")));
|
|
18362
18515
|
elements.updateNotificationUpdateButton?.addEventListener("click", () => runPiUpdateAndRestart().catch((error) => addEvent(error.message || String(error), "error")));
|
|
18516
|
+
elements.updateNotificationUpdateAllButton?.addEventListener("click", () => runPiUpdateAndRestart({ all: true }).catch((error) => addEvent(error.message || String(error), "error")));
|
|
18363
18517
|
elements.updateNotificationDismissButton?.addEventListener("click", () => hideUpdateNotification({ remember: true }));
|
|
18364
18518
|
updateServerActionButton();
|
|
18365
18519
|
elements.agentDoneNotificationsToggle.addEventListener("change", () => {
|
|
@@ -18713,6 +18867,7 @@ window.addEventListener("blur", () => {
|
|
|
18713
18867
|
});
|
|
18714
18868
|
|
|
18715
18869
|
elements.gitChangesRefreshButton?.addEventListener("click", refreshGitChangesDialog);
|
|
18870
|
+
elements.gitChangesPullButton?.addEventListener("click", () => pullGitChangesDialog().catch((error) => addEvent(error.message || String(error), "error")));
|
|
18716
18871
|
elements.gitChangesCloseButton?.addEventListener("click", closeGitChangesDialog);
|
|
18717
18872
|
elements.gitChangesBody?.addEventListener("scroll", updateGitChangesCurrentFileHeader, { passive: true });
|
|
18718
18873
|
elements.gitChangesDialog?.addEventListener("cancel", (event) => {
|
|
@@ -18721,7 +18876,7 @@ elements.gitChangesDialog?.addEventListener("cancel", (event) => {
|
|
|
18721
18876
|
});
|
|
18722
18877
|
elements.gitChangesDialog?.addEventListener("close", () => {
|
|
18723
18878
|
gitChangesRequestSerial += 1;
|
|
18724
|
-
gitChangesState = { ...gitChangesState, loading: false };
|
|
18879
|
+
gitChangesState = { ...gitChangesState, loading: false, pulling: false };
|
|
18725
18880
|
});
|
|
18726
18881
|
|
|
18727
18882
|
elements.refreshCodexUsageButton?.addEventListener("click", () => {
|