vanspod 0.1.2 → 0.1.3
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 +81 -75
- package/bin/register.cjs +11 -11
- package/bin/vanspod.cjs +2 -2
- package/dist/classroomUpdate.js +142 -142
- package/dist/connectionMascot.js +215 -215
- package/dist/installProgress.js +167 -167
- package/dist/lifecyclePage.js +33 -33
- package/dist/modelStatus.js +38 -38
- package/dist/version.js +1 -1
- package/package.json +30 -29
- package/scripts/uninstall.ps1 +133 -133
package/dist/installProgress.js
CHANGED
|
@@ -1,171 +1,171 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.installProgressScript = void 0;
|
|
4
|
-
exports.installProgressScript = `
|
|
5
|
-
var installBusy = false;
|
|
6
|
-
var installOpening = false;
|
|
7
|
-
function closeInstallDialog(target) {
|
|
8
|
-
target.closest(".confirm-dialog").remove();
|
|
9
|
-
installBusy = false;
|
|
10
|
-
}
|
|
11
|
-
document.addEventListener("click", function (event) {
|
|
12
|
-
var button = event.target?.closest?.("[data-install-dismiss]");
|
|
13
|
-
if (button && !button.disabled) closeInstallDialog(button);
|
|
14
|
-
});
|
|
15
|
-
document.addEventListener("submit", async function (event) {
|
|
16
|
-
var form = event.target;
|
|
17
|
-
if (form?.matches?.("form[data-abort-install]")) {
|
|
18
|
-
if (!window.confirm("停止後安裝可能不完整,確定停止?")) { event.preventDefault(); return; }
|
|
19
|
-
if (!installBusy || !form.closest(".confirm-panel")) return;
|
|
20
|
-
event.preventDefault();
|
|
21
|
-
var abortButton = form.querySelector("button");
|
|
22
|
-
abortButton.disabled = true;
|
|
23
|
-
form.closest(".confirm-panel").querySelector("[data-install-status]").textContent = "正在停止安裝…";
|
|
24
|
-
try {
|
|
25
|
-
var stopped = await fetch(form.action, { method: "POST", headers: { "X-Classroom-Progress": "1" } });
|
|
26
|
-
if (!stopped.ok) throw new Error("abort failed");
|
|
27
|
-
} catch (error) {
|
|
28
|
-
abortButton.disabled = false;
|
|
29
|
-
}
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
if (form?.matches?.('form[action="/environment"]')) {
|
|
33
|
-
var card = form.closest("section");
|
|
34
|
-
if (!card) return;
|
|
35
|
-
card.querySelectorAll('button[form="settings-environment"]').forEach(function (start) { start.hidden = true; });
|
|
36
|
-
card.querySelectorAll("form[data-abort-install]").forEach(function (abort) { abort.hidden = false; });
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
if (form?.matches?.('form[action="/prepare"]')) {
|
|
40
|
-
event.preventDefault();
|
|
41
|
-
if (installOpening || installBusy || document.querySelector(".confirm-dialog")) return;
|
|
42
|
-
installOpening = true;
|
|
43
|
-
var button = form.querySelector("button");
|
|
44
|
-
var card = form.closest(".install-card");
|
|
45
|
-
var previousError = card?.querySelector(".install-open-error");
|
|
46
|
-
if (previousError) previousError.remove();
|
|
47
|
-
button.disabled = true;
|
|
48
|
-
try {
|
|
49
|
-
var prepared = await fetch(form.action, {
|
|
50
|
-
method: "POST",
|
|
51
|
-
headers: { "X-Classroom-Progress": "1" },
|
|
52
|
-
body: new URLSearchParams(new FormData(form)),
|
|
53
|
-
});
|
|
54
|
-
if (!prepared.ok) throw new Error("prepare failed");
|
|
55
|
-
var page = new DOMParser().parseFromString(await prepared.text(), "text/html");
|
|
56
|
-
var dialog = page.querySelector(".confirm-dialog");
|
|
57
|
-
if (!dialog) throw new Error("dialog unavailable");
|
|
58
|
-
document.body.appendChild(dialog);
|
|
59
|
-
} catch (error) {
|
|
60
|
-
var message = document.createElement("p");
|
|
61
|
-
message.className = "error install-open-error";
|
|
62
|
-
message.setAttribute("role", "alert");
|
|
63
|
-
message.textContent = "無法開啟安裝視窗,請確認專案資料夾已設定後再試。";
|
|
64
|
-
(card || form).appendChild(message);
|
|
65
|
-
} finally {
|
|
66
|
-
button.disabled = false;
|
|
67
|
-
installOpening = false;
|
|
68
|
-
}
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
if (!form || !form.matches || !form.matches('form[action="/confirm"], form[action="/cancel"]')) return;
|
|
72
|
-
event.preventDefault();
|
|
73
|
-
if (installBusy) return;
|
|
74
|
-
installBusy = true;
|
|
75
|
-
var panel = form.closest(".confirm-panel");
|
|
76
|
-
var title = panel.querySelector("h2");
|
|
77
|
-
var status = panel.querySelector("[data-install-status]");
|
|
78
|
-
var output = panel.querySelector("[data-install-output]");
|
|
79
|
-
var dismiss = panel.querySelector("[data-install-dismiss]");
|
|
80
|
-
var choices = panel.querySelector(".choices");
|
|
81
|
-
var buttons = panel.querySelectorAll("button");
|
|
82
|
-
buttons.forEach(function (button) { button.disabled = true; });
|
|
83
|
-
if (form.matches('form[action="/cancel"]')) {
|
|
84
|
-
try {
|
|
85
|
-
var canceled = await fetch(form.action, {
|
|
86
|
-
method: "POST",
|
|
87
|
-
headers: { "X-Classroom-Progress": "1" },
|
|
88
|
-
body: new URLSearchParams(new FormData(form)),
|
|
89
|
-
});
|
|
90
|
-
if (!canceled.ok) throw new Error("cancel failed");
|
|
91
|
-
closeInstallDialog(form);
|
|
92
|
-
} catch (error) {
|
|
93
|
-
status.textContent = "取消失敗,請再試一次。";
|
|
94
|
-
buttons.forEach(function (button) { button.disabled = false; });
|
|
95
|
-
installBusy = false;
|
|
96
|
-
}
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
var abortForm = panel.querySelector("form[data-abort-install]");
|
|
100
|
-
if (abortForm) {
|
|
101
|
-
panel.querySelector('form[action="/confirm"]')?.setAttribute("data-reserved", "");
|
|
102
|
-
var cancelForm = panel.querySelector('form[action="/cancel"]');
|
|
103
|
-
if (cancelForm) cancelForm.hidden = true;
|
|
104
|
-
abortForm.hidden = false;
|
|
105
|
-
abortForm.querySelector("button").disabled = false;
|
|
106
|
-
}
|
|
107
|
-
panel.setAttribute("aria-busy", "true");
|
|
108
|
-
title.textContent = "安裝中";
|
|
109
|
-
status.textContent = "正在執行,請稍候…";
|
|
110
|
-
output.hidden = false;
|
|
111
|
-
var finished = false;
|
|
112
|
-
var timer;
|
|
113
|
-
async function update() {
|
|
114
|
-
try {
|
|
115
|
-
var response = await fetch("/command-status", { cache: "no-store" });
|
|
116
|
-
if (!response.ok) throw new Error("status unavailable");
|
|
117
|
-
var progress = await response.json();
|
|
118
|
-
if (finished) return;
|
|
119
|
-
var atBottom = output.scrollTop + output.clientHeight >= output.scrollHeight - 24;
|
|
120
|
-
output.textContent = progress.output || "等待安裝程式輸出…";
|
|
121
|
-
if (atBottom) output.scrollTop = output.scrollHeight;
|
|
122
|
-
status.textContent = "正在執行,請稍候…";
|
|
123
|
-
} catch (error) {
|
|
124
|
-
if (!finished) status.textContent = "暫時無法更新進度,正在重新連線…";
|
|
125
|
-
}
|
|
126
|
-
if (!finished) timer = setTimeout(update, 500);
|
|
127
|
-
}
|
|
128
|
-
try {
|
|
129
|
-
var request = fetch(form.action, {
|
|
130
|
-
method: "POST",
|
|
131
|
-
headers: { "X-Classroom-Progress": "1" },
|
|
132
|
-
body: new URLSearchParams(new FormData(form)),
|
|
133
|
-
});
|
|
134
|
-
update();
|
|
135
|
-
var result = await request;
|
|
136
|
-
if (!result.ok) throw new Error("confirmation failed");
|
|
137
|
-
var outcome = await result.json();
|
|
138
|
-
if (outcome.succeeded && Array.isArray(outcome.installedActionIds)) {
|
|
139
|
-
document.querySelectorAll('form[action="/prepare"] input[name="action_id"]').forEach(function (input) {
|
|
140
|
-
if (!outcome.installedActionIds.includes(input.value)) return;
|
|
141
|
-
var card = input.closest(".install-card");
|
|
142
|
-
if (!card || card.classList.contains("installed")) return;
|
|
143
|
-
card.classList.add("installed");
|
|
144
|
-
var button = card.querySelector('form[action="/prepare"] button');
|
|
145
|
-
button.classList.add("quiet");
|
|
146
|
-
button.textContent = "重新安裝";
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
finished = true;
|
|
150
|
-
clearTimeout(timer);
|
|
151
|
-
if (abortForm) abortForm.hidden = true;
|
|
152
|
-
panel.setAttribute("aria-busy", "false");
|
|
153
|
-
title.textContent = outcome.aborted ? "已中止" : outcome.succeeded ? "安裝成功" : "安裝失敗";
|
|
154
|
-
status.textContent = "請查看執行結果,按下確認後關閉。";
|
|
155
|
-
output.textContent = outcome.output || (outcome.succeeded ? "安裝完成。" : "指令執行失敗。");
|
|
156
|
-
choices.hidden = true;
|
|
157
|
-
dismiss.disabled = false;
|
|
158
|
-
dismiss.hidden = false;
|
|
159
|
-
} catch (error) {
|
|
160
|
-
finished = true;
|
|
161
|
-
clearTimeout(timer);
|
|
162
|
-
if (abortForm) abortForm.hidden = true;
|
|
163
|
-
panel.setAttribute("aria-busy", "false");
|
|
164
|
-
title.textContent = "無法確認安裝結果";
|
|
165
|
-
status.textContent = "請重新整理頁面查看結果,避免重複安裝。";
|
|
166
|
-
choices.hidden = true;
|
|
167
|
-
dismiss.disabled = false;
|
|
168
|
-
dismiss.hidden = false;
|
|
169
|
-
}
|
|
170
|
-
});
|
|
4
|
+
exports.installProgressScript = `
|
|
5
|
+
var installBusy = false;
|
|
6
|
+
var installOpening = false;
|
|
7
|
+
function closeInstallDialog(target) {
|
|
8
|
+
target.closest(".confirm-dialog").remove();
|
|
9
|
+
installBusy = false;
|
|
10
|
+
}
|
|
11
|
+
document.addEventListener("click", function (event) {
|
|
12
|
+
var button = event.target?.closest?.("[data-install-dismiss]");
|
|
13
|
+
if (button && !button.disabled) closeInstallDialog(button);
|
|
14
|
+
});
|
|
15
|
+
document.addEventListener("submit", async function (event) {
|
|
16
|
+
var form = event.target;
|
|
17
|
+
if (form?.matches?.("form[data-abort-install]")) {
|
|
18
|
+
if (!window.confirm("停止後安裝可能不完整,確定停止?")) { event.preventDefault(); return; }
|
|
19
|
+
if (!installBusy || !form.closest(".confirm-panel")) return;
|
|
20
|
+
event.preventDefault();
|
|
21
|
+
var abortButton = form.querySelector("button");
|
|
22
|
+
abortButton.disabled = true;
|
|
23
|
+
form.closest(".confirm-panel").querySelector("[data-install-status]").textContent = "正在停止安裝…";
|
|
24
|
+
try {
|
|
25
|
+
var stopped = await fetch(form.action, { method: "POST", headers: { "X-Classroom-Progress": "1" } });
|
|
26
|
+
if (!stopped.ok) throw new Error("abort failed");
|
|
27
|
+
} catch (error) {
|
|
28
|
+
abortButton.disabled = false;
|
|
29
|
+
}
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (form?.matches?.('form[action="/environment"]')) {
|
|
33
|
+
var card = form.closest("section");
|
|
34
|
+
if (!card) return;
|
|
35
|
+
card.querySelectorAll('button[form="settings-environment"]').forEach(function (start) { start.hidden = true; });
|
|
36
|
+
card.querySelectorAll("form[data-abort-install]").forEach(function (abort) { abort.hidden = false; });
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (form?.matches?.('form[action="/prepare"]')) {
|
|
40
|
+
event.preventDefault();
|
|
41
|
+
if (installOpening || installBusy || document.querySelector(".confirm-dialog")) return;
|
|
42
|
+
installOpening = true;
|
|
43
|
+
var button = form.querySelector("button");
|
|
44
|
+
var card = form.closest(".install-card");
|
|
45
|
+
var previousError = card?.querySelector(".install-open-error");
|
|
46
|
+
if (previousError) previousError.remove();
|
|
47
|
+
button.disabled = true;
|
|
48
|
+
try {
|
|
49
|
+
var prepared = await fetch(form.action, {
|
|
50
|
+
method: "POST",
|
|
51
|
+
headers: { "X-Classroom-Progress": "1" },
|
|
52
|
+
body: new URLSearchParams(new FormData(form)),
|
|
53
|
+
});
|
|
54
|
+
if (!prepared.ok) throw new Error("prepare failed");
|
|
55
|
+
var page = new DOMParser().parseFromString(await prepared.text(), "text/html");
|
|
56
|
+
var dialog = page.querySelector(".confirm-dialog");
|
|
57
|
+
if (!dialog) throw new Error("dialog unavailable");
|
|
58
|
+
document.body.appendChild(dialog);
|
|
59
|
+
} catch (error) {
|
|
60
|
+
var message = document.createElement("p");
|
|
61
|
+
message.className = "error install-open-error";
|
|
62
|
+
message.setAttribute("role", "alert");
|
|
63
|
+
message.textContent = "無法開啟安裝視窗,請確認專案資料夾已設定後再試。";
|
|
64
|
+
(card || form).appendChild(message);
|
|
65
|
+
} finally {
|
|
66
|
+
button.disabled = false;
|
|
67
|
+
installOpening = false;
|
|
68
|
+
}
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (!form || !form.matches || !form.matches('form[action="/confirm"], form[action="/cancel"]')) return;
|
|
72
|
+
event.preventDefault();
|
|
73
|
+
if (installBusy) return;
|
|
74
|
+
installBusy = true;
|
|
75
|
+
var panel = form.closest(".confirm-panel");
|
|
76
|
+
var title = panel.querySelector("h2");
|
|
77
|
+
var status = panel.querySelector("[data-install-status]");
|
|
78
|
+
var output = panel.querySelector("[data-install-output]");
|
|
79
|
+
var dismiss = panel.querySelector("[data-install-dismiss]");
|
|
80
|
+
var choices = panel.querySelector(".choices");
|
|
81
|
+
var buttons = panel.querySelectorAll("button");
|
|
82
|
+
buttons.forEach(function (button) { button.disabled = true; });
|
|
83
|
+
if (form.matches('form[action="/cancel"]')) {
|
|
84
|
+
try {
|
|
85
|
+
var canceled = await fetch(form.action, {
|
|
86
|
+
method: "POST",
|
|
87
|
+
headers: { "X-Classroom-Progress": "1" },
|
|
88
|
+
body: new URLSearchParams(new FormData(form)),
|
|
89
|
+
});
|
|
90
|
+
if (!canceled.ok) throw new Error("cancel failed");
|
|
91
|
+
closeInstallDialog(form);
|
|
92
|
+
} catch (error) {
|
|
93
|
+
status.textContent = "取消失敗,請再試一次。";
|
|
94
|
+
buttons.forEach(function (button) { button.disabled = false; });
|
|
95
|
+
installBusy = false;
|
|
96
|
+
}
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
var abortForm = panel.querySelector("form[data-abort-install]");
|
|
100
|
+
if (abortForm) {
|
|
101
|
+
panel.querySelector('form[action="/confirm"]')?.setAttribute("data-reserved", "");
|
|
102
|
+
var cancelForm = panel.querySelector('form[action="/cancel"]');
|
|
103
|
+
if (cancelForm) cancelForm.hidden = true;
|
|
104
|
+
abortForm.hidden = false;
|
|
105
|
+
abortForm.querySelector("button").disabled = false;
|
|
106
|
+
}
|
|
107
|
+
panel.setAttribute("aria-busy", "true");
|
|
108
|
+
title.textContent = "安裝中";
|
|
109
|
+
status.textContent = "正在執行,請稍候…";
|
|
110
|
+
output.hidden = false;
|
|
111
|
+
var finished = false;
|
|
112
|
+
var timer;
|
|
113
|
+
async function update() {
|
|
114
|
+
try {
|
|
115
|
+
var response = await fetch("/command-status", { cache: "no-store" });
|
|
116
|
+
if (!response.ok) throw new Error("status unavailable");
|
|
117
|
+
var progress = await response.json();
|
|
118
|
+
if (finished) return;
|
|
119
|
+
var atBottom = output.scrollTop + output.clientHeight >= output.scrollHeight - 24;
|
|
120
|
+
output.textContent = progress.output || "等待安裝程式輸出…";
|
|
121
|
+
if (atBottom) output.scrollTop = output.scrollHeight;
|
|
122
|
+
status.textContent = "正在執行,請稍候…";
|
|
123
|
+
} catch (error) {
|
|
124
|
+
if (!finished) status.textContent = "暫時無法更新進度,正在重新連線…";
|
|
125
|
+
}
|
|
126
|
+
if (!finished) timer = setTimeout(update, 500);
|
|
127
|
+
}
|
|
128
|
+
try {
|
|
129
|
+
var request = fetch(form.action, {
|
|
130
|
+
method: "POST",
|
|
131
|
+
headers: { "X-Classroom-Progress": "1" },
|
|
132
|
+
body: new URLSearchParams(new FormData(form)),
|
|
133
|
+
});
|
|
134
|
+
update();
|
|
135
|
+
var result = await request;
|
|
136
|
+
if (!result.ok) throw new Error("confirmation failed");
|
|
137
|
+
var outcome = await result.json();
|
|
138
|
+
if (outcome.succeeded && Array.isArray(outcome.installedActionIds)) {
|
|
139
|
+
document.querySelectorAll('form[action="/prepare"] input[name="action_id"]').forEach(function (input) {
|
|
140
|
+
if (!outcome.installedActionIds.includes(input.value)) return;
|
|
141
|
+
var card = input.closest(".install-card");
|
|
142
|
+
if (!card || card.classList.contains("installed")) return;
|
|
143
|
+
card.classList.add("installed");
|
|
144
|
+
var button = card.querySelector('form[action="/prepare"] button');
|
|
145
|
+
button.classList.add("quiet");
|
|
146
|
+
button.textContent = "重新安裝";
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
finished = true;
|
|
150
|
+
clearTimeout(timer);
|
|
151
|
+
if (abortForm) abortForm.hidden = true;
|
|
152
|
+
panel.setAttribute("aria-busy", "false");
|
|
153
|
+
title.textContent = outcome.aborted ? "已中止" : outcome.succeeded ? "安裝成功" : "安裝失敗";
|
|
154
|
+
status.textContent = "請查看執行結果,按下確認後關閉。";
|
|
155
|
+
output.textContent = outcome.output || (outcome.succeeded ? "安裝完成。" : "指令執行失敗。");
|
|
156
|
+
choices.hidden = true;
|
|
157
|
+
dismiss.disabled = false;
|
|
158
|
+
dismiss.hidden = false;
|
|
159
|
+
} catch (error) {
|
|
160
|
+
finished = true;
|
|
161
|
+
clearTimeout(timer);
|
|
162
|
+
if (abortForm) abortForm.hidden = true;
|
|
163
|
+
panel.setAttribute("aria-busy", "false");
|
|
164
|
+
title.textContent = "無法確認安裝結果";
|
|
165
|
+
status.textContent = "請重新整理頁面查看結果,避免重複安裝。";
|
|
166
|
+
choices.hidden = true;
|
|
167
|
+
dismiss.disabled = false;
|
|
168
|
+
dismiss.hidden = false;
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
171
|
`;
|
package/dist/lifecyclePage.js
CHANGED
|
@@ -3,39 +3,39 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.lifecyclePage = lifecyclePage;
|
|
4
4
|
function lifecyclePage(html, token) {
|
|
5
5
|
const controls = `<div style="margin-top:16px"><button type="button" data-lifecycle="stop">結束 VansPod</button> <button type="button" data-lifecycle="uninstall">移除 VansPod</button><p role="status" data-lifecycle-message></p></div>`;
|
|
6
|
-
const script = `<script>
|
|
7
|
-
(function () {
|
|
8
|
-
var token = ${JSON.stringify(token)};
|
|
9
|
-
async function request(action, body) {
|
|
10
|
-
var response = await fetch('/_vanspod/' + action, {method: body ? 'POST' : 'GET', headers: {'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json'}, body: body ? JSON.stringify(body) : undefined});
|
|
11
|
-
if (!response.ok) throw new Error('無法完成操作,請重新開啟 VansPod。');
|
|
12
|
-
return response.json();
|
|
13
|
-
}
|
|
14
|
-
document.addEventListener('click', async function (event) {
|
|
15
|
-
var button = event.target.closest && event.target.closest('[data-lifecycle]');
|
|
16
|
-
if (!button) return;
|
|
17
|
-
var message = document.querySelector('[data-lifecycle-message]');
|
|
18
|
-
button.disabled = true;
|
|
19
|
-
try {
|
|
20
|
-
var action = button.dataset.lifecycle;
|
|
21
|
-
var confirmed = false;
|
|
22
|
-
if (action === 'uninstall') {
|
|
23
|
-
var preview = await request('uninstall-preview');
|
|
24
|
-
if (!preview.ok) { message.textContent = preview.message; return; }
|
|
25
|
-
confirmed = window.confirm(preview.message + '\\n\\n確定完整移除 VansPod?');
|
|
26
|
-
if (!confirmed) return;
|
|
27
|
-
}
|
|
28
|
-
var result = await request(action, {confirmed: confirmed});
|
|
29
|
-
if (result.confirmation && window.confirm(result.message)) result = await request(action, {confirmed: true, interruptConfirmed: true});
|
|
30
|
-
message.textContent = result.message || (result.ok ? 'VansPod 已結束,可關閉此分頁。' : '操作未完成,請重試。');
|
|
31
|
-
} catch (error) { message.textContent = error.message; }
|
|
32
|
-
finally { button.disabled = false; }
|
|
33
|
-
});
|
|
34
|
-
request('status').then(function (status) {
|
|
35
|
-
if (status.startupError) { document.querySelector('[data-lifecycle-message]').textContent = status.startupError; return; }
|
|
36
|
-
if (status.launchVersion && status.launchVersion !== status.version) document.querySelector('[data-lifecycle-message]').textContent = '目前執行版本 ' + status.version + ',本次開啟版本 ' + status.launchVersion + '。換版請先結束 VansPod。';
|
|
37
|
-
}).catch(function () {});
|
|
38
|
-
})();
|
|
6
|
+
const script = `<script>
|
|
7
|
+
(function () {
|
|
8
|
+
var token = ${JSON.stringify(token)};
|
|
9
|
+
async function request(action, body) {
|
|
10
|
+
var response = await fetch('/_vanspod/' + action, {method: body ? 'POST' : 'GET', headers: {'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json'}, body: body ? JSON.stringify(body) : undefined});
|
|
11
|
+
if (!response.ok) throw new Error('無法完成操作,請重新開啟 VansPod。');
|
|
12
|
+
return response.json();
|
|
13
|
+
}
|
|
14
|
+
document.addEventListener('click', async function (event) {
|
|
15
|
+
var button = event.target.closest && event.target.closest('[data-lifecycle]');
|
|
16
|
+
if (!button) return;
|
|
17
|
+
var message = document.querySelector('[data-lifecycle-message]');
|
|
18
|
+
button.disabled = true;
|
|
19
|
+
try {
|
|
20
|
+
var action = button.dataset.lifecycle;
|
|
21
|
+
var confirmed = false;
|
|
22
|
+
if (action === 'uninstall') {
|
|
23
|
+
var preview = await request('uninstall-preview');
|
|
24
|
+
if (!preview.ok) { message.textContent = preview.message; return; }
|
|
25
|
+
confirmed = window.confirm(preview.message + '\\n\\n確定完整移除 VansPod?');
|
|
26
|
+
if (!confirmed) return;
|
|
27
|
+
}
|
|
28
|
+
var result = await request(action, {confirmed: confirmed});
|
|
29
|
+
if (result.confirmation && window.confirm(result.message)) result = await request(action, {confirmed: true, interruptConfirmed: true});
|
|
30
|
+
message.textContent = result.message || (result.ok ? 'VansPod 已結束,可關閉此分頁。' : '操作未完成,請重試。');
|
|
31
|
+
} catch (error) { message.textContent = error.message; }
|
|
32
|
+
finally { button.disabled = false; }
|
|
33
|
+
});
|
|
34
|
+
request('status').then(function (status) {
|
|
35
|
+
if (status.startupError) { document.querySelector('[data-lifecycle-message]').textContent = status.startupError; return; }
|
|
36
|
+
if (status.launchVersion && status.launchVersion !== status.version) document.querySelector('[data-lifecycle-message]').textContent = '目前執行版本 ' + status.version + ',本次開啟版本 ' + status.launchVersion + '。換版請先結束 VansPod。';
|
|
37
|
+
}).catch(function () {});
|
|
38
|
+
})();
|
|
39
39
|
</script>`;
|
|
40
40
|
return html.replace(/<form method="post" action="\/stop">[\s\S]*?<\/form>/, controls).replace('</body>', `${script}</body>`);
|
|
41
41
|
}
|
package/dist/modelStatus.js
CHANGED
|
@@ -1,42 +1,42 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.modelStatusScript = void 0;
|
|
4
|
-
exports.modelStatusScript = `
|
|
5
|
-
async function refreshModelStatus() {
|
|
6
|
-
try {
|
|
7
|
-
if (!document.hidden && !settingsBusy) {
|
|
8
|
-
var response = await fetch("/model-status", { cache: "no-store" });
|
|
9
|
-
if (!response.ok) throw new Error("status unavailable");
|
|
10
|
-
var status = await response.json();
|
|
11
|
-
// A settings action redraws the whole page; never paint an older answer over it.
|
|
12
|
-
if (!settingsBusy) applyModelStatus(status);
|
|
13
|
-
}
|
|
14
|
-
} catch (error) {}
|
|
15
|
-
setTimeout(refreshModelStatus, 1000);
|
|
16
|
-
}
|
|
17
|
-
function applyModelStatus(status) {
|
|
18
|
-
document.querySelectorAll("[data-client-light]").forEach(function (light) {
|
|
19
|
-
var next = status.lights[light.getAttribute("data-client-light")];
|
|
20
|
-
if (!next || (light.className === next.className && light.title === next.status)) return;
|
|
21
|
-
light.className = next.className;
|
|
22
|
-
light.title = next.status;
|
|
23
|
-
light.setAttribute("aria-label", next.status);
|
|
24
|
-
});
|
|
25
|
-
if (status.traffic) document.querySelectorAll("[data-client-flow]").forEach(function (flow) {
|
|
26
|
-
flow.classList.toggle("flowing", status.traffic[flow.getAttribute("data-client-flow")] === true);
|
|
27
|
-
});
|
|
28
|
-
var card = document.querySelector('[data-setting="模型"]');
|
|
29
|
-
if (!card) return;
|
|
30
|
-
var banner = card.querySelector(".model-update");
|
|
31
|
-
var holder = document.createElement("template");
|
|
32
|
-
holder.innerHTML = status.restart;
|
|
33
|
-
var updated = holder.content.firstElementChild;
|
|
34
|
-
if (typeof refreshClassroomRestart === 'function') refreshClassroomRestart(status.restart);
|
|
35
|
-
if (document.querySelector('[data-classroom-restart]')) return;
|
|
36
|
-
if (banner?.getAttribute("data-restart-clients") === updated?.getAttribute("data-restart-clients")) return;
|
|
37
|
-
if (banner && updated) banner.replaceWith(updated);
|
|
38
|
-
else if (banner) banner.remove();
|
|
39
|
-
else if (updated) card.querySelector(".settings-actions").before(updated);
|
|
40
|
-
}
|
|
41
|
-
setTimeout(refreshModelStatus, 1000);
|
|
4
|
+
exports.modelStatusScript = `
|
|
5
|
+
async function refreshModelStatus() {
|
|
6
|
+
try {
|
|
7
|
+
if (!document.hidden && !settingsBusy) {
|
|
8
|
+
var response = await fetch("/model-status", { cache: "no-store" });
|
|
9
|
+
if (!response.ok) throw new Error("status unavailable");
|
|
10
|
+
var status = await response.json();
|
|
11
|
+
// A settings action redraws the whole page; never paint an older answer over it.
|
|
12
|
+
if (!settingsBusy) applyModelStatus(status);
|
|
13
|
+
}
|
|
14
|
+
} catch (error) {}
|
|
15
|
+
setTimeout(refreshModelStatus, 1000);
|
|
16
|
+
}
|
|
17
|
+
function applyModelStatus(status) {
|
|
18
|
+
document.querySelectorAll("[data-client-light]").forEach(function (light) {
|
|
19
|
+
var next = status.lights[light.getAttribute("data-client-light")];
|
|
20
|
+
if (!next || (light.className === next.className && light.title === next.status)) return;
|
|
21
|
+
light.className = next.className;
|
|
22
|
+
light.title = next.status;
|
|
23
|
+
light.setAttribute("aria-label", next.status);
|
|
24
|
+
});
|
|
25
|
+
if (status.traffic) document.querySelectorAll("[data-client-flow]").forEach(function (flow) {
|
|
26
|
+
flow.classList.toggle("flowing", status.traffic[flow.getAttribute("data-client-flow")] === true);
|
|
27
|
+
});
|
|
28
|
+
var card = document.querySelector('[data-setting="模型"]');
|
|
29
|
+
if (!card) return;
|
|
30
|
+
var banner = card.querySelector(".model-update");
|
|
31
|
+
var holder = document.createElement("template");
|
|
32
|
+
holder.innerHTML = status.restart;
|
|
33
|
+
var updated = holder.content.firstElementChild;
|
|
34
|
+
if (typeof refreshClassroomRestart === 'function') refreshClassroomRestart(status.restart);
|
|
35
|
+
if (document.querySelector('[data-classroom-restart]')) return;
|
|
36
|
+
if (banner?.getAttribute("data-restart-clients") === updated?.getAttribute("data-restart-clients")) return;
|
|
37
|
+
if (banner && updated) banner.replaceWith(updated);
|
|
38
|
+
else if (banner) banner.remove();
|
|
39
|
+
else if (updated) card.querySelector(".settings-actions").before(updated);
|
|
40
|
+
}
|
|
41
|
+
setTimeout(refreshModelStatus, 1000);
|
|
42
42
|
`;
|
package/dist/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,29 +1,30 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "vanspod",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"bin": { "vanspod": "bin/vanspod.cjs" },
|
|
5
|
-
"engines": { "node": ">=24" },
|
|
6
|
-
"os": ["win32"],
|
|
7
|
-
"files": ["bin/", "dist/**/*.js", "!dist/test/**", "!dist/sea-*.js", "media/brand-logo.png", "media/brand-icon.ico", "media/fadou-superhero.png", "media/fadou-superhero-blink.png", "scripts/tray.ps1", "scripts/uninstall.ps1", "README.md"],
|
|
8
|
-
"publishConfig": { "access": "public" },
|
|
9
|
-
"description": "本機 VansPod。學生用系統瀏覽器兌換課堂連線。",
|
|
10
|
-
"
|
|
11
|
-
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"test:
|
|
20
|
-
"test:
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
"
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "vanspod",
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"bin": { "vanspod": "bin/vanspod.cjs" },
|
|
5
|
+
"engines": { "node": ">=24" },
|
|
6
|
+
"os": ["win32"],
|
|
7
|
+
"files": ["bin/", "dist/**/*.js", "!dist/test/**", "!dist/sea-*.js", "media/brand-logo.png", "media/brand-icon.ico", "media/fadou-superhero.png", "media/fadou-superhero-blink.png", "scripts/tray.ps1", "scripts/uninstall.ps1", "README.md"],
|
|
8
|
+
"publishConfig": { "access": "public" },
|
|
9
|
+
"description": "本機 VansPod。學生用系統瀏覽器兌換課堂連線。",
|
|
10
|
+
"repository": { "type": "git", "url": "git+https://github.com/mz038197/VansPod.git" },
|
|
11
|
+
"scripts": {
|
|
12
|
+
"test": "tsc -p . && node --test dist/test/*.test.js",
|
|
13
|
+
"typecheck": "tsc -p . --noEmit",
|
|
14
|
+
"build": "tsc -p .",
|
|
15
|
+
"start": "tsc -p . && node dist/entry.js",
|
|
16
|
+
"prepack": "npm run build",
|
|
17
|
+
"postinstall": "node bin/register.cjs",
|
|
18
|
+
"dist": "node scripts/dist.mjs",
|
|
19
|
+
"test:windows-exe": "npm run dist && node scripts/test-windows-exe.mjs",
|
|
20
|
+
"test:lifecycle": "tsc -p . && node --test dist/test/launcher.test.js",
|
|
21
|
+
"test:distribution": "npm run dist && node scripts/test-distribution.mjs"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/node": "^24.5.2",
|
|
25
|
+
"typescript": "^5.9.2"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"yaml": "^2.9.1"
|
|
29
|
+
}
|
|
30
|
+
}
|