@huaqiu/dsh-tool-plugin-update 0.6.1 → 0.6.2
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/lib/client.js +516 -13
- package/lib/client.js.map +1 -1
- package/lib/index.mjs +86 -18
- package/package.json +3 -3
- package/src/client/api.ts +156 -1
- package/src/client/copy.ts +65 -0
- package/src/client/index.ts +63 -0
- package/src/client/update-view.ts +513 -8
- package/src/routes.ts +110 -12
package/lib/index.mjs
CHANGED
|
@@ -3,6 +3,22 @@ import { getLogger } from "@huaqiu/dsh-plugin-log";
|
|
|
3
3
|
const UPDATE_ROUTE_PREFIX = "/api/v1/huaqiu/plugin-update";
|
|
4
4
|
/** End-to-end budget for one forwarded request, in milliseconds. */
|
|
5
5
|
const DEFAULT_FORWARD_TIMEOUT_MS = 3e4;
|
|
6
|
+
/**
|
|
7
|
+
* Budget for `POST /runtime-update/request` — deliberately far larger than
|
|
8
|
+
* {@link DEFAULT_FORWARD_TIMEOUT_MS}.
|
|
9
|
+
*
|
|
10
|
+
* This route is not a quick RPC. Before it answers, hq-edge re-reads the
|
|
11
|
+
* update manifest, downloads the entire runtime bundle (hundreds of MB),
|
|
12
|
+
* verifies its SHA-256, extracts it into the shared staging root and validates
|
|
13
|
+
* the layout + version stamp. Measured end-to-end against a local update
|
|
14
|
+
* server: 56 s (16:49:49 → 16:50:45).
|
|
15
|
+
*
|
|
16
|
+
* Under the generic 30 s budget the forwarder aborts mid-flight and answers
|
|
17
|
+
* `504 hq-edge request timed out after 30000ms`, so the panel reports a failure
|
|
18
|
+
* for an update that is still running perfectly well — and which then goes on
|
|
19
|
+
* to complete, swap the runtime and restart HQ Edge underneath the error toast.
|
|
20
|
+
*/
|
|
21
|
+
const RUNTIME_REQUEST_TIMEOUT_MS = 6e5;
|
|
6
22
|
function sendJson(res, status, body) {
|
|
7
23
|
const payload = JSON.stringify(body);
|
|
8
24
|
res.writeHead(status, {
|
|
@@ -37,34 +53,85 @@ function readBody(req) {
|
|
|
37
53
|
req.on("error", reject);
|
|
38
54
|
});
|
|
39
55
|
}
|
|
40
|
-
/**
|
|
41
|
-
|
|
42
|
-
|
|
56
|
+
/**
|
|
57
|
+
* Map a sub-path + method to { method, path } on the hq-edge API.
|
|
58
|
+
*
|
|
59
|
+
* `timeoutMs` overrides {@link DEFAULT_FORWARD_TIMEOUT_MS} for routes whose
|
|
60
|
+
* upstream work legitimately outlasts it (only `/runtime-request` today).
|
|
61
|
+
*
|
|
62
|
+
* The method is part of the lookup because one sub-path can carry both a read
|
|
63
|
+
* and a write: `/runtime-source` is GET for the current value and POST to
|
|
64
|
+
* change it. A method the route does not declare is `null`, which the
|
|
65
|
+
* forwarder answers 405 rather than forwarding a GET to a POST-only upstream
|
|
66
|
+
* and surfacing hq-edge's 404 as a plugin bug.
|
|
67
|
+
*/
|
|
68
|
+
function routeFor(sub, method) {
|
|
69
|
+
if (sub === "/runtime-source") {
|
|
70
|
+
if (method === "GET") return {
|
|
71
|
+
method: "GET",
|
|
72
|
+
path: "/api/v1/runtime-update/source"
|
|
73
|
+
};
|
|
74
|
+
if (method === "POST") return {
|
|
75
|
+
method: "POST",
|
|
76
|
+
path: "/api/v1/runtime-update/source"
|
|
77
|
+
};
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
if (sub === "/plugin-source") {
|
|
81
|
+
if (method === "GET") return {
|
|
82
|
+
method: "GET",
|
|
83
|
+
path: "/api/v1/plugin-updates/source"
|
|
84
|
+
};
|
|
85
|
+
if (method === "POST") return {
|
|
86
|
+
method: "POST",
|
|
87
|
+
path: "/api/v1/plugin-updates/source"
|
|
88
|
+
};
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
if (sub === "/report" || sub === "") return method === "GET" ? {
|
|
43
92
|
method: "GET",
|
|
44
93
|
path: "/api/v1/plugin-updates"
|
|
45
|
-
};
|
|
46
|
-
if (sub === "/runtime") return {
|
|
94
|
+
} : null;
|
|
95
|
+
if (sub === "/runtime") return method === "GET" ? {
|
|
47
96
|
method: "GET",
|
|
48
97
|
path: "/api/v1/runtime-update"
|
|
49
|
-
};
|
|
50
|
-
if (sub === "/
|
|
98
|
+
} : null;
|
|
99
|
+
if (sub === "/runtime-request") return method === "POST" ? {
|
|
100
|
+
method: "POST",
|
|
101
|
+
path: "/api/v1/runtime-update/request",
|
|
102
|
+
timeoutMs: RUNTIME_REQUEST_TIMEOUT_MS
|
|
103
|
+
} : null;
|
|
104
|
+
if (sub === "/check") return method === "POST" ? {
|
|
51
105
|
method: "POST",
|
|
52
106
|
path: "/api/v1/plugin-updates/check"
|
|
53
|
-
};
|
|
54
|
-
if (sub === "/install") return {
|
|
107
|
+
} : null;
|
|
108
|
+
if (sub === "/install") return method === "POST" ? {
|
|
55
109
|
method: "POST",
|
|
56
110
|
path: "/api/v1/plugin-updates/install"
|
|
57
|
-
};
|
|
58
|
-
if (sub === "/ignore") return {
|
|
111
|
+
} : null;
|
|
112
|
+
if (sub === "/ignore") return method === "POST" ? {
|
|
59
113
|
method: "POST",
|
|
60
114
|
path: "/api/v1/plugin-updates/ignore"
|
|
61
|
-
};
|
|
62
|
-
if (sub === "/unignore") return {
|
|
115
|
+
} : null;
|
|
116
|
+
if (sub === "/unignore") return method === "POST" ? {
|
|
63
117
|
method: "POST",
|
|
64
118
|
path: "/api/v1/plugin-updates/unignore"
|
|
65
|
-
};
|
|
119
|
+
} : null;
|
|
66
120
|
return null;
|
|
67
121
|
}
|
|
122
|
+
/** Every sub-path this forwarder serves — used to tell a 404 from a 405. */
|
|
123
|
+
const ROUTE_SUB_PATHS = /* @__PURE__ */ new Set([
|
|
124
|
+
"",
|
|
125
|
+
"/report",
|
|
126
|
+
"/check",
|
|
127
|
+
"/install",
|
|
128
|
+
"/ignore",
|
|
129
|
+
"/unignore",
|
|
130
|
+
"/runtime",
|
|
131
|
+
"/runtime-request",
|
|
132
|
+
"/runtime-source",
|
|
133
|
+
"/plugin-source"
|
|
134
|
+
]);
|
|
68
135
|
/** Create the prefix-route handler forwarding to hq-edge. */
|
|
69
136
|
function createUpdateForwarder(baseUrlResolver, timeoutMs = DEFAULT_FORWARD_TIMEOUT_MS) {
|
|
70
137
|
return async (req, res) => {
|
|
@@ -73,9 +140,9 @@ function createUpdateForwarder(baseUrlResolver, timeoutMs = DEFAULT_FORWARD_TIME
|
|
|
73
140
|
sendJson(res, 404, { error: "not found" });
|
|
74
141
|
return;
|
|
75
142
|
}
|
|
76
|
-
const target = routeFor(sub);
|
|
143
|
+
const target = routeFor(sub, req.method === "HEAD" ? "GET" : req.method ?? "");
|
|
77
144
|
if (target === null) {
|
|
78
|
-
sendJson(res, 404, { error: "not found" });
|
|
145
|
+
sendJson(res, sub === "" || ROUTE_SUB_PATHS.has(sub) ? 405 : 404, { error: sub === "" || ROUTE_SUB_PATHS.has(sub) ? `method ${req.method} not allowed on ${sub || "/"}` : "not found" });
|
|
79
146
|
return;
|
|
80
147
|
}
|
|
81
148
|
if (req.method !== "GET" && req.method !== "POST" && req.method !== "HEAD") {
|
|
@@ -94,8 +161,9 @@ function createUpdateForwarder(baseUrlResolver, timeoutMs = DEFAULT_FORWARD_TIME
|
|
|
94
161
|
sendJson(res, 400, { error: err instanceof Error ? err.message : String(err) });
|
|
95
162
|
return;
|
|
96
163
|
}
|
|
164
|
+
const budget = target.timeoutMs ?? timeoutMs;
|
|
97
165
|
const controller = new AbortController();
|
|
98
|
-
const timer = setTimeout(() => controller.abort(),
|
|
166
|
+
const timer = setTimeout(() => controller.abort(), budget);
|
|
99
167
|
try {
|
|
100
168
|
const upstream = await fetch(`${base}${target.path}`, {
|
|
101
169
|
method: target.method,
|
|
@@ -117,7 +185,7 @@ function createUpdateForwarder(baseUrlResolver, timeoutMs = DEFAULT_FORWARD_TIME
|
|
|
117
185
|
} catch (err) {
|
|
118
186
|
const message = err instanceof Error ? err.message : String(err);
|
|
119
187
|
if (err instanceof Error && err.name === "AbortError") {
|
|
120
|
-
sendJson(res, 504, { error: `hq-edge request timed out after ${
|
|
188
|
+
sendJson(res, 504, { error: `hq-edge request timed out after ${budget}ms` });
|
|
121
189
|
return;
|
|
122
190
|
}
|
|
123
191
|
sendJson(res, 502, { error: `hq-edge request failed: ${message}` });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@huaqiu/dsh-tool-plugin-update",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./lib/index.mjs",
|
|
6
6
|
"types": "./lib/index.d.mts",
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
"@deepseek-ai/dsh-host-webserver": "0.1.0-rc.8"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@huaqiu/dsh-plugin-log": "0.6.
|
|
35
|
-
"@huaqiu/dsh-sidebar-entry": "0.6.
|
|
34
|
+
"@huaqiu/dsh-plugin-log": "0.6.2",
|
|
35
|
+
"@huaqiu/dsh-sidebar-entry": "0.6.2"
|
|
36
36
|
},
|
|
37
37
|
"files": [
|
|
38
38
|
"lib",
|
package/src/client/api.ts
CHANGED
|
@@ -24,13 +24,83 @@ export interface PluginUpdateItem {
|
|
|
24
24
|
notes?: string[]
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
/**
|
|
28
|
+
* One downloadable HQ Edge runtime distribution, as hq-edge reports it
|
|
29
|
+
* (`RuntimeUpdateEntry` in `apps/server/src/dsh/PluginUpdateService.ts`).
|
|
30
|
+
*
|
|
31
|
+
* Note the shape: `runtime.latest` is this ENTRY OBJECT, not a bare version
|
|
32
|
+
* string. Reading it as a string is what once rendered "[object Object]".
|
|
33
|
+
*/
|
|
34
|
+
export interface RuntimeUpdateEntry {
|
|
35
|
+
/** Clean SemVer of the available distribution. */
|
|
36
|
+
version: string
|
|
37
|
+
/** SHA-256 of the release archive, lowercase hex (64 chars). */
|
|
38
|
+
sha256: string
|
|
39
|
+
/** Platform the archive targets: win | darwin | linux | all. */
|
|
40
|
+
platform: string
|
|
41
|
+
/** Optional CPU architecture: x64 | arm64 (absent = any). */
|
|
42
|
+
arch?: string
|
|
43
|
+
/** Exact absolute HTTPS URL of the release archive. */
|
|
44
|
+
url: string
|
|
45
|
+
}
|
|
46
|
+
|
|
27
47
|
export interface RuntimeUpdateInfo {
|
|
28
48
|
configured: boolean
|
|
29
49
|
currentVersion?: string | null
|
|
30
|
-
latest
|
|
50
|
+
/** The latest compatible distribution, or null when the manifest had none. */
|
|
51
|
+
latest?: RuntimeUpdateEntry | null
|
|
31
52
|
updateAvailable: boolean
|
|
32
53
|
checkedAt?: string | null
|
|
33
54
|
lastError?: string | null
|
|
55
|
+
/**
|
|
56
|
+
* Where the runtime-update manifest is read from. Optional: an older hq-edge
|
|
57
|
+
* does not send it, and the panel must degrade to a read-only source row.
|
|
58
|
+
*/
|
|
59
|
+
source?: RuntimeUpdateSource
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The runtime-update manifest source, editable from this panel.
|
|
64
|
+
*
|
|
65
|
+
* `origin` says which URL is in effect: `override` is a URL the user set here
|
|
66
|
+
* (persisted by hq-edge per host), `config` is the packaged/env default, and
|
|
67
|
+
* `unset` means runtime updates are not configured at all.
|
|
68
|
+
*/
|
|
69
|
+
export interface RuntimeUpdateSource {
|
|
70
|
+
/** The manifest URL currently in effect; empty when none is known. */
|
|
71
|
+
url: string
|
|
72
|
+
origin: 'override' | 'config' | 'unset'
|
|
73
|
+
/** The URL configuration supplies, independent of any user override. */
|
|
74
|
+
configuredUrl: string
|
|
75
|
+
/** True when a user override is shadowing the configured URL. */
|
|
76
|
+
override: boolean
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* The plugin release-manifest source (the gitee
|
|
81
|
+
* `plugins-release-manifest.json` by default) — same shape and same edit flow
|
|
82
|
+
* as {@link RuntimeUpdateSource}, because hq-edge serves both from one shared
|
|
83
|
+
* type.
|
|
84
|
+
*/
|
|
85
|
+
export type PluginUpdateSource = RuntimeUpdateSource
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* The latest runtime version to display, or null when there is none.
|
|
89
|
+
*
|
|
90
|
+
* hq-edge sends the full release entry; an older or future server could send
|
|
91
|
+
* a bare string. Both are accepted here so the facts row can never again fall
|
|
92
|
+
* back to `String(object)` — i.e. "[object Object]" — in the DOM.
|
|
93
|
+
*/
|
|
94
|
+
export function runtimeLatestVersion(
|
|
95
|
+
runtime: RuntimeUpdateInfo | null | undefined,
|
|
96
|
+
): string | null {
|
|
97
|
+
const latest: unknown = runtime?.latest
|
|
98
|
+
if (typeof latest === 'string') return latest.length > 0 ? latest : null
|
|
99
|
+
if (latest !== null && typeof latest === 'object' && 'version' in latest) {
|
|
100
|
+
const version = (latest as { version?: unknown }).version
|
|
101
|
+
if (typeof version === 'string' && version.length > 0) return version
|
|
102
|
+
}
|
|
103
|
+
return null
|
|
34
104
|
}
|
|
35
105
|
|
|
36
106
|
export interface PluginUpdateReport {
|
|
@@ -43,6 +113,11 @@ export interface PluginUpdateReport {
|
|
|
43
113
|
fetchedAt?: string
|
|
44
114
|
lastError?: string | null
|
|
45
115
|
runtime: RuntimeUpdateInfo
|
|
116
|
+
/**
|
|
117
|
+
* Where the plugin release manifest is read from. Optional: an older
|
|
118
|
+
* hq-edge does not send it, and the panel must degrade to no source row.
|
|
119
|
+
*/
|
|
120
|
+
pluginSource?: PluginUpdateSource
|
|
46
121
|
}
|
|
47
122
|
|
|
48
123
|
async function request<T>(path: string, init?: RequestInit): Promise<T> {
|
|
@@ -92,7 +167,87 @@ export function ignoreOffer(setVersion: string): Promise<unknown> {
|
|
|
92
167
|
return request<unknown>('/ignore', { method: 'POST', body: JSON.stringify({ setVersion }) })
|
|
93
168
|
}
|
|
94
169
|
|
|
170
|
+
/**
|
|
171
|
+
* The outcome of a user-confirmed HQ Edge runtime update.
|
|
172
|
+
*
|
|
173
|
+
* hq-edge answers `202 { requestId, targetVersion, state: 'accepted' }` and
|
|
174
|
+
* then shuts itself down for the EDA host to swap the runtime, so a success
|
|
175
|
+
* here means "accepted", not "installed". Failures arrive as 409 (already
|
|
176
|
+
* updating → `busy`), 422 (preflight/manifest/download refused) or 503.
|
|
177
|
+
*/
|
|
178
|
+
export interface RuntimeUpdateRequestResult {
|
|
179
|
+
requestId?: string
|
|
180
|
+
targetVersion?: string
|
|
181
|
+
state?: string
|
|
182
|
+
ok?: boolean
|
|
183
|
+
error?: string
|
|
184
|
+
busy?: boolean
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** POST the user-confirmed HQ Edge runtime update for one target version. */
|
|
188
|
+
export function requestRuntimeUpdate(
|
|
189
|
+
targetVersion: string,
|
|
190
|
+
): Promise<RuntimeUpdateRequestResult> {
|
|
191
|
+
return request<RuntimeUpdateRequestResult>('/runtime-request', {
|
|
192
|
+
method: 'POST',
|
|
193
|
+
body: JSON.stringify({ targetVersion }),
|
|
194
|
+
})
|
|
195
|
+
}
|
|
196
|
+
|
|
95
197
|
/** POST unignore for the set version (reverses an ignore). */
|
|
96
198
|
export function unignoreOffer(setVersion: string): Promise<unknown> {
|
|
97
199
|
return request<unknown>('/unignore', { method: 'POST', body: JSON.stringify({ setVersion }) })
|
|
98
200
|
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* GET the runtime-update manifest source: the URL in effect, its origin
|
|
204
|
+
* (`override` | `config` | `unset`) and the configured URL behind it.
|
|
205
|
+
*
|
|
206
|
+
* Read-only upstream, so this is open like `/report` — only the mutation
|
|
207
|
+
* below is loopback-guarded (and reaches it same-origin anyway).
|
|
208
|
+
*/
|
|
209
|
+
export function getRuntimeUpdateSource(): Promise<RuntimeUpdateSource> {
|
|
210
|
+
return request<RuntimeUpdateSource>('/runtime-source')
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* POST a new runtime-update manifest URL, or `""` to clear a user override
|
|
215
|
+
* and fall back to the configured URL.
|
|
216
|
+
*
|
|
217
|
+
* hq-edge validates the URL itself (`https` required except for loopback, no
|
|
218
|
+
* embedded credentials) and answers 422 with a message meant for a person —
|
|
219
|
+
* this client surfaces it verbatim rather than pre-validating, so the rule
|
|
220
|
+
* lives in exactly one place. A successful answer is the NEW source; the
|
|
221
|
+
* caller must then re-run discovery, because the cached report still names
|
|
222
|
+
* the old manifest's offer.
|
|
223
|
+
*/
|
|
224
|
+
export function setRuntimeUpdateSource(url: string): Promise<RuntimeUpdateSource> {
|
|
225
|
+
return request<RuntimeUpdateSource>('/runtime-source', {
|
|
226
|
+
method: 'POST',
|
|
227
|
+
body: JSON.stringify({ url }),
|
|
228
|
+
})
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* GET the plugin release-manifest source: the URL discovery reads, its origin
|
|
233
|
+
* (`override` | `config` | `unset`) and the configured URL behind it. Same
|
|
234
|
+
* open/read-only contract as the runtime source.
|
|
235
|
+
*/
|
|
236
|
+
export function getPluginUpdateSource(): Promise<PluginUpdateSource> {
|
|
237
|
+
return request<PluginUpdateSource>('/plugin-source')
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* POST a new plugin release-manifest URL, or `""` to clear a user override.
|
|
242
|
+
*
|
|
243
|
+
* hq-edge persists the override per host AND re-runs discovery in the
|
|
244
|
+
* background, so the next report (and the sidebar badge) already reflect the
|
|
245
|
+
* new manifest. This client still re-checks explicitly after a save — the
|
|
246
|
+
* background pass is a safety net for other consumers, not a substitute.
|
|
247
|
+
*/
|
|
248
|
+
export function setPluginUpdateSource(url: string): Promise<PluginUpdateSource> {
|
|
249
|
+
return request<PluginUpdateSource>('/plugin-source', {
|
|
250
|
+
method: 'POST',
|
|
251
|
+
body: JSON.stringify({ url }),
|
|
252
|
+
})
|
|
253
|
+
}
|
package/src/client/copy.ts
CHANGED
|
@@ -56,10 +56,42 @@ const zh = {
|
|
|
56
56
|
unknown: '版本未知',
|
|
57
57
|
notConfigured: '未配置更新源',
|
|
58
58
|
note: '运行时更新由 EDA 安装器处理',
|
|
59
|
+
update: '安装运行时更新',
|
|
60
|
+
updateConfirm: (version: string) =>
|
|
61
|
+
`安装 HQ Edge 运行时 ${version}?HQ Edge 会先关闭并完成替换,然后重新启动。`,
|
|
62
|
+
updating: '正在下载并校验运行时包,可能需要一分钟…',
|
|
63
|
+
updateAccepted: '运行时更新已受理,HQ Edge 正在重启…',
|
|
64
|
+
// ── Update-source editor ──
|
|
65
|
+
sourceHint: '运行时更新清单的下载地址,保存后立即生效',
|
|
66
|
+
sourceEdit: '修改',
|
|
67
|
+
sourceSave: '保存',
|
|
68
|
+
sourceReset: '恢复默认',
|
|
69
|
+
saved: '更新源已保存,正在重新检查更新…',
|
|
70
|
+
saveFailed: '保存更新源失败',
|
|
71
|
+
sourceInputLabel: '运行时更新清单地址',
|
|
72
|
+
sourcePlaceholder: 'https://gitee.com/huaqiu-electronics/dsh-pcb-eda/raw/main/edge-headless-release-manifest.json',
|
|
59
73
|
},
|
|
74
|
+
// ── Plugin release-manifest source editor ──
|
|
75
|
+
pluginSource: {
|
|
76
|
+
title: '插件更新源',
|
|
77
|
+
hint: '插件更新清单的下载地址,保存后立即生效',
|
|
78
|
+
edit: '修改',
|
|
79
|
+
save: '保存',
|
|
80
|
+
reset: '恢复默认',
|
|
81
|
+
saved: '更新源已保存,正在重新检查更新…',
|
|
82
|
+
saveFailed: '保存更新源失败',
|
|
83
|
+
inputLabel: '插件更新清单地址',
|
|
84
|
+
placeholder: 'https://gitee.com/huaqiu-electronics/dsh-pcb-eda/raw/main/plugins-release-manifest.json',
|
|
85
|
+
},
|
|
86
|
+
// Origin tags, shared by both source editors (hq-edge serves one type).
|
|
87
|
+
originOverride: '自定义',
|
|
88
|
+
originConfig: '默认配置',
|
|
89
|
+
originUnset: '未配置',
|
|
60
90
|
lastError: '上次检查失败',
|
|
61
91
|
installError: '安装失败',
|
|
62
92
|
installSuccess: '更新已安装,正在重启 DSH…',
|
|
93
|
+
confirmInstall: '确认安装',
|
|
94
|
+
cancel: '取消',
|
|
63
95
|
}
|
|
64
96
|
|
|
65
97
|
export type UpdateCopy = typeof zh
|
|
@@ -111,10 +143,43 @@ const en: UpdateCopy = {
|
|
|
111
143
|
unknown: 'Version unknown',
|
|
112
144
|
notConfigured: 'No update source configured',
|
|
113
145
|
note: 'Runtime updates are handled by the EDA installer',
|
|
146
|
+
update: 'Install runtime update',
|
|
147
|
+
updateConfirm: (version: string) =>
|
|
148
|
+
`Install HQ Edge runtime ${version}? HQ Edge shuts down to swap the runtime, then restarts.`,
|
|
149
|
+
updating: 'Downloading and verifying the runtime bundle — this can take a minute…',
|
|
150
|
+
updateAccepted: 'Runtime update accepted — HQ Edge is restarting…',
|
|
151
|
+
// ── Update-source editor ──
|
|
152
|
+
sourceHint: 'Where the runtime update manifest is fetched from; saved changes take effect immediately',
|
|
153
|
+
sourceEdit: 'Edit',
|
|
154
|
+
sourceSave: 'Save',
|
|
155
|
+
sourceReset: 'Reset to default',
|
|
156
|
+
saved: 'Update source saved — re-checking for updates…',
|
|
157
|
+
saveFailed: 'Failed to save the update source',
|
|
158
|
+
sourceInputLabel: 'Runtime update manifest URL',
|
|
159
|
+
sourcePlaceholder: 'https://gitee.com/huaqiu-electronics/dsh-pcb-eda/raw/main/edge-headless-release-manifest.json',
|
|
160
|
+
},
|
|
161
|
+
// ── Plugin release-manifest source editor ──
|
|
162
|
+
pluginSource: {
|
|
163
|
+
title: 'Plugin update source',
|
|
164
|
+
hint: 'Where the plugin update manifest is fetched from; saved changes take effect immediately',
|
|
165
|
+
edit: 'Edit',
|
|
166
|
+
save: 'Save',
|
|
167
|
+
reset: 'Reset to default',
|
|
168
|
+
saved: 'Update source saved — re-checking for updates…',
|
|
169
|
+
saveFailed: 'Failed to save the update source',
|
|
170
|
+
inputLabel: 'Plugin update manifest URL',
|
|
171
|
+
placeholder:
|
|
172
|
+
'https://gitee.com/huaqiu-electronics/dsh-pcb-eda/raw/main/plugins-release-manifest.json',
|
|
114
173
|
},
|
|
174
|
+
// Origin tags, shared by both source editors (hq-edge serves one type).
|
|
175
|
+
originOverride: 'Custom',
|
|
176
|
+
originConfig: 'Configured',
|
|
177
|
+
originUnset: 'Not configured',
|
|
115
178
|
lastError: 'Last check failed',
|
|
116
179
|
installError: 'Install failed',
|
|
117
180
|
installSuccess: 'Update installed — restarting DSH…',
|
|
181
|
+
confirmInstall: 'Confirm install',
|
|
182
|
+
cancel: 'Cancel',
|
|
118
183
|
}
|
|
119
184
|
|
|
120
185
|
export const COPY = { zh, en } as const
|
package/src/client/index.ts
CHANGED
|
@@ -694,6 +694,69 @@ const VIEW_CSS_TEXT = `
|
|
|
694
694
|
gap: 8px;
|
|
695
695
|
}
|
|
696
696
|
|
|
697
|
+
/* ── Runtime update action row (arm → confirm step) ───────────────────── */
|
|
698
|
+
.hqpu-runtime-actions {
|
|
699
|
+
margin-top: 12px;
|
|
700
|
+
}
|
|
701
|
+
.hqpu-runtime-confirm {
|
|
702
|
+
flex: 1 1 100%;
|
|
703
|
+
margin: 0;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
/* ── Runtime update source (fact row + editable URL field) ────────────── */
|
|
707
|
+
.hqpu-source-line {
|
|
708
|
+
display: inline-flex;
|
|
709
|
+
align-items: center;
|
|
710
|
+
gap: 8px;
|
|
711
|
+
min-width: 0;
|
|
712
|
+
max-width: 100%;
|
|
713
|
+
}
|
|
714
|
+
.hqpu-source-url {
|
|
715
|
+
min-width: 0;
|
|
716
|
+
overflow: hidden;
|
|
717
|
+
text-overflow: ellipsis;
|
|
718
|
+
white-space: nowrap;
|
|
719
|
+
}
|
|
720
|
+
.hqpu-source-editor {
|
|
721
|
+
display: flex;
|
|
722
|
+
align-items: center;
|
|
723
|
+
flex-wrap: wrap;
|
|
724
|
+
gap: 8px;
|
|
725
|
+
margin-top: 12px;
|
|
726
|
+
}
|
|
727
|
+
/* The plugin-source editor sits inside its own group right under the fact
|
|
728
|
+
* row, so it needs less air than the runtime one under the facts block. */
|
|
729
|
+
.hqpu-source-editor--plugin {
|
|
730
|
+
margin-top: 4px;
|
|
731
|
+
}
|
|
732
|
+
.hqpu-source-hint {
|
|
733
|
+
flex: 1 1 100%;
|
|
734
|
+
margin: 0;
|
|
735
|
+
}
|
|
736
|
+
.hqpu-source-controls {
|
|
737
|
+
flex: 1 1 100%;
|
|
738
|
+
}
|
|
739
|
+
.hqpu-source-input {
|
|
740
|
+
flex: 1 1 240px;
|
|
741
|
+
min-width: 0;
|
|
742
|
+
height: 28px;
|
|
743
|
+
box-sizing: border-box;
|
|
744
|
+
padding: 0 10px;
|
|
745
|
+
border: 0.5px solid var(--dsw-alias-border-l4);
|
|
746
|
+
border-radius: var(--dsw-radius-md);
|
|
747
|
+
background: var(--dsw-alias-bg-layer-3);
|
|
748
|
+
color: var(--dsw-alias-label-primary);
|
|
749
|
+
font: inherit;
|
|
750
|
+
font-size: 12.5px;
|
|
751
|
+
}
|
|
752
|
+
.hqpu-source-input:disabled {
|
|
753
|
+
opacity: 0.5;
|
|
754
|
+
}
|
|
755
|
+
.hqpu-source-input:focus-visible {
|
|
756
|
+
outline: var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary));
|
|
757
|
+
outline-offset: 1px;
|
|
758
|
+
}
|
|
759
|
+
|
|
697
760
|
/* ── Footer action row (ui-primitives Modal .footer) ───────────────────── */
|
|
698
761
|
.hqpu-footer {
|
|
699
762
|
display: flex;
|