@riceawa/dsh-lan-gateway 0.6.2 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -12
- package/lib/client.js +83 -143
- package/lib/client.js.map +1 -1
- package/lib/index.js +52 -2
- package/package.json +21 -23
- package/src/client/index.ts +37 -26
- package/src/client/lan-gateway-card.tsx +173 -197
- package/src/gateway.ts +31 -5
- package/src/request-policy.ts +36 -0
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
8
|
<img src="https://img.shields.io/badge/DeepSeek%20Harness-4d6bfe?logo=deepseek&logoColor=fff&style=flat-square" alt="DeepSeek Harness" />
|
|
9
|
-
<img src="https://img.shields.io/badge/version-0.
|
|
9
|
+
<img src="https://img.shields.io/badge/version-0.7.0-2b7fff?style=flat-square" alt="version 0.7.0" />
|
|
10
10
|
<img src="https://img.shields.io/badge/TLS-8b5cf6?logo=lock&logoColor=fff&style=flat-square" alt="TLS" />
|
|
11
11
|
<img src="https://img.shields.io/github/license/rice-awa/dsh-lan-gateway?style=flat-square" alt="MIT license" />
|
|
12
12
|
<a href="https://awesome-dsh-plugin.com"><img src="https://awesome-dsh-plugin.com/badge.svg" alt="awesome · DSH plugin" /></a>
|
|
@@ -44,7 +44,7 @@ cd dsh-lan-gateway
|
|
|
44
44
|
pnpm install
|
|
45
45
|
pnpm build # host → lib/index.js
|
|
46
46
|
pnpm build:client # client → lib/client.js
|
|
47
|
-
pnpm test #
|
|
47
|
+
pnpm test # 216 项
|
|
48
48
|
```
|
|
49
49
|
|
|
50
50
|
仓库里还有一个 [lan-gateway](skills/lan-gateway.md) 技能,安装后可直接在 dsh 对话里说「设置网关密码为 …」「开启远程访问」,agent 会调用 `lan_gateway` 工具完成,密码以参数传入,不写入配置,也不回显。改密码也可以不走模型:在本机打开 **Plugins** 页的「LAN 网关」卡片,直接填两次新密码覆盖(见下「登录密码」)。安装方式见 [INSTALL.md](INSTALL.md#for-agents完整安装流程)。
|
|
@@ -70,7 +70,7 @@ lan_gateway disable # 关闭
|
|
|
70
70
|
|
|
71
71
|
## 配置
|
|
72
72
|
|
|
73
|
-
所有可调项都写在本插件自己的 profile 条目里(dsh ≥ 0.1.7 起,设置写入按**条目 id** 寻址;此前的 `lan-gateway` 用户设置命名空间已随 `settingsScope` 一起移除)。打开侧边栏的 **Plugins**
|
|
73
|
+
所有可调项都写在本插件自己的 profile 条目里(dsh ≥ 0.1.7 起,设置写入按**条目 id** 寻址;此前的 `lan-gateway` 用户设置命名空间已随 `settingsScope` 一起移除)。打开侧边栏的 **Plugins** 页 →「已安装」→ `@riceawa/dsh-lan-gateway`:本插件的配置就渲染在这个包自己的页面上——挂的是 dsh 给「bundle 自己的配置」声明的 `plugins.bundle.config` 槽(按**包名** `@riceawa/dsh-lan-gateway` 挂载,与 dsh-mnemon、dshmarket 等插件同一套机制),页面标题、图标与面包屑由 Plugins 页自己绘制。保存即生效,监听器会按新配置自动重启。宿主机上用 `127.0.0.1` / `localhost` 打开网关地址时该页面同样可用(网关只对宿主机本机浏览器放行它的管理前缀,见「安全模型」);用局域网 IP、域名或从别的机器访问时页面读不到配置,改用 `lan_gateway` 工具。注册只在底座确实 served 本插件条目时发生——没有 Loader 条目(写入必然 409)时页面不会出现。下表既是卡片字段,也是配置键;页面顶部另有一栏「登录密码」,改密码不走配置表(见下「登录密码」):
|
|
74
74
|
|
|
75
75
|
| 键 | 默认值 | 说明 |
|
|
76
76
|
| --- | --- | --- |
|
|
@@ -100,7 +100,7 @@ lan_gateway disable # 关闭
|
|
|
100
100
|
卡片顶部就是「登录密码」栏:一个「已设置 / 未设置」状态,两个密码框(新密码、再次输入),点「修改密码」直接覆盖原密码。
|
|
101
101
|
|
|
102
102
|
- **不回显旧密码。** 界面只报告密码是否存在;卡片读不到旧密码。`state.json` 里只有 scrypt 哈希与盐,`/lan-gateway/password` 也从不把哈希、盐或长度返回给浏览器。
|
|
103
|
-
- **不需要旧密码。** 该路由与配置路由共用同一道围栏:只接受本机 loopback 的同源请求(Host 必须是回环、跨站请求拒绝、写操作必须带匹配的 `Origin
|
|
103
|
+
- **不需要旧密码。** 该路由与配置路由共用同一道围栏:只接受本机 loopback 的同源请求(Host 必须是回环、跨站请求拒绝、写操作必须带匹配的 `Origin`)。经网关访问时,只有宿主机本机浏览器(回环来源 + 回环地址)能被放行到这道围栏,远程浏览器仍是一律 403。能通过这两道门的本地用户本来就能读 `~/.dsh`。
|
|
104
104
|
- **改完即生效。** 写盘后递增会话代次:旧密码立即失效,所有已登录会话与已建立的 WebSocket 全部作废,各来源需要重新登录。
|
|
105
105
|
- **只设置,不清空。** 清空密码会按设计停掉监听器(没有密码不允许监听),卡片不做这件事;要清空请用 `lan_gateway set-password` 并留空密码。
|
|
106
106
|
- **密码不是配置键。** 它不写进 profile 条目,也不会出现在 `--dump-config` 里;保存配置字段动不到它,改密码也不会碰你尚未保存的字段草稿。
|
|
@@ -183,7 +183,7 @@ lan_gateway disable # 关闭
|
|
|
183
183
|
- **共享上游会话中继**(dsh ≥ 0.1.7)。dsh 不再信任回环 Host,要求出示 HMAC 签名的 `dsh-auth-*` cookie。插件经 `connection` 服务拿到启动令牌,在回环传输上做一次浏览器等价的令牌换取,取得 cookie 后中继到每个转发请求;上游一旦 401 就丢弃这条会话并重新换取。这仍是「单密码 = 单操作者」:通过网关登录的用户共用同一条上游会话,持钥的上游才是真正的授权主体。
|
|
184
184
|
- **登录页**。`/__login` 由网关独占、不转发。密码以 scrypt 校验,每写一次重新加盐;登录尝试按来源限流(5 次 / 分钟)。
|
|
185
185
|
- **会话 cookie** 是 `payload.signature` 结构(HMAC-SHA256),带撤销 epoch 与逐会话 id,`HttpOnly; SameSite=Strict`。登出撤销的是当前这条会话:它建立的 WebSocket 一并断开,其他设备不受影响。改密、清密、`rotate-secret` 递增 epoch,作废全部已签发 cookie 并断开全部已建立的 WebSocket,客户端需要重新登录。清空密码会直接停止监听。
|
|
186
|
-
- **管理面不外泄**。`/lan-gateway
|
|
186
|
+
- **管理面不外泄**。`/lan-gateway/*`(含配置路由与改密路由)由网关独占:唯一放行的是**宿主机本机浏览器**,即「TCP 来源为 loopback **且** 浏览器地址栏写的是回环权威(`127.0.0.1` / `localhost` / `::1`)」的请求,其余一律 403 不转发,远程访问者无法借网关改写 Host 触及本机 loopback 的配置接口。两个条件缺一不可:来源一项远程无法伪造,地址一项则挡住受信 TLS 终止代理的部署——那里所有请求的 TCP 来源都是代理自己的回环地址,只有回环权威仍被拒。放行的请求照样要过会话门与 CSRF 围栏,再以改写后的 loopback Host 交给插件自己的回环同源围栏。升级请求(WebSocket)不在此列,一律拒。原生 `/lan-gateway/config` 只应答回环 Host 且同源的请求。远程管理走 `lan_gateway` 工具。
|
|
187
187
|
- **CSRF 围栏**(HTTP 与 WebSocket)。网关把 Origin 改写回 loopback,会蒙蔽 dsh 自身的 CSRF 防线,所以在改写前对每个转发请求自检:`sec-fetch-site: cross-site` 直接拒;Origin 必须匹配访问者实际使用的网关权威来源;状态变更方法与 WebSocket 升级请求必须携带同源 Origin,否则 403。
|
|
188
188
|
- **未设置密码时拒绝监听**,与来源无关。
|
|
189
189
|
|
|
@@ -204,7 +204,7 @@ client bundle 在模块级给 `Crypto` 原型补一个基于 `crypto.getRandomVa
|
|
|
204
204
|
## 开发
|
|
205
205
|
|
|
206
206
|
```bash
|
|
207
|
-
pnpm test #
|
|
207
|
+
pnpm test # 216 项
|
|
208
208
|
pnpm typecheck # tsc 双端(host + client)
|
|
209
209
|
```
|
|
210
210
|
|
|
@@ -212,18 +212,20 @@ pnpm typecheck # tsc 双端(host + client)
|
|
|
212
212
|
✓ tests/gateway.test.ts (40) 分类 / HMAC cookie / epoch / 逐会话撤销 / 密码状态 / 限流
|
|
213
213
|
✓ tests/start-guard.test.ts (19) fail-closed 启动守卫 / 配置路由回环围栏 /
|
|
214
214
|
Secure cookie 属性推断(含 null 清除路径)
|
|
215
|
-
✓ tests/request-policy.test.ts (
|
|
216
|
-
两个方向的头部变换
|
|
215
|
+
✓ tests/request-policy.test.ts (50) 判定缝纯函数:路径归一化 / 归属前缀 / 同站与登录围栏 /
|
|
216
|
+
回环权威(Host)判定 / 两个方向的头部变换
|
|
217
217
|
✓ tests/upstream-session.test.ts ( 8) 真实回环令牌换取:cookie 名匹配 / 拒绝后重换 /
|
|
218
218
|
invalidate 重获取 / 日志播报 / 保住已持有会话
|
|
219
219
|
✓ tests/x509.test.ts ( 6) 自签名证书 DER/SAN/签名/TLS 握手
|
|
220
220
|
✓ tests/tls.test.ts ( 9) 证书持久化 / 到期换发 / 重生成 / 自定义证书加载
|
|
221
221
|
✓ tests/uuid-shim.test.ts ( 3) 不安全源补丁 / 安全源 no-op / v4 正确性
|
|
222
|
-
✓ tests/settings-card.test.ts (
|
|
222
|
+
✓ tests/settings-card.test.ts (19) 设置页字段编解码(三态 auto ↔ false 不可混淆)/
|
|
223
223
|
卡片注册槽与条目 id 契约 / 密码草稿闸门(长度与确认)/
|
|
224
|
-
密码状态徽标三态(缺字段 =
|
|
225
|
-
|
|
226
|
-
|
|
224
|
+
密码状态徽标三态(缺字段 = 未知,不是「未设置」)/
|
|
225
|
+
「网关自己拒绝」与其它失败的文案分流
|
|
226
|
+
✓ tests/integration/gateway.test.ts (30) 真实网关端到端:全来源登录 / LAN 豁免 / 跨站 403 / 升级拒绝 /
|
|
227
|
+
cookie 属性 / epoch 撤销 / 逐会话登出 / 尾斜杠 / IPv6 / 会话中继 /
|
|
228
|
+
管理面只对本机浏览器放行(来源与 Host 两项都要满足)
|
|
227
229
|
✓ tests/integration/management-plane.test.ts (24) 真实 apply():工具与卡片交替启停 / 未编辑字段与未知键保留 /
|
|
228
230
|
清空后继承 / 拒绝不可启动配置 / 卡片改密路由(覆盖原密码 /
|
|
229
231
|
递增代次 / 不返回哈希 / 不给清空 / 无 settings 也生效)
|
package/lib/client.js
CHANGED
|
@@ -151,14 +151,18 @@ window.__ModuleLoader__.load({
|
|
|
151
151
|
//#endregion
|
|
152
152
|
//#region src/client/lan-gateway-card.tsx
|
|
153
153
|
/**
|
|
154
|
-
* The lan-gateway
|
|
155
|
-
* through its `plugins.
|
|
154
|
+
* The lan-gateway configuration card, rendered by the official DSH Plugins page
|
|
155
|
+
* on this bundle's own page through its `plugins.bundle.config` slot.
|
|
156
156
|
*
|
|
157
157
|
* ModLens-style: the card carries NO injected services. It reads and writes
|
|
158
158
|
* the loopback-only `/lan-gateway/config` host route (the browser never sees
|
|
159
159
|
* the settings seam or any secret), so the only platform service it needs is
|
|
160
160
|
* the `slots` service every plugin already has.
|
|
161
161
|
*
|
|
162
|
+
* The page — not the card — draws the plugin's title, icon, and crumb, and the
|
|
163
|
+
* card is the page body: `view: 'summary'` renders nothing (bundle
|
|
164
|
+
* configuration is `page`-only).
|
|
165
|
+
*
|
|
162
166
|
* @module @riceawa/dsh-lan-gateway/client/card
|
|
163
167
|
*/
|
|
164
168
|
/**
|
|
@@ -185,6 +189,22 @@ window.__ModuleLoader__.load({
|
|
|
185
189
|
if (password !== confirm) return "mismatch";
|
|
186
190
|
return null;
|
|
187
191
|
}
|
|
192
|
+
/**
|
|
193
|
+
* Whether the config route's refusal came from the gateway rather than from dsh
|
|
194
|
+
* itself.
|
|
195
|
+
*
|
|
196
|
+
* The gateway owns the `/lan-gateway*` prefix and answers it with a bare 403
|
|
197
|
+
* `forbidden` whenever the client is not on the host; dsh, when it answers the
|
|
198
|
+
* route at all, does not produce that pair. Telling the two apart is what lets
|
|
199
|
+
* the card say "you are not on the host loopback" instead of the generic
|
|
200
|
+
* "cannot read the configuration".
|
|
201
|
+
* @param status - the HTTP status of the failed read.
|
|
202
|
+
* @param body - that response's body.
|
|
203
|
+
* @returns true when the gateway itself refused the read.
|
|
204
|
+
*/
|
|
205
|
+
function refusedByGateway(status, body) {
|
|
206
|
+
return status === 403 && body.trim() === "forbidden";
|
|
207
|
+
}
|
|
188
208
|
const LABELS = {
|
|
189
209
|
zh: {
|
|
190
210
|
title: "LAN 网关",
|
|
@@ -194,7 +214,8 @@ window.__ModuleLoader__.load({
|
|
|
194
214
|
saving: "保存中…",
|
|
195
215
|
discard: "放弃",
|
|
196
216
|
reset: "重置",
|
|
197
|
-
readOnly: "
|
|
217
|
+
readOnly: "读不到网关配置:这个接口只在宿主机本机应答——Host 必须是回环地址且同源。请在宿主机上打开 dsh web 时修改,远程请改用 lan_gateway 工具。",
|
|
218
|
+
readOnlyGateway: "网关拒绝了这次读取:/lan-gateway/* 管理面由网关独占,只放行「TCP 来源为回环 且 地址写的是 127.0.0.1 / localhost」的浏览器。你现在不是从宿主机回环地址访问的——请在宿主机上用 127.0.0.1 或 localhost 打开本页(局域网 IP、域名都不算),或远程改用 lan_gateway 工具。",
|
|
198
219
|
saveFailed: "保存未生效,请检查输入后重试。",
|
|
199
220
|
loadFailed: "无法读取网关配置",
|
|
200
221
|
emptyMeansClear: "留空 = 使用默认",
|
|
@@ -259,7 +280,8 @@ window.__ModuleLoader__.load({
|
|
|
259
280
|
saving: "Saving…",
|
|
260
281
|
discard: "Discard",
|
|
261
282
|
reset: "Reset",
|
|
262
|
-
readOnly: "
|
|
283
|
+
readOnly: "Cannot read the configuration: the route answers on the host only — the Host must be a loopback address and same-origin. Change the settings where dsh web runs on the host, or use the lan_gateway tool remotely.",
|
|
284
|
+
readOnlyGateway: "The gateway refused this read: it owns the /lan-gateway/* management plane and admits only browsers that are both loopback-sourced and using a 127.0.0.1 / localhost address. You are not on the host loopback — open this page on the host via 127.0.0.1 or localhost (a LAN IP or a hostname does not qualify), or use the lan_gateway tool remotely.",
|
|
263
285
|
saveFailed: "The save did not land — check the inputs and retry.",
|
|
264
286
|
loadFailed: "Cannot read the gateway configuration",
|
|
265
287
|
emptyMeansClear: "Empty = default",
|
|
@@ -321,28 +343,19 @@ window.__ModuleLoader__.load({
|
|
|
321
343
|
return (typeof navigator !== "undefined" ? navigator.language : "en").toLowerCase().startsWith("zh") ? LABELS.zh : LABELS.en;
|
|
322
344
|
}
|
|
323
345
|
/**
|
|
324
|
-
*
|
|
325
|
-
*
|
|
326
|
-
* @returns the localized card title.
|
|
327
|
-
*/
|
|
328
|
-
function cardTitle() {
|
|
329
|
-
return labels().title;
|
|
330
|
-
}
|
|
331
|
-
/**
|
|
332
|
-
* Render the LAN gateway card. Self-loading: fetches the config route on
|
|
333
|
-
* mount, posts the edited config on save.
|
|
346
|
+
* Render the LAN gateway configuration card. Self-loading: fetches the config
|
|
347
|
+
* route on mount, posts the edited config on save.
|
|
334
348
|
*
|
|
335
|
-
* `
|
|
336
|
-
*
|
|
337
|
-
*
|
|
349
|
+
* `summary` renders nothing: dsh asks a bundle's own configuration for its page
|
|
350
|
+
* body only, and the card's former one-liner (a list entry on the official
|
|
351
|
+
* plugin card) went away with `plugins.item`.
|
|
338
352
|
* @param props - the view the Plugins page is asking for.
|
|
339
|
-
* @returns the
|
|
353
|
+
* @returns the page body, or nothing while the view is `summary`.
|
|
340
354
|
*/
|
|
341
355
|
function LanGatewayCard(props) {
|
|
342
356
|
const t = labels();
|
|
343
|
-
const [open, setOpen] = (0, react.useState)(false);
|
|
344
357
|
const [route, setRoute] = (0, react.useState)(null);
|
|
345
|
-
const [
|
|
358
|
+
const [failure, setFailure] = (0, react.useState)(null);
|
|
346
359
|
const [drafts, setDrafts] = (0, react.useState)({});
|
|
347
360
|
const [saving, setSaving] = (0, react.useState)(false);
|
|
348
361
|
const [failed, setFailed] = (0, react.useState)(null);
|
|
@@ -355,36 +368,28 @@ window.__ModuleLoader__.load({
|
|
|
355
368
|
let cancelled = false;
|
|
356
369
|
fetch("/lan-gateway/config").then(async (response) => {
|
|
357
370
|
if (cancelled) return;
|
|
358
|
-
if (!response.ok)
|
|
371
|
+
if (!response.ok) {
|
|
372
|
+
const body = await response.text().catch(() => "");
|
|
373
|
+
if (!cancelled) setFailure(refusedByGateway(response.status, body) ? "gateway" : "other");
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
359
376
|
setRoute(await response.json());
|
|
360
377
|
}).catch(() => {
|
|
361
|
-
if (!cancelled)
|
|
378
|
+
if (!cancelled) setFailure("other");
|
|
362
379
|
});
|
|
363
380
|
return () => {
|
|
364
381
|
cancelled = true;
|
|
365
382
|
};
|
|
366
383
|
}, []);
|
|
367
|
-
if (props.view === "summary") return
|
|
368
|
-
if (
|
|
384
|
+
if (props.view === "summary") return null;
|
|
385
|
+
if (failure !== null) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
369
386
|
style: styles.card,
|
|
370
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.
|
|
371
|
-
style: styles.
|
|
372
|
-
children:
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
children: t.title
|
|
377
|
-
})
|
|
378
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
379
|
-
style: styles.description,
|
|
380
|
-
children: t.loadFailed
|
|
381
|
-
})]
|
|
382
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
383
|
-
style: styles.body,
|
|
384
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
385
|
-
style: styles.hint,
|
|
386
|
-
children: t.readOnly
|
|
387
|
-
})
|
|
387
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
388
|
+
style: styles.status,
|
|
389
|
+
children: t.loadFailed
|
|
390
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
391
|
+
style: styles.hint,
|
|
392
|
+
children: failure === "gateway" ? t.readOnlyGateway : t.readOnly
|
|
388
393
|
})]
|
|
389
394
|
});
|
|
390
395
|
if (route === null) return null;
|
|
@@ -592,46 +597,18 @@ window.__ModuleLoader__.load({
|
|
|
592
597
|
};
|
|
593
598
|
const statusLine = `${route.running ? t.running : t.stopped} · ${t.tls}: ${route.tls} · :${route.port}`;
|
|
594
599
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
595
|
-
style:
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
"
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
style: styles.headerTop,
|
|
608
|
-
children: [
|
|
609
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
610
|
-
style: styles.name,
|
|
611
|
-
children: t.title
|
|
612
|
-
}),
|
|
613
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
614
|
-
style: styles.status,
|
|
615
|
-
title: statusLine,
|
|
616
|
-
children: statusLine
|
|
617
|
-
}),
|
|
618
|
-
dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
619
|
-
style: styles.pending,
|
|
620
|
-
children: t.unsaved
|
|
621
|
-
}) : null,
|
|
622
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
623
|
-
style: open ? {
|
|
624
|
-
...styles.chevron,
|
|
625
|
-
...styles.chevronOpen
|
|
626
|
-
} : styles.chevron,
|
|
627
|
-
children: open ? "▾" : "▸"
|
|
628
|
-
})
|
|
629
|
-
]
|
|
630
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
631
|
-
style: styles.description,
|
|
632
|
-
children: t.description
|
|
633
|
-
})]
|
|
634
|
-
}), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
600
|
+
style: styles.card,
|
|
601
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
602
|
+
style: styles.statusRow,
|
|
603
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
604
|
+
style: styles.status,
|
|
605
|
+
title: statusLine,
|
|
606
|
+
children: statusLine
|
|
607
|
+
}), dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
608
|
+
style: styles.pending,
|
|
609
|
+
children: t.unsaved
|
|
610
|
+
}) : null]
|
|
611
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
635
612
|
style: styles.body,
|
|
636
613
|
children: [
|
|
637
614
|
route.lastError ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
@@ -754,7 +731,7 @@ window.__ModuleLoader__.load({
|
|
|
754
731
|
]
|
|
755
732
|
})
|
|
756
733
|
]
|
|
757
|
-
})
|
|
734
|
+
})]
|
|
758
735
|
});
|
|
759
736
|
}
|
|
760
737
|
/** Theme token with a fallback for token-less environments. */
|
|
@@ -764,13 +741,10 @@ window.__ModuleLoader__.load({
|
|
|
764
741
|
const L = {
|
|
765
742
|
border: tk("--dsw-alias-border-l2", "rgba(127,127,127,0.35)"),
|
|
766
743
|
bg: tk("--dsw-alias-bg-layer-3", "transparent"),
|
|
767
|
-
bgOpen: tk("--dsw-alias-bg-layer-2", "transparent"),
|
|
768
744
|
labelPrimary: tk("--dsw-alias-label-primary", "inherit"),
|
|
769
745
|
labelSecondary: tk("--dsw-alias-label-secondary", "inherit"),
|
|
770
746
|
labelTertiary: tk("--dsw-alias-label-tertiary", "rgba(127,127,127,0.8)"),
|
|
771
|
-
labelDimmed: tk("--dsw-alias-label-dimmed", "rgba(127,127,127,0.6)"),
|
|
772
747
|
error: tk("--dsw-alias-label-error", "#d1242f"),
|
|
773
|
-
brand: tk("--dsw-alias-brand-primary", "#4f6ef7"),
|
|
774
748
|
badgeBg: tk("--dsw-alias-bg-module-platform", "rgba(127,127,127,0.14)")
|
|
775
749
|
};
|
|
776
750
|
const styles = {
|
|
@@ -779,45 +753,21 @@ window.__ModuleLoader__.load({
|
|
|
779
753
|
border: `1px solid ${L.border}`,
|
|
780
754
|
borderRadius: "12px",
|
|
781
755
|
background: L.bg,
|
|
782
|
-
transition: "border-color .16s, background .16s",
|
|
783
|
-
overflow: "hidden"
|
|
784
|
-
},
|
|
785
|
-
cardOpen: {
|
|
786
|
-
background: L.bgOpen,
|
|
787
|
-
borderColor: L.labelDimmed
|
|
788
|
-
},
|
|
789
|
-
header: {
|
|
790
756
|
display: "flex",
|
|
791
757
|
flexDirection: "column",
|
|
792
|
-
|
|
793
|
-
gap: "6px",
|
|
794
|
-
width: "100%",
|
|
795
|
-
padding: "14px 16px",
|
|
796
|
-
border: 0,
|
|
797
|
-
background: "none",
|
|
798
|
-
font: "inherit",
|
|
799
|
-
color: "inherit",
|
|
800
|
-
textAlign: "left",
|
|
801
|
-
cursor: "pointer"
|
|
758
|
+
overflow: "hidden"
|
|
802
759
|
},
|
|
803
|
-
|
|
760
|
+
statusRow: {
|
|
804
761
|
display: "flex",
|
|
805
762
|
alignItems: "center",
|
|
806
763
|
gap: "12px",
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
name: {
|
|
810
|
-
flex: "1 1 auto",
|
|
811
|
-
minWidth: 0,
|
|
812
|
-
fontSize: "15px",
|
|
813
|
-
fontWeight: 600,
|
|
814
|
-
lineHeight: 1.4,
|
|
815
|
-
color: L.labelPrimary
|
|
764
|
+
padding: "12px 16px",
|
|
765
|
+
borderBottom: `1px solid ${L.border}`
|
|
816
766
|
},
|
|
817
767
|
status: {
|
|
818
768
|
flex: "0 1 auto",
|
|
819
769
|
minWidth: 0,
|
|
820
|
-
maxWidth: "
|
|
770
|
+
maxWidth: "80%",
|
|
821
771
|
fontSize: "11px",
|
|
822
772
|
lineHeight: 1.4,
|
|
823
773
|
color: L.labelTertiary,
|
|
@@ -825,15 +775,6 @@ window.__ModuleLoader__.load({
|
|
|
825
775
|
overflow: "hidden",
|
|
826
776
|
textOverflow: "ellipsis"
|
|
827
777
|
},
|
|
828
|
-
description: {
|
|
829
|
-
display: "block",
|
|
830
|
-
fontSize: "13px",
|
|
831
|
-
lineHeight: 1.5,
|
|
832
|
-
color: L.labelTertiary,
|
|
833
|
-
whiteSpace: "nowrap",
|
|
834
|
-
overflow: "hidden",
|
|
835
|
-
textOverflow: "ellipsis"
|
|
836
|
-
},
|
|
837
778
|
pending: {
|
|
838
779
|
flex: "none",
|
|
839
780
|
borderRadius: "999px",
|
|
@@ -845,17 +786,8 @@ window.__ModuleLoader__.load({
|
|
|
845
786
|
background: L.badgeBg,
|
|
846
787
|
color: L.labelSecondary
|
|
847
788
|
},
|
|
848
|
-
chevron: {
|
|
849
|
-
flex: "none",
|
|
850
|
-
color: L.labelTertiary,
|
|
851
|
-
fontSize: "12px",
|
|
852
|
-
transition: "transform .16s"
|
|
853
|
-
},
|
|
854
|
-
chevronOpen: { transform: "rotate(180deg)" },
|
|
855
789
|
body: {
|
|
856
|
-
|
|
857
|
-
margin: "0 16px",
|
|
858
|
-
paddingBottom: "8px",
|
|
790
|
+
padding: "4px 16px 8px",
|
|
859
791
|
display: "flex",
|
|
860
792
|
flexDirection: "column"
|
|
861
793
|
},
|
|
@@ -1015,8 +947,12 @@ window.__ModuleLoader__.load({
|
|
|
1015
947
|
* module scope. With TLS enabled the origin is secure and the shim is a
|
|
1016
948
|
* no-op.
|
|
1017
949
|
* 2. Settings card: registers the LAN gateway card into the official Plugins
|
|
1018
|
-
* page
|
|
1019
|
-
* from the
|
|
950
|
+
* page as THIS bundle's own configuration (`plugins.bundle.config`, keyed by
|
|
951
|
+
* the package name), so port, CIDRs, auth, and TLS stay adjustable from the
|
|
952
|
+
* GUI on the plugin's own page. `plugins.item` is deliberately not used: dsh
|
|
953
|
+
* reserves it for the official settings pages, one companion package per
|
|
954
|
+
* host-plane namespace, and a bundle's configuration belongs in its own
|
|
955
|
+
* config slot instead.
|
|
1020
956
|
*/
|
|
1021
957
|
/** RFC 4122 v4 UUID from crypto.getRandomValues (available on insecure origins). */
|
|
1022
958
|
function uuidFromRandomValues() {
|
|
@@ -1060,8 +996,14 @@ window.__ModuleLoader__.load({
|
|
|
1060
996
|
installRandomUuidShim();
|
|
1061
997
|
const name = "dsh-lan-gateway";
|
|
1062
998
|
/**
|
|
1063
|
-
* The
|
|
1064
|
-
*
|
|
999
|
+
* The package name dsh keys a bundle's own configuration by in
|
|
1000
|
+
* `plugins.bundle.config`, and the name this profile's bundle row resolves to.
|
|
1001
|
+
* It is the package's npm name — not the locale namespace or the row id.
|
|
1002
|
+
*/
|
|
1003
|
+
const PACKAGE_NAME = "@riceawa/dsh-lan-gateway";
|
|
1004
|
+
/**
|
|
1005
|
+
* The profile entry id this plugin's bundle patch composes it under. dsh >=
|
|
1006
|
+
* 0.1.7 addresses both settings writes and the serve gate by entry id.
|
|
1065
1007
|
*/
|
|
1066
1008
|
const ENTRY_ID = "dsh-lan-gateway";
|
|
1067
1009
|
/** The slots service the card rides, and the settings mirror the gate reads. */
|
|
@@ -1072,11 +1014,9 @@ window.__ModuleLoader__.load({
|
|
|
1072
1014
|
*/
|
|
1073
1015
|
function apply(ctx) {
|
|
1074
1016
|
installRandomUuidShim();
|
|
1075
|
-
ctx.effect(() => ctx.configForms.whileServed([ENTRY_ID], () => ctx.slots.inject("plugins.
|
|
1076
|
-
name: "plugins.
|
|
1077
|
-
|
|
1078
|
-
order: 30,
|
|
1079
|
-
label: () => cardTitle()
|
|
1017
|
+
ctx.effect(() => ctx.configForms.whileServed([ENTRY_ID], () => ctx.slots.inject("plugins.bundle.config", () => ctx.slots.register({
|
|
1018
|
+
name: "plugins.bundle.config",
|
|
1019
|
+
key: PACKAGE_NAME
|
|
1080
1020
|
}, LanGatewayCard))));
|
|
1081
1021
|
}
|
|
1082
1022
|
//#endregion
|