@swimmingliu/autovpn 1.6.0 → 1.6.1
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/dist/server/runtime.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
1
3
|
import { artifactLatest, artifactList } from '../artifacts/list.js';
|
|
2
4
|
import { previewArtifact } from '../artifacts/preview.js';
|
|
3
5
|
import { profilePayload, saveProfilePayload } from '../config/profile.js';
|
|
4
6
|
import { startDetachedRetry as defaultStartDetachedRetry, startDetachedRun as defaultStartDetachedRun, stopManagedJob as defaultStopManagedJob } from '../jobs/commands.js';
|
|
5
7
|
import { followLog as defaultFollowLog } from '../jobs/logs.js';
|
|
8
|
+
import { latestJobId, loadJob } from '../jobs/read.js';
|
|
9
|
+
import { resolveArtifactsRoot, resolveProfilePath } from '../runtime/paths.js';
|
|
6
10
|
const DEPLOY_SECRET_KEYS = new Set([
|
|
7
11
|
'cloudflare_api_token',
|
|
8
12
|
'cloudflare_global_key',
|
|
@@ -99,10 +103,172 @@ function runEnv(options) {
|
|
|
99
103
|
VPN_AUTOMATION_UPSTREAM_PROXY: proxyUrl
|
|
100
104
|
};
|
|
101
105
|
}
|
|
106
|
+
function parsePositiveNumber(value, fallback) {
|
|
107
|
+
const parsed = Number.parseFloat(String(value ?? ''));
|
|
108
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
109
|
+
}
|
|
110
|
+
function terminalStateTtlMs(env) {
|
|
111
|
+
return parsePositiveNumber(env.AUTOVPN_SERVER_TERMINAL_STATE_TTL_SECONDS, 600) * 1000;
|
|
112
|
+
}
|
|
113
|
+
function historyRetentionMs(env) {
|
|
114
|
+
const raw = String(env.AUTOVPN_SERVER_HISTORY_RETENTION_DAYS ?? '').trim();
|
|
115
|
+
if (raw) {
|
|
116
|
+
const parsed = Number.parseFloat(raw);
|
|
117
|
+
if (Number.isFinite(parsed) && parsed <= 0)
|
|
118
|
+
return 0;
|
|
119
|
+
if (Number.isFinite(parsed) && parsed > 0)
|
|
120
|
+
return parsed * 24 * 60 * 60 * 1000;
|
|
121
|
+
}
|
|
122
|
+
return 7 * 24 * 60 * 60 * 1000;
|
|
123
|
+
}
|
|
124
|
+
function parseTimeMs(value) {
|
|
125
|
+
const time = Date.parse(String(value ?? '').replace(/\+00:00$/, 'Z'));
|
|
126
|
+
return Number.isFinite(time) ? time : 0;
|
|
127
|
+
}
|
|
128
|
+
function jobTerminalAt(job) {
|
|
129
|
+
return parseTimeMs(job.finished_at) || parseTimeMs(job.created_at) || parseTimeMs(job.updated_at);
|
|
130
|
+
}
|
|
131
|
+
function latestJob(projectRoot, env) {
|
|
132
|
+
try {
|
|
133
|
+
return loadJob(projectRoot, latestJobId(projectRoot, env), env);
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return undefined;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function terminalRunStateFromLatestJob(job, nowMs, ttlMs) {
|
|
140
|
+
const status = String(job?.status ?? '');
|
|
141
|
+
if (status === 'running' || status === 'stopping')
|
|
142
|
+
return status;
|
|
143
|
+
if (!['success', 'failed', 'stopped'].includes(status))
|
|
144
|
+
return undefined;
|
|
145
|
+
const finishedAt = jobTerminalAt(job ?? {});
|
|
146
|
+
if (!finishedAt || nowMs - finishedAt > ttlMs)
|
|
147
|
+
return undefined;
|
|
148
|
+
return status === 'stopped' ? 'idle' : status;
|
|
149
|
+
}
|
|
150
|
+
function terminalRunStateFromArtifact(artifact, nowMs, ttlMs) {
|
|
151
|
+
const status = String(artifact?.run_status ?? '');
|
|
152
|
+
if (!['success', 'failed'].includes(status))
|
|
153
|
+
return undefined;
|
|
154
|
+
const artifactDir = String(artifact?.artifact_dir ?? '');
|
|
155
|
+
let updatedAt = 0;
|
|
156
|
+
try {
|
|
157
|
+
updatedAt = artifactDir ? fs.statSync(artifactDir).mtimeMs : 0;
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
updatedAt = 0;
|
|
161
|
+
}
|
|
162
|
+
if (!updatedAt || nowMs - updatedAt > ttlMs)
|
|
163
|
+
return undefined;
|
|
164
|
+
return status;
|
|
165
|
+
}
|
|
166
|
+
function jobsRoot(projectRoot, env) {
|
|
167
|
+
return path.join(path.dirname(resolveProfilePath(projectRoot, env)), 'jobs');
|
|
168
|
+
}
|
|
169
|
+
function safeRmDir(dirPath) {
|
|
170
|
+
try {
|
|
171
|
+
fs.rmSync(dirPath, { recursive: true, force: true });
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
// Cleanup is best-effort; serve startup and state loading must continue.
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
function pruneArtifacts(projectRoot, env, cutoffMs, activeArtifactDirs) {
|
|
178
|
+
const root = resolveArtifactsRoot(projectRoot, env);
|
|
179
|
+
if (!fs.existsSync(root))
|
|
180
|
+
return;
|
|
181
|
+
for (const name of fs.readdirSync(root)) {
|
|
182
|
+
const artifactDir = path.join(root, name);
|
|
183
|
+
let stat;
|
|
184
|
+
try {
|
|
185
|
+
stat = fs.statSync(artifactDir);
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
if (!stat.isDirectory() || activeArtifactDirs.has(path.resolve(artifactDir)))
|
|
191
|
+
continue;
|
|
192
|
+
if (stat.mtimeMs < cutoffMs) {
|
|
193
|
+
safeRmDir(artifactDir);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
function pruneJobs(projectRoot, env, cutoffMs) {
|
|
198
|
+
const root = jobsRoot(projectRoot, env);
|
|
199
|
+
const activeArtifactDirs = new Set();
|
|
200
|
+
const indexPath = path.join(root, 'index.json');
|
|
201
|
+
if (!fs.existsSync(indexPath))
|
|
202
|
+
return activeArtifactDirs;
|
|
203
|
+
let index;
|
|
204
|
+
try {
|
|
205
|
+
index = JSON.parse(fs.readFileSync(indexPath, 'utf8'));
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
return activeArtifactDirs;
|
|
209
|
+
}
|
|
210
|
+
const kept = [];
|
|
211
|
+
for (const item of (index.jobs ?? [])) {
|
|
212
|
+
const jobId = String(item.job_id ?? '');
|
|
213
|
+
let job;
|
|
214
|
+
try {
|
|
215
|
+
job = loadJob(projectRoot, jobId, env);
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
const status = String(job.status ?? '');
|
|
221
|
+
const artifactDir = String(job.artifact_dir ?? '');
|
|
222
|
+
if (artifactDir && ['running', 'stopping'].includes(status)) {
|
|
223
|
+
activeArtifactDirs.add(path.resolve(artifactDir));
|
|
224
|
+
}
|
|
225
|
+
const keep = ['running', 'stopping'].includes(status) || (jobTerminalAt(job) || parseTimeMs(job.created_at)) >= cutoffMs;
|
|
226
|
+
if (keep) {
|
|
227
|
+
kept.push({
|
|
228
|
+
job_id: job.job_id,
|
|
229
|
+
status: job.status,
|
|
230
|
+
kind: job.kind,
|
|
231
|
+
created_at: job.created_at,
|
|
232
|
+
job_file: job.job_file
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
safeRmDir(path.dirname(String(job.job_file ?? '')));
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
const latestId = kept.some((item) => item.job_id === index.latest_job_id)
|
|
240
|
+
? String(index.latest_job_id ?? '')
|
|
241
|
+
: String(kept.at(-1)?.job_id ?? '');
|
|
242
|
+
try {
|
|
243
|
+
fs.mkdirSync(path.dirname(indexPath), { recursive: true });
|
|
244
|
+
fs.writeFileSync(indexPath, `${JSON.stringify({ schema_version: 1, latest_job_id: latestId, jobs: kept }, null, 2)}\n`, 'utf8');
|
|
245
|
+
}
|
|
246
|
+
catch {
|
|
247
|
+
// Best-effort cleanup.
|
|
248
|
+
}
|
|
249
|
+
return activeArtifactDirs;
|
|
250
|
+
}
|
|
251
|
+
function createHistoryPruner(options) {
|
|
252
|
+
let lastPrunedAt = 0;
|
|
253
|
+
return () => {
|
|
254
|
+
const env = options.env ?? process.env;
|
|
255
|
+
const retention = historyRetentionMs(env);
|
|
256
|
+
if (retention <= 0)
|
|
257
|
+
return;
|
|
258
|
+
const nowMs = (options.now ?? (() => new Date()))().getTime();
|
|
259
|
+
if (lastPrunedAt && nowMs - lastPrunedAt < 24 * 60 * 60 * 1000)
|
|
260
|
+
return;
|
|
261
|
+
lastPrunedAt = nowMs;
|
|
262
|
+
const cutoffMs = nowMs - retention;
|
|
263
|
+
const activeArtifactDirs = pruneJobs(options.projectRoot, env, cutoffMs);
|
|
264
|
+
pruneArtifacts(options.projectRoot, env, cutoffMs, activeArtifactDirs);
|
|
265
|
+
};
|
|
266
|
+
}
|
|
102
267
|
export function createServerRuntime(options) {
|
|
103
268
|
let runState = 'idle';
|
|
104
269
|
let activeJobId = '';
|
|
105
270
|
let unsubscribeLogs;
|
|
271
|
+
const pruneHistory = createHistoryPruner(options);
|
|
106
272
|
const subscribers = new Set();
|
|
107
273
|
const startDetachedRun = options.startDetachedRun ?? defaultStartDetachedRun;
|
|
108
274
|
const startDetachedRetry = options.startDetachedRetry ?? defaultStartDetachedRetry;
|
|
@@ -149,9 +315,34 @@ export function createServerRuntime(options) {
|
|
|
149
315
|
}
|
|
150
316
|
});
|
|
151
317
|
}
|
|
318
|
+
function restoreRunState(artifact) {
|
|
319
|
+
if (runState !== 'idle') {
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
const env = options.env ?? process.env;
|
|
323
|
+
const job = latestJob(options.projectRoot, env);
|
|
324
|
+
if (job && ['running', 'stopping'].includes(String(job.status ?? ''))) {
|
|
325
|
+
runState = String(job.status);
|
|
326
|
+
activeJobId = String(job.job_id ?? '');
|
|
327
|
+
if (activeJobId && !unsubscribeLogs) {
|
|
328
|
+
followJob(activeJobId);
|
|
329
|
+
}
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
const nowMs = (options.now ?? (() => new Date()))().getTime();
|
|
333
|
+
const ttlMs = terminalStateTtlMs(env);
|
|
334
|
+
runState = terminalRunStateFromLatestJob(job, nowMs, ttlMs)
|
|
335
|
+
?? terminalRunStateFromArtifact(artifact, nowMs, ttlMs)
|
|
336
|
+
?? 'idle';
|
|
337
|
+
if (runState !== 'running' && runState !== 'stopping') {
|
|
338
|
+
activeJobId = '';
|
|
339
|
+
}
|
|
340
|
+
}
|
|
152
341
|
return {
|
|
153
342
|
async loadState() {
|
|
343
|
+
pruneHistory();
|
|
154
344
|
const artifact = normalizeLatestArtifact(options.projectRoot, options.env ?? process.env);
|
|
345
|
+
restoreRunState(artifact);
|
|
155
346
|
const retries = artifactList(options.projectRoot, options.env ?? process.env);
|
|
156
347
|
return {
|
|
157
348
|
profile: sanitizeProfileForServer(profilePayload(options.projectRoot, options.env)),
|
|
@@ -25,7 +25,6 @@ export function renderWebAdapterScript(options = {}) {
|
|
|
25
25
|
'.server-login-brand{display:flex;align-items:center;gap:14px;}',
|
|
26
26
|
'.server-login-logo{width:54px;height:54px;object-fit:contain;border-radius:18px;}',
|
|
27
27
|
'.server-login-title{margin:0;font-size:28px;font-weight:850;letter-spacing:0;}',
|
|
28
|
-
'.server-login-copy{margin:5px 0 0;color:var(--text-soft,#6d7794);line-height:1.5;}',
|
|
29
28
|
'.server-login-form{display:grid;gap:14px;}',
|
|
30
29
|
'.server-login-form input{width:100%;min-height:48px;border:1px solid var(--border,#dfe5f5);border-radius:14px;background:#fff;color:var(--text,#1e2746);padding:0 14px;outline:none;}',
|
|
31
30
|
'.server-login-form input:focus{border-color:rgba(91,92,226,.45);box-shadow:0 0 0 4px rgba(91,92,226,.12);}',
|
|
@@ -47,6 +46,7 @@ export function renderWebAdapterScript(options = {}) {
|
|
|
47
46
|
|
|
48
47
|
function showLoginPage(message = '', banned = false) {
|
|
49
48
|
ensureLoginStyles();
|
|
49
|
+
document.title = 'AutoNetwork';
|
|
50
50
|
let root = document.querySelector('[data-server-login]');
|
|
51
51
|
if (!root) {
|
|
52
52
|
root = document.createElement('section');
|
|
@@ -57,8 +57,7 @@ export function renderWebAdapterScript(options = {}) {
|
|
|
57
57
|
'<div class="server-login-brand">',
|
|
58
58
|
'<img class="server-login-logo" src="./assets/vpn-auto-logo-v2-minimal.svg" alt="" aria-hidden="true" />',
|
|
59
59
|
'<div>',
|
|
60
|
-
'<h1 class="server-login-title">
|
|
61
|
-
'<p class="server-login-copy">输入 serve 启动时打印的密码继续访问。</p>',
|
|
60
|
+
'<h1 class="server-login-title">AutoNetwork</h1>',
|
|
62
61
|
'</div>',
|
|
63
62
|
'</div>',
|
|
64
63
|
'<form class="server-login-form" data-server-login-form>',
|
|
@@ -149,6 +148,7 @@ export function renderWebAdapterScript(options = {}) {
|
|
|
149
148
|
}
|
|
150
149
|
|
|
151
150
|
window.vpnAutomation = {
|
|
151
|
+
loadState: async () => state(),
|
|
152
152
|
loadProfile: async () => {
|
|
153
153
|
const payload = await state();
|
|
154
154
|
return payload.profile || {};
|
package/dist/web/renderer/app.js
CHANGED
|
@@ -159,17 +159,43 @@ async function bootstrap() {
|
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
state.isDemo = false;
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
162
|
+
const loadedState = window.vpnAutomation.loadState
|
|
163
|
+
? await window.vpnAutomation.loadState()
|
|
164
|
+
: { profile: await window.vpnAutomation.loadProfile() };
|
|
165
|
+
hydrateInitialRuntimeState(loadedState);
|
|
165
166
|
touchUpdate();
|
|
166
167
|
await refreshQrCode();
|
|
167
|
-
|
|
168
|
-
|
|
168
|
+
if (!loadedState?.retryArtifacts) {
|
|
169
|
+
await hydrateRetryArtifacts();
|
|
170
|
+
}
|
|
171
|
+
if (!loadedState?.artifact) {
|
|
172
|
+
await hydrateLatestArtifact();
|
|
173
|
+
}
|
|
169
174
|
renderAll();
|
|
170
175
|
state.unsubscribe = window.vpnAutomation.onPipelineEvent(handlePipelineEvent);
|
|
171
176
|
}
|
|
172
177
|
|
|
178
|
+
function hydrateInitialRuntimeState(loadedState = {}) {
|
|
179
|
+
const loadedProfile = loadedState.profile ?? {};
|
|
180
|
+
state.profile = loadedProfile;
|
|
181
|
+
state.savedProfile = structuredClone(loadedProfile);
|
|
182
|
+
const nextRunState = String(loadedState.runState ?? '');
|
|
183
|
+
if (['idle', 'running', 'stopping', 'failed', 'success'].includes(nextRunState)) {
|
|
184
|
+
state.runState = nextRunState;
|
|
185
|
+
state.runResult = nextRunState === 'idle' ? state.runResult : nextRunState;
|
|
186
|
+
state.runStartedAt = nextRunState === 'running' ? Date.now() : null;
|
|
187
|
+
}
|
|
188
|
+
if (loadedState.artifact) {
|
|
189
|
+
hydrateArtifactState(loadedState.artifact);
|
|
190
|
+
}
|
|
191
|
+
if (Array.isArray(loadedState.retryArtifacts)) {
|
|
192
|
+
hydrateRetryArtifactState(loadedState.retryArtifacts);
|
|
193
|
+
}
|
|
194
|
+
if (loadedState.deployment) {
|
|
195
|
+
state.deployment = loadedState.deployment;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
173
199
|
function bindActions() {
|
|
174
200
|
document.addEventListener('click', handleDocumentClick);
|
|
175
201
|
document.addEventListener('input', handleDocumentInput);
|
|
@@ -264,6 +290,12 @@ function runTone() {
|
|
|
264
290
|
if (state.runState === 'stopping') {
|
|
265
291
|
return 'warning';
|
|
266
292
|
}
|
|
293
|
+
if (state.runState === 'failed') {
|
|
294
|
+
return 'danger';
|
|
295
|
+
}
|
|
296
|
+
if (state.runState === 'success') {
|
|
297
|
+
return 'success';
|
|
298
|
+
}
|
|
267
299
|
return 'neutral';
|
|
268
300
|
}
|
|
269
301
|
|
|
@@ -882,7 +914,7 @@ async function exportLogs() {
|
|
|
882
914
|
}
|
|
883
915
|
|
|
884
916
|
async function runPipeline() {
|
|
885
|
-
if (state.runState
|
|
917
|
+
if (resolveRunControlState(state.runState).isBusy || !state.profile) {
|
|
886
918
|
return;
|
|
887
919
|
}
|
|
888
920
|
|
|
@@ -939,7 +971,7 @@ async function runPipeline() {
|
|
|
939
971
|
}
|
|
940
972
|
|
|
941
973
|
async function retryStage() {
|
|
942
|
-
if (state.runState
|
|
974
|
+
if (resolveRunControlState(state.runState).isBusy || !state.profile || !state.selectedRetryArtifactDir || !state.selectedRetryStage) {
|
|
943
975
|
return;
|
|
944
976
|
}
|
|
945
977
|
|
|
@@ -1065,10 +1097,10 @@ async function stopPipeline() {
|
|
|
1065
1097
|
|
|
1066
1098
|
function finishRun(result = {}) {
|
|
1067
1099
|
const messages = getMessages(state.language);
|
|
1068
|
-
state.runState = 'idle';
|
|
1069
1100
|
state.runStartedAt = null;
|
|
1070
1101
|
|
|
1071
1102
|
if (result.stopped) {
|
|
1103
|
+
state.runState = 'idle';
|
|
1072
1104
|
state.runResult = 'stopped';
|
|
1073
1105
|
touchUpdate();
|
|
1074
1106
|
renderAll();
|
|
@@ -1078,6 +1110,7 @@ function finishRun(result = {}) {
|
|
|
1078
1110
|
}
|
|
1079
1111
|
|
|
1080
1112
|
if (result.ok) {
|
|
1113
|
+
state.runState = 'success';
|
|
1081
1114
|
state.runResult = 'success';
|
|
1082
1115
|
touchUpdate();
|
|
1083
1116
|
renderAll();
|
|
@@ -1086,6 +1119,7 @@ function finishRun(result = {}) {
|
|
|
1086
1119
|
return;
|
|
1087
1120
|
}
|
|
1088
1121
|
|
|
1122
|
+
state.runState = 'failed';
|
|
1089
1123
|
state.runResult = 'failed';
|
|
1090
1124
|
touchUpdate();
|
|
1091
1125
|
renderAll();
|
|
@@ -1101,7 +1135,7 @@ function handlePipelineEvent(event) {
|
|
|
1101
1135
|
if (event.type === 'server_state') {
|
|
1102
1136
|
const nextRunState = String(event.run_state ?? '');
|
|
1103
1137
|
if (['idle', 'running', 'stopping', 'failed', 'success'].includes(nextRunState)) {
|
|
1104
|
-
state.runState = nextRunState
|
|
1138
|
+
state.runState = nextRunState;
|
|
1105
1139
|
if (['idle', 'failed', 'success'].includes(nextRunState)) {
|
|
1106
1140
|
state.runStartedAt = null;
|
|
1107
1141
|
}
|
|
@@ -1321,6 +1355,27 @@ async function hydrateArtifactPreview() {
|
|
|
1321
1355
|
}
|
|
1322
1356
|
}
|
|
1323
1357
|
|
|
1358
|
+
function hydrateArtifactState(result) {
|
|
1359
|
+
if (!result?.artifact_dir) {
|
|
1360
|
+
return;
|
|
1361
|
+
}
|
|
1362
|
+
state.artifactDir = result.artifact_dir;
|
|
1363
|
+
state.counts = normalizeCounts(result.counts ?? {});
|
|
1364
|
+
state.sourceCounts = normalizeSourceCounts(result.source_counts ?? {});
|
|
1365
|
+
state.outputFiles = result.outputFiles ?? [];
|
|
1366
|
+
state.nodeRows = result.nodeRows ?? [];
|
|
1367
|
+
state.retryContext = result.retry_context ?? {};
|
|
1368
|
+
state.deployment = result.deployment ?? state.deployment ?? {};
|
|
1369
|
+
if (result.run_status === 'success') {
|
|
1370
|
+
state.runResult = 'success';
|
|
1371
|
+
} else if (result.run_status === 'failed') {
|
|
1372
|
+
state.runResult = 'failed';
|
|
1373
|
+
}
|
|
1374
|
+
if (result.stage_status) {
|
|
1375
|
+
state.stageStatus = result.stage_status;
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1324
1379
|
async function hydrateLatestArtifact() {
|
|
1325
1380
|
if (!window.vpnAutomation?.latestArtifact) {
|
|
1326
1381
|
return;
|
|
@@ -1331,23 +1386,26 @@ async function hydrateLatestArtifact() {
|
|
|
1331
1386
|
if (!result?.ok || !result.artifact_dir) {
|
|
1332
1387
|
return;
|
|
1333
1388
|
}
|
|
1334
|
-
|
|
1335
|
-
state.counts = normalizeCounts(result.counts ?? {});
|
|
1336
|
-
state.sourceCounts = normalizeSourceCounts(result.source_counts ?? {});
|
|
1337
|
-
state.outputFiles = result.outputFiles ?? [];
|
|
1338
|
-
state.nodeRows = result.nodeRows ?? [];
|
|
1339
|
-
state.retryContext = result.retry_context ?? {};
|
|
1340
|
-
state.deployment = result.deployment ?? {};
|
|
1341
|
-
state.runResult = result.run_status === 'success' ? 'success' : state.runResult;
|
|
1342
|
-
if (result.stage_status) {
|
|
1343
|
-
state.stageStatus = result.stage_status;
|
|
1344
|
-
}
|
|
1389
|
+
hydrateArtifactState(result);
|
|
1345
1390
|
touchUpdate();
|
|
1346
1391
|
} catch (error) {
|
|
1347
1392
|
appendLog(formatMessage(getMessages(state.language).openFailed, { error: error.message }));
|
|
1348
1393
|
}
|
|
1349
1394
|
}
|
|
1350
1395
|
|
|
1396
|
+
function hydrateRetryArtifactState(items = []) {
|
|
1397
|
+
state.retryArtifacts = items;
|
|
1398
|
+
if (!state.selectedRetryArtifactDir) {
|
|
1399
|
+
state.selectedRetryArtifactDir = state.retryArtifacts[0]?.artifact_dir ?? '';
|
|
1400
|
+
}
|
|
1401
|
+
const selectedArtifact = state.retryArtifacts.find((item) => item.artifact_dir === state.selectedRetryArtifactDir) ?? state.retryArtifacts[0];
|
|
1402
|
+
const nextStage = selectedArtifact?.retryable_stages?.includes(state.selectedRetryStage)
|
|
1403
|
+
? state.selectedRetryStage
|
|
1404
|
+
: resolveDefaultRetryStage(selectedArtifact);
|
|
1405
|
+
state.selectedRetryArtifactDir = selectedArtifact?.artifact_dir ?? '';
|
|
1406
|
+
state.selectedRetryStage = nextStage;
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1351
1409
|
async function hydrateRetryArtifacts() {
|
|
1352
1410
|
if (!window.vpnAutomation?.artifactList) {
|
|
1353
1411
|
return;
|
|
@@ -1358,16 +1416,7 @@ async function hydrateRetryArtifacts() {
|
|
|
1358
1416
|
if (!result?.ok) {
|
|
1359
1417
|
return;
|
|
1360
1418
|
}
|
|
1361
|
-
|
|
1362
|
-
if (!state.selectedRetryArtifactDir) {
|
|
1363
|
-
state.selectedRetryArtifactDir = state.retryArtifacts[0]?.artifact_dir ?? '';
|
|
1364
|
-
}
|
|
1365
|
-
const selectedArtifact = state.retryArtifacts.find((item) => item.artifact_dir === state.selectedRetryArtifactDir) ?? state.retryArtifacts[0];
|
|
1366
|
-
const nextStage = selectedArtifact?.retryable_stages?.includes(state.selectedRetryStage)
|
|
1367
|
-
? state.selectedRetryStage
|
|
1368
|
-
: resolveDefaultRetryStage(selectedArtifact);
|
|
1369
|
-
state.selectedRetryArtifactDir = selectedArtifact?.artifact_dir ?? '';
|
|
1370
|
-
state.selectedRetryStage = nextStage;
|
|
1419
|
+
hydrateRetryArtifactState(result.items ?? []);
|
|
1371
1420
|
touchUpdate();
|
|
1372
1421
|
} catch (error) {
|
|
1373
1422
|
appendLog(formatMessage(getMessages(state.language).openFailed, { error: error.message }));
|
|
@@ -5,7 +5,7 @@ const ZH_MESSAGES = {
|
|
|
5
5
|
locale: 'zh-CN',
|
|
6
6
|
appTitle: 'AutoVPN',
|
|
7
7
|
sidebarTitle: 'AutoVPN',
|
|
8
|
-
sidebarVersion: 'v.1.6.
|
|
8
|
+
sidebarVersion: 'v.1.6.1',
|
|
9
9
|
brandSubtitle: '概览、运行、结果、订阅、日志、设置统一管理',
|
|
10
10
|
languageLabel: '',
|
|
11
11
|
saveButton: '保存配置',
|
|
@@ -58,7 +58,9 @@ const ZH_MESSAGES = {
|
|
|
58
58
|
runStateLabels: {
|
|
59
59
|
idle: '待运行',
|
|
60
60
|
running: '运行中',
|
|
61
|
-
stopping: '停止中'
|
|
61
|
+
stopping: '停止中',
|
|
62
|
+
success: '已完成',
|
|
63
|
+
failed: '失败'
|
|
62
64
|
},
|
|
63
65
|
runResultLabels: {
|
|
64
66
|
idle: '未开始',
|
|
@@ -173,12 +173,19 @@ export function buildViewModel(state, messages, language) {
|
|
|
173
173
|
currentStatusLabel: resolveCurrentStatusLabel(state, messages),
|
|
174
174
|
currentTaskLabel: resolveCurrentTaskLabel(state, messages),
|
|
175
175
|
runStateLabel: messages.runStateLabels[state.runState] ?? messages.runStateLabels.idle,
|
|
176
|
-
runStateTone: state.runState
|
|
176
|
+
runStateTone: runStateTone(state.runState),
|
|
177
177
|
settingsDrawer: state.settingsDrawer ?? null,
|
|
178
178
|
modalTransform: state.modalTransform ?? ''
|
|
179
179
|
};
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
+
function runStateTone(runState) {
|
|
183
|
+
if (runState === 'running' || runState === 'success') return 'success';
|
|
184
|
+
if (runState === 'stopping') return 'warning';
|
|
185
|
+
if (runState === 'failed') return 'danger';
|
|
186
|
+
return 'neutral';
|
|
187
|
+
}
|
|
188
|
+
|
|
182
189
|
export function buildSidebarNav(messages, activePage) {
|
|
183
190
|
return PAGE_ORDER.map((page) => `
|
|
184
191
|
<button
|