dsh-enhancement 0.1.0 → 1.0.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/lib/client.js +5 -4
- package/package.json +6 -6
- package/src/client/index.tsx +10 -9
package/lib/client.js
CHANGED
|
@@ -133,7 +133,9 @@ window.__ModuleLoader__.load({
|
|
|
133
133
|
.enh-root input[type="checkbox"] { appearance:none; flex:none; width:36px; height:20px; border-radius:999px; background:var(--dsw-alias-border-l2, #d0d5dd); position:relative; cursor:pointer; transition:background .15s; margin:0; }
|
|
134
134
|
.enh-root input[type="checkbox"]::after { content:""; position:absolute; top:2px; left:2px; width:16px; height:16px; border-radius:50%; background:#fff; box-shadow:0 1px 2px rgba(0,0,0,.2); transition:transform .15s; }
|
|
135
135
|
.enh-root input[type="checkbox"]:checked { background:var(--dsw-alias-state-business-primary, #4d8dff); }
|
|
136
|
+
|
|
136
137
|
.enh-root input[type="checkbox"]:checked::after { transform:translateX(16px); }
|
|
138
|
+
.enh-root input[type="checkbox"]:disabled { opacity:.45; cursor:not-allowed; }
|
|
137
139
|
.enh-root input[type="checkbox"]:focus-visible { outline:2px solid var(--dsw-alias-state-business-primary, #4d8dff); outline-offset:2px; }
|
|
138
140
|
|
|
139
141
|
.enh-root .enh-block-head { display:flex; align-items:flex-start; justify-content:space-between; gap:12px; }
|
|
@@ -420,7 +422,7 @@ window.__ModuleLoader__.load({
|
|
|
420
422
|
const restart = () => run(async () => {
|
|
421
423
|
await restartGateway();
|
|
422
424
|
}, '网关已重启');
|
|
423
|
-
const copyToClipboard = (text
|
|
425
|
+
const copyToClipboard = (text) => {
|
|
424
426
|
setError(null);
|
|
425
427
|
const fallback = () => {
|
|
426
428
|
const area = document.createElement('textarea');
|
|
@@ -431,7 +433,6 @@ window.__ModuleLoader__.load({
|
|
|
431
433
|
area.select();
|
|
432
434
|
try {
|
|
433
435
|
document.execCommand('copy');
|
|
434
|
-
setNotice(`${label}已复制`);
|
|
435
436
|
}
|
|
436
437
|
catch (err) {
|
|
437
438
|
setError(err instanceof Error ? err.message : String(err));
|
|
@@ -441,7 +442,7 @@ window.__ModuleLoader__.load({
|
|
|
441
442
|
}
|
|
442
443
|
};
|
|
443
444
|
if (navigator.clipboard?.writeText) {
|
|
444
|
-
void navigator.clipboard.writeText(text).
|
|
445
|
+
void navigator.clipboard.writeText(text).catch(fallback);
|
|
445
446
|
}
|
|
446
447
|
else {
|
|
447
448
|
fallback();
|
|
@@ -587,7 +588,7 @@ window.__ModuleLoader__.load({
|
|
|
587
588
|
pushSubject !== status.push.subject;
|
|
588
589
|
const accessDirty = newPassword.length > 0 || confirmPassword.length > 0;
|
|
589
590
|
const iconUrl = (kind) => `/pwa/enhancement/${kind}?v=${iconVersion}`;
|
|
590
|
-
return ((0, jsx_runtime_1.jsxs)("div", { className: "enh-root", style: styles.wrap, children: [(0, jsx_runtime_1.jsx)("style", { children: CARD_CSS }), (0, jsx_runtime_1.jsxs)("div", { style: styles.header, children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h3", { style: styles.title, children: "\u63D2\u4EF6\u72B6\u6001" }), (0, jsx_runtime_1.jsx)("div", { style: styles.muted, children: "DSH \u589E\u5F3A\u7684\u7F51\u5173\u3001PWA\u3001\u901A\u77E5\u4E0E\u63A8\u9001\u914D\u7F6E\u3002" })] }), (0, jsx_runtime_1.jsxs)("span", { style: styles.pill, children: [(0, jsx_runtime_1.jsx)("span", { style: styles.dot }), "\u8FD0\u884C\u4E2D"] })] }), error ? (0, jsx_runtime_1.jsx)("div", { style: styles.error, children: error }) : null, notice ? (0, jsx_runtime_1.jsx)("div", { style: styles.success, children: notice }) : null, (0, jsx_runtime_1.jsxs)(Block, { order: 1, title: "\u7F51\u5173\u914D\u7F6E", hint: "\u5BC6\u7801\u7F51\u5173\u7684\u76D1\u542C\u3001\u8BBF\u95EE\u4E0E\u4F1A\u8BDD\u8BBE\u7F6E\uFF1BHTTPS \u914D\u7F6E\u4E5F\u5728\u672C\u5757\u5185\uFF0C\u4FDD\u5B58\u65F6\u6309\u987A\u5E8F\u5E94\u7528\u3002", dirty: gatewayDirty, busy: busy, saveLabel: "\u4FDD\u5B58\u5E76\u5E94\u7528", onSave: saveGateway, status: blockStatus.gateway, extra: (0, jsx_runtime_1.jsx)("button", { style: styles.button, disabled: busy || !status.passwordSet, onClick: restart, children: "\u91CD\u542F\u7F51\u5173" }), children: [(0, jsx_runtime_1.jsxs)(InlineRow, { label: "DSH \u4E0A\u6E38", children: [(0, jsx_runtime_1.jsx)("code", { style: { ...styles.code, flex: '1 1 260px', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }, children: status.dshAccessUrl || status.dshOrigin }), (0, jsx_runtime_1.jsx)("button", { style: styles.iconButton, title: "\u590D\u5236", "aria-label": "\u590D\u5236 DSH \u4E0A\u6E38\u5730\u5740\uFF08\u542B token\uFF09", onClick: () => copyToClipboard(status.dshAccessUrl || status.dshOrigin, 'DSH 上游地址'), children: (0, jsx_runtime_1.jsx)(CopyIcon, {}) }), (0, jsx_runtime_1.jsx)("a", { style: styles.iconButton, title: "\u6253\u5F00", "aria-label": "\u6253\u5F00 DSH \u4E0A\u6E38\uFF08\u542B token\uFF09", href: status.dshAccessUrl || status.dshOrigin, target: "_blank", rel: "noreferrer", children: (0, jsx_runtime_1.jsx)(OpenIcon, {}) })] }), (0, jsx_runtime_1.jsx)(InlineRow, { label: "\u7F51\u5173\u5730\u5740", children: status.url ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("code", { style: { ...styles.code, flex: '1 1 260px', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }, children: status.url }), (0, jsx_runtime_1.jsx)("button", { style: styles.iconButton, title: "\u590D\u5236", "aria-label": "\u590D\u5236\u7F51\u5173\u5730\u5740", onClick: () => copyToClipboard(status.url, '网关地址'), children: (0, jsx_runtime_1.jsx)(CopyIcon, {}) }), (0, jsx_runtime_1.jsx)("a", { style: styles.iconButton, title: "\u6253\u5F00", "aria-label": "\u6253\u5F00\u7F51\u5173", href: status.url, target: "_blank", rel: "noreferrer", children: (0, jsx_runtime_1.jsx)(OpenIcon, {}) })] })) : (0, jsx_runtime_1.jsx)("span", { style: styles.muted, children: "\u672A\u8FD0\u884C\uFF1B\u8BBE\u7F6E\u5BC6\u7801\u5E76\u542F\u7528\u540E\u751F\u6210" }) }), !status.dshSecretReady ? (0, jsx_runtime_1.jsx)("div", { style: styles.error, children: "\u7F51\u5173\u4F1A\u8BDD\u6CE8\u5165\u4E0D\u53EF\u7528\uFF1A\u65E0\u6CD5\u8BFB\u53D6 DSH \u6D4F\u89C8\u5668\u7B7E\u540D\u5BC6\u94A5\uFF0C\u7F51\u5173\u4EE3\u7406\u4F1A\u8FD4\u56DE 401\u3002" }) : null, status.error ? (0, jsx_runtime_1.jsxs)("div", { style: styles.error, children: ["\u6700\u8FD1\u9519\u8BEF\uFF1A", status.error] }) : null, (0, jsx_runtime_1.jsx)(Row, { label: "\u542F\u7528\u7F51\u5173", children: (0, jsx_runtime_1.jsxs)("label", { style: { display: 'inline-flex', alignItems: 'center', gap: 8 }, children: [(0, jsx_runtime_1.jsx)("input", { type: "checkbox", checked: enabled, onChange: (event) => setEnabled(event.target.checked) }), (0, jsx_runtime_1.jsx)("span", { children: enabled ? '启用' : '停用' })] }) }), enabled ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { className: "enh-grid3", children: [(0, jsx_runtime_1.jsxs)(Row, { label: "\u76D1\u542C\u5730\u5740", children: [(0, jsx_runtime_1.jsx)("select", { style: styles.select, value: bindHost, onChange: (event) => setBindHost(event.target.value), children: interfaceOptions.map((option) => ((0, jsx_runtime_1.jsx)("option", { value: option.value, children: option.label }, option.value))) }), currentInterface ? (0, jsx_runtime_1.jsxs)("div", { style: styles.muted, children: ["\u5F53\u524D\uFF1A", currentInterface.address, "\uFF08", currentInterface.name, "\uFF09"] }) : null] }), (0, jsx_runtime_1.jsx)(Row, { label: "\u7AEF\u53E3", children: (0, jsx_runtime_1.jsx)("input", { style: styles.input, value: port, inputMode: "numeric", onChange: (event) => setPort(event.target.value) }) }), (0, jsx_runtime_1.jsxs)(Row, { label: "\u4F1A\u8BDD\u6709\u6548\u671F", children: [(0, jsx_runtime_1.jsx)("input", { style: styles.input, value: sessionDays, inputMode: "numeric", onChange: (event) => setSessionDays(event.target.value) }), (0, jsx_runtime_1.jsx)("div", { style: styles.muted, children: "\u5929\uFF1B\u5BC6\u7801\u4FEE\u6539\u540E\u5168\u90E8\u4F1A\u8BDD\u5931\u6548\u3002" })] })] }), (0, jsx_runtime_1.jsx)(Row, { label: "\u767B\u5F55\u5931\u8D25\u8282\u6D41", children: (0, jsx_runtime_1.jsxs)("label", { style: { display: 'inline-flex', alignItems: 'center', gap: 8 }, children: [(0, jsx_runtime_1.jsx)("input", { type: "checkbox", checked: loginThrottle, onChange: (event) => setLoginThrottle(event.target.checked) }), (0, jsx_runtime_1.jsx)("span", { children: loginThrottle ? '开启(5 次 / 10 分钟 → 锁 1 分钟)' : '关闭(不推荐)' })] }) }), (0, jsx_runtime_1.jsx)(Row, { label: "HTTPS \u6A21\u5F0F", children: (0, jsx_runtime_1.jsxs)("select", { style: styles.select, value: httpsChoice, onChange: (event) => setHttpsChoice(event.target.value), children: [(0, jsx_runtime_1.jsx)("option", { value: "off", children: "\u5173\u95ED HTTPS" }), (0, jsx_runtime_1.jsx)("option", { value: "selfsigned", children: "\u4F7F\u7528 CA \u8BC1\u4E66\u81EA\u7B7E\u53D1" }), (0, jsx_runtime_1.jsx)("option", { value: "uploaded", children: "\u4F7F\u7528\u5DF2\u6709\u8BC1\u4E66" })] }) }), httpsChoice === 'off' ? ((0, jsx_runtime_1.jsx)("div", { style: styles.muted, children: "\u5173\u95ED\u540E\u6D4F\u89C8\u5668\u4F1A\u63D0\u793A\u4E0D\u5B89\u5168\uFF0CPWA \u5B89\u88C5\u3001Service Worker \u4E0E Web Push \u90FD\u4E0D\u53EF\u7528\u3002\u4FDD\u5B58\u540E\u751F\u6548\u3002" })) : null, httpsChoice === 'selfsigned' ? ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)(Row, { label: "CA \u6765\u6E90", children: (0, jsx_runtime_1.jsxs)("div", { style: { display: 'flex', flexWrap: 'wrap', gap: 16 }, children: [(0, jsx_runtime_1.jsxs)("label", { style: { display: 'inline-flex', alignItems: 'center', gap: 6 }, children: [(0, jsx_runtime_1.jsx)("input", { type: "radio", name: "ca-source", checked: caSource === 'generated', onChange: () => setCaSource('generated') }), (0, jsx_runtime_1.jsx)("span", { children: "\u81EA\u52A8\u751F\u6210 CA" })] }), (0, jsx_runtime_1.jsxs)("label", { style: { display: 'inline-flex', alignItems: 'center', gap: 6 }, children: [(0, jsx_runtime_1.jsx)("input", { type: "radio", name: "ca-source", checked: caSource === 'uploaded', onChange: () => setCaSource('uploaded') }), (0, jsx_runtime_1.jsx)("span", { children: "\u4F7F\u7528\u5DF2\u6709 CA" })] })] }) }), caSource === 'uploaded' ? ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: (0, jsx_runtime_1.jsxs)(Row, { label: "\u5F53\u524D CA", children: [status.https.uploadedCaInfo ? ((0, jsx_runtime_1.jsxs)("div", { style: styles.muted, children: [status.https.uploadedCaInfo.subject, "\uFF0C\u6709\u6548\u671F\u81F3 ", status.https.uploadedCaInfo.validTo, "\uFF1B\u6587\u4EF6\u5DF2\u4FDD\u5B58\uFF0C\u53EF\u76F4\u63A5\u4F7F\u7528\uFF0C\u65E0\u9700\u91CD\u65B0\u4E0A\u4F20\u3002"] })) : status.https.caInfo ? ((0, jsx_runtime_1.jsxs)("div", { style: styles.muted, children: [status.https.caInfo.subject, "\uFF0C\u6709\u6548\u671F\u81F3 ", status.https.caInfo.validTo, "\uFF1B\u6587\u4EF6\u4FDD\u5B58\u5728\u672C\u673A TLS \u76EE\u5F55\uFF0C\u53EF\u7EE7\u7EED\u7B7E\u53D1 leaf\u3002"] })) : (0, jsx_runtime_1.jsx)("div", { style: styles.muted, children: "\u5C1A\u672A\u4E0A\u4F20 CA\u3002" }), (0, jsx_runtime_1.jsx)("input", { id: "enh-ca-cert-file", type: "file", accept: ".pem,.crt,.cer", style: { display: 'none' }, onChange: (event) => setCaCertFile(event.target.files?.[0] ?? null) }), (0, jsx_runtime_1.jsx)("input", { id: "enh-ca-key-file", type: "file", accept: ".pem,.key", style: { display: 'none' }, onChange: (event) => setCaKeyFile(event.target.files?.[0] ?? null) }), (0, jsx_runtime_1.jsxs)("div", { style: styles.actions, children: [(0, jsx_runtime_1.jsx)("label", { htmlFor: "enh-ca-cert-file", style: { ...styles.button, display: 'inline-flex', alignItems: 'center' }, children: caCertFile ? `已选择:${caCertFile.name}` : status.https.uploadedCaInfo ? '更换 CA 证书' : '选择 CA 证书' }), (0, jsx_runtime_1.jsx)("label", { htmlFor: "enh-ca-key-file", style: { ...styles.button, display: 'inline-flex', alignItems: 'center' }, children: caKeyFile ? `已选择:${caKeyFile.name}` : status.https.uploadedCaInfo ? '更换 CA 私钥' : '选择 CA 私钥' })] }), caCertFile || caKeyFile ? (0, jsx_runtime_1.jsx)("div", { style: styles.muted, children: "\u4FDD\u5B58\u65F6\u624D\u4F1A\u5E94\u7528\uFF1B\u53EA\u4FDD\u7559\u6700\u65B0\u4E00\u4EFD\u6587\u4EF6\u3002" }) : null] }) })) : ((0, jsx_runtime_1.jsx)("div", { style: styles.muted, children: "\u4FDD\u5B58\u65F6\u4F1A\u5728\u63D2\u4EF6 TLS \u76EE\u5F55\u751F\u6210\u672C\u5730 CA\uFF0C\u5E76\u7528\u5B83\u7B7E\u53D1\u5F53\u524D\u76D1\u542C\u5730\u5740 + \u989D\u5916\u4E3B\u673A\u540D\u7684\u8BC1\u4E66\u3002" })), (0, jsx_runtime_1.jsxs)(Row, { label: "\u989D\u5916\u4E3B\u673A\u540D", children: [(0, jsx_runtime_1.jsx)("input", { style: styles.input, placeholder: "\u4F8B\u5982 dsh.lan, myhost.local\uFF08\u9017\u53F7\u5206\u9694\uFF09", value: httpsHostnames, onChange: (event) => setHttpsHostnames(event.target.value) }), (0, jsx_runtime_1.jsx)("div", { style: styles.muted, children: "\u76D1\u542C IP\u3001localhost \u4F1A\u81EA\u52A8\u5305\u542B\uFF1B\u8FD9\u91CC\u8865\u5145 DNS \u4E3B\u673A\u540D\u3002" })] }), status.https.caAvailable ? ((0, jsx_runtime_1.jsx)("div", { style: styles.actions, children: (0, jsx_runtime_1.jsx)("a", { style: { ...styles.button, textDecoration: 'none', display: 'inline-flex', alignItems: 'center' }, href: `${API}/tls/ca`, download: "dsh-enhancement-ca.crt", children: "\u4E0B\u8F7D CA \u8BC1\u4E66" }) })) : null, status.https.mode !== 'off' && status.https.cert ? ((0, jsx_runtime_1.jsxs)("div", { style: styles.muted, children: ["\u5F53\u524D\u8BC1\u4E66\uFF1A", status.https.cert.subject, "\uFF0C\u6709\u6548\u671F\u81F3 ", status.https.cert.validTo] })) : null, (0, jsx_runtime_1.jsx)("div", { style: styles.muted, children: "iOS \u5B89\u88C5 CA\uFF1ASafari \u6253\u5F00\u4E0B\u8F7D\u6587\u4EF6 \u2192 \u8BBE\u7F6E \u2192 \u901A\u7528 \u2192 VPN \u4E0E\u8BBE\u5907\u7BA1\u7406 \u2192 \u5B89\u88C5\u63CF\u8FF0\u6587\u4EF6 \u2192 \u8BBE\u7F6E \u2192 \u901A\u7528 \u2192 \u5173\u4E8E\u672C\u673A \u2192 \u8BC1\u4E66\u4FE1\u4EFB\u8BBE\u7F6E \u2192 \u5BF9 \u201CDSH Enhancement Local CA\u201D \u5F00\u542F\u5B8C\u5168\u4FE1\u4EFB\u3002" })] })) : null, httpsChoice === 'uploaded' ? ((0, jsx_runtime_1.jsx)("div", { children: (0, jsx_runtime_1.jsxs)(Row, { label: "\u5F53\u524D\u8BC1\u4E66", children: [status.https.cert && status.https.mode === 'uploaded' ? ((0, jsx_runtime_1.jsxs)("div", { style: styles.muted, children: [status.https.cert.subject, "\uFF0C\u6709\u6548\u671F\u81F3 ", status.https.cert.validTo, "\uFF1BSAN\uFF1A", status.https.cert.subjectAltName || '未提供', "\u3002\u8BC1\u4E66\u548C\u79C1\u94A5\u4FDD\u5B58\u5728\u672C\u673A TLS \u76EE\u5F55\u3002"] })) : (0, jsx_runtime_1.jsx)("div", { style: styles.muted, children: "\u5C1A\u672A\u4E0A\u4F20\u8BC1\u4E66\u3002" }), (0, jsx_runtime_1.jsx)("input", { id: "enh-cert-file", type: "file", accept: ".pem,.crt,.cer", style: { display: 'none' }, onChange: (event) => setCertFile(event.target.files?.[0] ?? null) }), (0, jsx_runtime_1.jsx)("input", { id: "enh-key-file", type: "file", accept: ".pem,.key", style: { display: 'none' }, onChange: (event) => setKeyFile(event.target.files?.[0] ?? null) }), (0, jsx_runtime_1.jsxs)("div", { style: styles.actions, children: [(0, jsx_runtime_1.jsx)("label", { htmlFor: "enh-cert-file", style: { ...styles.button, display: 'inline-flex', alignItems: 'center' }, children: certFile ? `已选择:${certFile.name}` : status.https.mode === 'uploaded' ? '更换证书' : '选择证书' }), (0, jsx_runtime_1.jsx)("label", { htmlFor: "enh-key-file", style: { ...styles.button, display: 'inline-flex', alignItems: 'center' }, children: keyFile ? `已选择:${keyFile.name}` : status.https.mode === 'uploaded' ? '更换私钥' : '选择私钥' })] }), certFile || keyFile ? (0, jsx_runtime_1.jsx)("div", { style: styles.muted, children: "\u4FDD\u5B58\u65F6\u624D\u4F1A\u5E94\u7528\uFF1B\u8BC1\u4E66 SAN \u5FC5\u987B\u8986\u76D6\u8BBF\u95EE\u5730\u5740/IP\uFF0C\u53EA\u4FDD\u7559\u6700\u65B0\u4E00\u4EFD\u6587\u4EF6\u3002" }) : null] }) })) : null] })) : (0, jsx_runtime_1.jsx)("div", { style: styles.muted, children: "\u542F\u7528\u7F51\u5173\u540E\u663E\u793A\u76D1\u542C\u5730\u5740\u3001\u7AEF\u53E3\u3001\u4F1A\u8BDD\u548C HTTPS \u8BBE\u7F6E\u3002" })] }), (0, jsx_runtime_1.jsxs)(Block, { order: 2, title: "\u8BBF\u95EE\u63A7\u5236", hint: status.passwordSet ? '已设置访问密码;修改后全部网关会话失效。' : '尚未设置密码;设置后才能启用网关。', dirty: accessDirty, busy: busy, saveLabel: "\u8BBE\u7F6E\u5BC6\u7801", onSave: savePassword, status: blockStatus.access, extra: (0, jsx_runtime_1.jsx)("button", { style: { ...styles.button, ...styles.danger }, disabled: busy || !status.passwordSet, onClick: logoutAll, children: "\u9000\u51FA\u5168\u90E8\u7F51\u5173\u4F1A\u8BDD" }), children: [(0, jsx_runtime_1.jsxs)("div", { className: "enh-grid2", children: [(0, jsx_runtime_1.jsx)(Row, { label: "\u65B0\u5BC6\u7801", children: (0, jsx_runtime_1.jsx)(PasswordInput, { value: newPassword, onChange: setNewPassword, autoComplete: "new-password", show: showNewPassword, onToggle: () => setShowNewPassword((value) => !value) }) }), (0, jsx_runtime_1.jsx)(Row, { label: "\u786E\u8BA4\u65B0\u5BC6\u7801", children: (0, jsx_runtime_1.jsx)(PasswordInput, { value: confirmPassword, onChange: setConfirmPassword, autoComplete: "new-password", show: showConfirmPassword, onToggle: () => setShowConfirmPassword((value) => !value) }) })] }), newPassword.length > 0 ? ((0, jsx_runtime_1.jsx)("div", { style: { ...styles.error, marginTop: -4 }, children: newPassword.length < 6 ? '新密码至少 6 个字符' : confirmPassword.length > 0 && newPassword !== confirmPassword ? '两次输入的新密码不一致' : '' })) : null, (0, jsx_runtime_1.jsx)("div", { style: styles.muted, children: "\u81F3\u5C11 6 \u4E2A\u5B57\u7B26\uFF1B\u5BC6\u7801\u7B49\u4EF7\u4E8E\u672C\u673A DSH \u7684\u8BBF\u95EE\u6743\u9650\uFF0C\u8BF7\u4F7F\u7528\u5F3A\u5BC6\u7801\u3002" })] }), (0, jsx_runtime_1.jsxs)(Block, { order: 3, title: "PWA\uFF08\u6DFB\u52A0\u5230\u684C\u9762 / \u4E3B\u5C4F\uFF09", hint: "\u542F\u7528\u540E\u63A5\u7BA1 /manifest.webmanifest\u3001\u56FE\u6807\u3001/sw.js \u4E0E\u5B89\u88C5\u63D0\u793A\uFF1BHTTP \u4E0B\u5B89\u88C5\u5165\u53E3\u548C Service Worker \u4E0D\u751F\u6548\u3002", dirty: pwaDirty, busy: busy, saveLabel: "\u4FDD\u5B58 PWA \u914D\u7F6E", onSave: savePwa, status: blockStatus.pwa, extra: (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("a", { style: { ...styles.button, textDecoration: 'none', display: 'inline-flex', alignItems: 'center' }, href: "/manifest.webmanifest", target: "_blank", rel: "noreferrer", children: "\u67E5\u770B manifest" }), (0, jsx_runtime_1.jsx)("a", { style: { ...styles.button, textDecoration: 'none', display: 'inline-flex', alignItems: 'center' }, href: "/sw.js", target: "_blank", rel: "noreferrer", children: "\u67E5\u770B Service Worker" })] }), children: [(0, jsx_runtime_1.jsx)(Row, { label: "\u542F\u7528 PWA", children: (0, jsx_runtime_1.jsxs)("label", { style: { display: 'inline-flex', alignItems: 'center', gap: 8 }, children: [(0, jsx_runtime_1.jsx)("input", { type: "checkbox", checked: pwaEnabled, onChange: (event) => setPwaEnabled(event.target.checked) }), (0, jsx_runtime_1.jsx)("span", { children: pwaEnabled ? '启用' : '停用' })] }) }), status.https.mode === 'off' ? (0, jsx_runtime_1.jsx)("div", { style: styles.error, children: "\u5F53\u524D\u662F HTTP\uFF1A\u5B89\u88C5\u63D0\u793A\u3001Service Worker\u3001Web Push \u90FD\u4E0D\u4F1A\u751F\u6548\u3002\u8BF7\u5148\u5728\u7F51\u5173\u914D\u7F6E\u542F\u7528 HTTPS\u3002" }) : null, status.pwa.error ? (0, jsx_runtime_1.jsxs)("div", { style: styles.error, children: ["PWA \u6CE8\u518C\u5931\u8D25\uFF1A", status.pwa.error] }) : null, pwaEnabled ? ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsxs)("div", { className: "enh-grid2", children: [(0, jsx_runtime_1.jsx)(Row, { label: "\u5E94\u7528\u540D", children: (0, jsx_runtime_1.jsx)("input", { style: styles.input, value: pwaName, onChange: (event) => setPwaName(event.target.value) }) }), (0, jsx_runtime_1.jsx)(Row, { label: "\u77ED\u540D\u79F0", children: (0, jsx_runtime_1.jsx)("input", { style: styles.input, value: pwaShortName, onChange: (event) => setPwaShortName(event.target.value) }) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "enh-grid3", children: [(0, jsx_runtime_1.jsx)(Row, { label: "\u663E\u793A\u6A21\u5F0F", children: (0, jsx_runtime_1.jsxs)("select", { style: styles.select, value: pwaDisplay, onChange: (event) => setPwaDisplay(event.target.value), children: [(0, jsx_runtime_1.jsx)("option", { value: "standalone", children: "standalone" }), (0, jsx_runtime_1.jsx)("option", { value: "fullscreen", children: "fullscreen" }), (0, jsx_runtime_1.jsx)("option", { value: "minimal-ui", children: "minimal-ui" })] }) }), (0, jsx_runtime_1.jsx)(Row, { label: "\u4E3B\u9898\u8272", children: (0, jsx_runtime_1.jsx)("input", { style: styles.input, value: pwaThemeColor, onChange: (event) => setPwaThemeColor(event.target.value) }) }), (0, jsx_runtime_1.jsx)(Row, { label: "\u80CC\u666F\u8272", children: (0, jsx_runtime_1.jsx)("input", { style: styles.input, value: pwaBackgroundColor, onChange: (event) => setPwaBackgroundColor(event.target.value) }) })] }), (0, jsx_runtime_1.jsx)("div", { style: { ...styles.label, marginTop: 8 }, children: "\u5206\u5E73\u53F0\u56FE\u6807" }), (0, jsx_runtime_1.jsx)("div", { style: styles.muted, children: "\u4E0A\u4F20\u540E\u7ACB\u5373\u751F\u6548\uFF1BAndroid \u81EA\u9002\u5E94\u56FE\u6807\u8BF7\u5355\u72EC\u4E0A\u4F20 maskable \u56FE\u3002\u5EFA\u8BAE 512 \u56FE\u6807\u5185\u5BB9\u7559\u5728\u4E2D\u5FC3 80% \u5B89\u5168\u533A\u3002" }), (0, jsx_runtime_1.jsx)("div", { className: "enh-icon-grid", children: [
|
|
591
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "enh-root", style: styles.wrap, children: [(0, jsx_runtime_1.jsx)("style", { children: CARD_CSS }), (0, jsx_runtime_1.jsxs)("div", { style: styles.header, children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h3", { style: styles.title, children: "\u63D2\u4EF6\u72B6\u6001" }), (0, jsx_runtime_1.jsx)("div", { style: styles.muted, children: "DSH \u589E\u5F3A\u7684\u7F51\u5173\u3001PWA\u3001\u901A\u77E5\u4E0E\u63A8\u9001\u914D\u7F6E\u3002" })] }), (0, jsx_runtime_1.jsxs)("span", { style: styles.pill, children: [(0, jsx_runtime_1.jsx)("span", { style: styles.dot }), "\u8FD0\u884C\u4E2D"] })] }), error ? (0, jsx_runtime_1.jsx)("div", { style: styles.error, children: error }) : null, notice ? (0, jsx_runtime_1.jsx)("div", { style: styles.success, children: notice }) : null, (0, jsx_runtime_1.jsxs)(Block, { order: 2, title: "\u7F51\u5173\u914D\u7F6E", hint: "\u5BC6\u7801\u7F51\u5173\u7684\u76D1\u542C\u3001\u8BBF\u95EE\u4E0E\u4F1A\u8BDD\u8BBE\u7F6E\uFF1BHTTPS \u914D\u7F6E\u4E5F\u5728\u672C\u5757\u5185\uFF0C\u4FDD\u5B58\u65F6\u6309\u987A\u5E8F\u5E94\u7528\u3002", dirty: gatewayDirty, busy: busy, saveLabel: "\u4FDD\u5B58\u5E76\u5E94\u7528", onSave: saveGateway, status: blockStatus.gateway, extra: (0, jsx_runtime_1.jsx)("button", { style: styles.button, disabled: busy || !status.passwordSet, onClick: restart, children: "\u91CD\u542F\u7F51\u5173" }), children: [(0, jsx_runtime_1.jsxs)(InlineRow, { label: "DSH \u4E0A\u6E38", children: [(0, jsx_runtime_1.jsx)("code", { style: { ...styles.code, flex: '1 1 260px', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }, children: status.dshAccessUrl || status.dshOrigin }), (0, jsx_runtime_1.jsx)("button", { style: styles.iconButton, title: "\u590D\u5236", "aria-label": "\u590D\u5236 DSH \u4E0A\u6E38\u5730\u5740\uFF08\u542B token\uFF09", onClick: () => copyToClipboard(status.dshAccessUrl || status.dshOrigin), children: (0, jsx_runtime_1.jsx)(CopyIcon, {}) }), (0, jsx_runtime_1.jsx)("a", { style: styles.iconButton, title: "\u6253\u5F00", "aria-label": "\u6253\u5F00 DSH \u4E0A\u6E38\uFF08\u542B token\uFF09", href: status.dshAccessUrl || status.dshOrigin, target: "_blank", rel: "noreferrer", children: (0, jsx_runtime_1.jsx)(OpenIcon, {}) })] }), (0, jsx_runtime_1.jsx)(InlineRow, { label: "\u7F51\u5173\u5730\u5740", children: status.url ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("code", { style: { ...styles.code, flex: '1 1 260px', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }, children: status.url }), (0, jsx_runtime_1.jsx)("button", { style: styles.iconButton, title: "\u590D\u5236", "aria-label": "\u590D\u5236\u7F51\u5173\u5730\u5740", onClick: () => copyToClipboard(status.url), children: (0, jsx_runtime_1.jsx)(CopyIcon, {}) }), (0, jsx_runtime_1.jsx)("a", { style: styles.iconButton, title: "\u6253\u5F00", "aria-label": "\u6253\u5F00\u7F51\u5173", href: status.url, target: "_blank", rel: "noreferrer", children: (0, jsx_runtime_1.jsx)(OpenIcon, {}) })] })) : (0, jsx_runtime_1.jsx)("span", { style: styles.muted, children: "\u672A\u8FD0\u884C\uFF1B\u8BBE\u7F6E\u5BC6\u7801\u5E76\u542F\u7528\u540E\u751F\u6210" }) }), !status.dshSecretReady ? (0, jsx_runtime_1.jsx)("div", { style: styles.error, children: "\u7F51\u5173\u4F1A\u8BDD\u6CE8\u5165\u4E0D\u53EF\u7528\uFF1A\u65E0\u6CD5\u8BFB\u53D6 DSH \u6D4F\u89C8\u5668\u7B7E\u540D\u5BC6\u94A5\uFF0C\u7F51\u5173\u4EE3\u7406\u4F1A\u8FD4\u56DE 401\u3002" }) : null, status.error ? (0, jsx_runtime_1.jsxs)("div", { style: styles.error, children: ["\u6700\u8FD1\u9519\u8BEF\uFF1A", status.error] }) : null, (0, jsx_runtime_1.jsx)(Row, { label: "\u542F\u7528\u7F51\u5173", children: (0, jsx_runtime_1.jsxs)("label", { style: { display: 'inline-flex', alignItems: 'center', gap: 8 }, children: [(0, jsx_runtime_1.jsx)("input", { type: "checkbox", checked: enabled, disabled: !status.passwordSet, onChange: (event) => setEnabled(event.target.checked) }), (0, jsx_runtime_1.jsx)("span", { children: !status.passwordSet ? '请先设置密码' : enabled ? '启用' : '停用' })] }) }), enabled ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { className: "enh-grid3", children: [(0, jsx_runtime_1.jsxs)(Row, { label: "\u76D1\u542C\u5730\u5740", children: [(0, jsx_runtime_1.jsx)("select", { style: styles.select, value: bindHost, onChange: (event) => setBindHost(event.target.value), children: interfaceOptions.map((option) => ((0, jsx_runtime_1.jsx)("option", { value: option.value, children: option.label }, option.value))) }), currentInterface ? (0, jsx_runtime_1.jsxs)("div", { style: styles.muted, children: ["\u5F53\u524D\uFF1A", currentInterface.address, "\uFF08", currentInterface.name, "\uFF09"] }) : null] }), (0, jsx_runtime_1.jsx)(Row, { label: "\u7AEF\u53E3", children: (0, jsx_runtime_1.jsx)("input", { style: styles.input, value: port, inputMode: "numeric", onChange: (event) => setPort(event.target.value) }) }), (0, jsx_runtime_1.jsxs)(Row, { label: "\u4F1A\u8BDD\u6709\u6548\u671F", children: [(0, jsx_runtime_1.jsx)("input", { style: styles.input, value: sessionDays, inputMode: "numeric", onChange: (event) => setSessionDays(event.target.value) }), (0, jsx_runtime_1.jsx)("div", { style: styles.muted, children: "\u5929\uFF1B\u5BC6\u7801\u4FEE\u6539\u540E\u5168\u90E8\u4F1A\u8BDD\u5931\u6548\u3002" })] })] }), (0, jsx_runtime_1.jsx)(Row, { label: "\u767B\u5F55\u5931\u8D25\u8282\u6D41", children: (0, jsx_runtime_1.jsxs)("label", { style: { display: 'inline-flex', alignItems: 'center', gap: 8 }, children: [(0, jsx_runtime_1.jsx)("input", { type: "checkbox", checked: loginThrottle, onChange: (event) => setLoginThrottle(event.target.checked) }), (0, jsx_runtime_1.jsx)("span", { children: loginThrottle ? '开启(5 次 / 10 分钟 → 锁 1 分钟)' : '关闭(不推荐)' })] }) }), (0, jsx_runtime_1.jsx)(Row, { label: "HTTPS \u6A21\u5F0F", children: (0, jsx_runtime_1.jsxs)("select", { style: styles.select, value: httpsChoice, onChange: (event) => setHttpsChoice(event.target.value), children: [(0, jsx_runtime_1.jsx)("option", { value: "off", children: "\u5173\u95ED HTTPS" }), (0, jsx_runtime_1.jsx)("option", { value: "selfsigned", children: "\u4F7F\u7528 CA \u8BC1\u4E66\u81EA\u7B7E\u53D1" }), (0, jsx_runtime_1.jsx)("option", { value: "uploaded", children: "\u4F7F\u7528\u5DF2\u6709\u8BC1\u4E66" })] }) }), httpsChoice === 'off' ? ((0, jsx_runtime_1.jsx)("div", { style: styles.muted, children: "\u5173\u95ED\u540E\u6D4F\u89C8\u5668\u4F1A\u63D0\u793A\u4E0D\u5B89\u5168\uFF0CPWA \u5B89\u88C5\u3001Service Worker \u4E0E Web Push \u90FD\u4E0D\u53EF\u7528\u3002\u4FDD\u5B58\u540E\u751F\u6548\u3002" })) : null, httpsChoice === 'selfsigned' ? ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)(Row, { label: "CA \u6765\u6E90", children: (0, jsx_runtime_1.jsxs)("div", { style: { display: 'flex', flexWrap: 'wrap', gap: 16 }, children: [(0, jsx_runtime_1.jsxs)("label", { style: { display: 'inline-flex', alignItems: 'center', gap: 6 }, children: [(0, jsx_runtime_1.jsx)("input", { type: "radio", name: "ca-source", checked: caSource === 'generated', onChange: () => setCaSource('generated') }), (0, jsx_runtime_1.jsx)("span", { children: "\u81EA\u52A8\u751F\u6210 CA" })] }), (0, jsx_runtime_1.jsxs)("label", { style: { display: 'inline-flex', alignItems: 'center', gap: 6 }, children: [(0, jsx_runtime_1.jsx)("input", { type: "radio", name: "ca-source", checked: caSource === 'uploaded', onChange: () => setCaSource('uploaded') }), (0, jsx_runtime_1.jsx)("span", { children: "\u4F7F\u7528\u5DF2\u6709 CA" })] })] }) }), caSource === 'uploaded' ? ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: (0, jsx_runtime_1.jsxs)(Row, { label: "\u5F53\u524D CA", children: [status.https.uploadedCaInfo ? ((0, jsx_runtime_1.jsxs)("div", { style: styles.muted, children: [status.https.uploadedCaInfo.subject, "\uFF0C\u6709\u6548\u671F\u81F3 ", status.https.uploadedCaInfo.validTo, "\uFF1B\u6587\u4EF6\u5DF2\u4FDD\u5B58\uFF0C\u53EF\u76F4\u63A5\u4F7F\u7528\uFF0C\u65E0\u9700\u91CD\u65B0\u4E0A\u4F20\u3002"] })) : status.https.caInfo ? ((0, jsx_runtime_1.jsxs)("div", { style: styles.muted, children: [status.https.caInfo.subject, "\uFF0C\u6709\u6548\u671F\u81F3 ", status.https.caInfo.validTo, "\uFF1B\u6587\u4EF6\u4FDD\u5B58\u5728\u672C\u673A TLS \u76EE\u5F55\uFF0C\u53EF\u7EE7\u7EED\u7B7E\u53D1 leaf\u3002"] })) : (0, jsx_runtime_1.jsx)("div", { style: styles.muted, children: "\u5C1A\u672A\u4E0A\u4F20 CA\u3002" }), (0, jsx_runtime_1.jsx)("input", { id: "enh-ca-cert-file", type: "file", accept: ".pem,.crt,.cer", style: { display: 'none' }, onChange: (event) => setCaCertFile(event.target.files?.[0] ?? null) }), (0, jsx_runtime_1.jsx)("input", { id: "enh-ca-key-file", type: "file", accept: ".pem,.key", style: { display: 'none' }, onChange: (event) => setCaKeyFile(event.target.files?.[0] ?? null) }), (0, jsx_runtime_1.jsxs)("div", { style: styles.actions, children: [(0, jsx_runtime_1.jsx)("label", { htmlFor: "enh-ca-cert-file", style: { ...styles.button, display: 'inline-flex', alignItems: 'center' }, children: caCertFile ? `已选择:${caCertFile.name}` : status.https.uploadedCaInfo ? '更换 CA 证书' : '选择 CA 证书' }), (0, jsx_runtime_1.jsx)("label", { htmlFor: "enh-ca-key-file", style: { ...styles.button, display: 'inline-flex', alignItems: 'center' }, children: caKeyFile ? `已选择:${caKeyFile.name}` : status.https.uploadedCaInfo ? '更换 CA 私钥' : '选择 CA 私钥' })] }), caCertFile || caKeyFile ? (0, jsx_runtime_1.jsx)("div", { style: styles.muted, children: "\u4FDD\u5B58\u65F6\u624D\u4F1A\u5E94\u7528\uFF1B\u53EA\u4FDD\u7559\u6700\u65B0\u4E00\u4EFD\u6587\u4EF6\u3002" }) : null] }) })) : ((0, jsx_runtime_1.jsx)("div", { style: styles.muted, children: "\u4FDD\u5B58\u65F6\u4F1A\u5728\u63D2\u4EF6 TLS \u76EE\u5F55\u751F\u6210\u672C\u5730 CA\uFF0C\u5E76\u7528\u5B83\u7B7E\u53D1\u5F53\u524D\u76D1\u542C\u5730\u5740 + \u989D\u5916\u4E3B\u673A\u540D\u7684\u8BC1\u4E66\u3002" })), (0, jsx_runtime_1.jsxs)(Row, { label: "\u989D\u5916\u4E3B\u673A\u540D", children: [(0, jsx_runtime_1.jsx)("input", { style: styles.input, placeholder: "\u4F8B\u5982 dsh.lan, myhost.local\uFF08\u9017\u53F7\u5206\u9694\uFF09", value: httpsHostnames, onChange: (event) => setHttpsHostnames(event.target.value) }), (0, jsx_runtime_1.jsx)("div", { style: styles.muted, children: "\u76D1\u542C IP\u3001localhost \u4F1A\u81EA\u52A8\u5305\u542B\uFF1B\u8FD9\u91CC\u8865\u5145 DNS \u4E3B\u673A\u540D\u3002" })] }), status.https.caAvailable ? ((0, jsx_runtime_1.jsx)("div", { style: styles.actions, children: (0, jsx_runtime_1.jsx)("a", { style: { ...styles.button, textDecoration: 'none', display: 'inline-flex', alignItems: 'center' }, href: `${API}/tls/ca`, download: "dsh-enhancement-ca.crt", children: "\u4E0B\u8F7D CA \u8BC1\u4E66" }) })) : null, status.https.mode !== 'off' && status.https.cert ? ((0, jsx_runtime_1.jsxs)("div", { style: styles.muted, children: ["\u5F53\u524D\u8BC1\u4E66\uFF1A", status.https.cert.subject, "\uFF0C\u6709\u6548\u671F\u81F3 ", status.https.cert.validTo] })) : null, (0, jsx_runtime_1.jsx)("div", { style: styles.muted, children: "iOS \u5B89\u88C5 CA\uFF1ASafari \u6253\u5F00\u4E0B\u8F7D\u6587\u4EF6 \u2192 \u8BBE\u7F6E \u2192 \u901A\u7528 \u2192 VPN \u4E0E\u8BBE\u5907\u7BA1\u7406 \u2192 \u5B89\u88C5\u63CF\u8FF0\u6587\u4EF6 \u2192 \u8BBE\u7F6E \u2192 \u901A\u7528 \u2192 \u5173\u4E8E\u672C\u673A \u2192 \u8BC1\u4E66\u4FE1\u4EFB\u8BBE\u7F6E \u2192 \u5BF9 \u201CDSH Enhancement Local CA\u201D \u5F00\u542F\u5B8C\u5168\u4FE1\u4EFB\u3002" })] })) : null, httpsChoice === 'uploaded' ? ((0, jsx_runtime_1.jsx)("div", { children: (0, jsx_runtime_1.jsxs)(Row, { label: "\u5F53\u524D\u8BC1\u4E66", children: [status.https.cert && status.https.mode === 'uploaded' ? ((0, jsx_runtime_1.jsxs)("div", { style: styles.muted, children: [status.https.cert.subject, "\uFF0C\u6709\u6548\u671F\u81F3 ", status.https.cert.validTo, "\uFF1BSAN\uFF1A", status.https.cert.subjectAltName || '未提供', "\u3002\u8BC1\u4E66\u548C\u79C1\u94A5\u4FDD\u5B58\u5728\u672C\u673A TLS \u76EE\u5F55\u3002"] })) : (0, jsx_runtime_1.jsx)("div", { style: styles.muted, children: "\u5C1A\u672A\u4E0A\u4F20\u8BC1\u4E66\u3002" }), (0, jsx_runtime_1.jsx)("input", { id: "enh-cert-file", type: "file", accept: ".pem,.crt,.cer", style: { display: 'none' }, onChange: (event) => setCertFile(event.target.files?.[0] ?? null) }), (0, jsx_runtime_1.jsx)("input", { id: "enh-key-file", type: "file", accept: ".pem,.key", style: { display: 'none' }, onChange: (event) => setKeyFile(event.target.files?.[0] ?? null) }), (0, jsx_runtime_1.jsxs)("div", { style: styles.actions, children: [(0, jsx_runtime_1.jsx)("label", { htmlFor: "enh-cert-file", style: { ...styles.button, display: 'inline-flex', alignItems: 'center' }, children: certFile ? `已选择:${certFile.name}` : status.https.mode === 'uploaded' ? '更换证书' : '选择证书' }), (0, jsx_runtime_1.jsx)("label", { htmlFor: "enh-key-file", style: { ...styles.button, display: 'inline-flex', alignItems: 'center' }, children: keyFile ? `已选择:${keyFile.name}` : status.https.mode === 'uploaded' ? '更换私钥' : '选择私钥' })] }), certFile || keyFile ? (0, jsx_runtime_1.jsx)("div", { style: styles.muted, children: "\u4FDD\u5B58\u65F6\u624D\u4F1A\u5E94\u7528\uFF1B\u8BC1\u4E66 SAN \u5FC5\u987B\u8986\u76D6\u8BBF\u95EE\u5730\u5740/IP\uFF0C\u53EA\u4FDD\u7559\u6700\u65B0\u4E00\u4EFD\u6587\u4EF6\u3002" }) : null] }) })) : null] })) : (0, jsx_runtime_1.jsx)("div", { style: styles.muted, children: "\u542F\u7528\u7F51\u5173\u540E\u663E\u793A\u76D1\u542C\u5730\u5740\u3001\u7AEF\u53E3\u3001\u4F1A\u8BDD\u548C HTTPS \u8BBE\u7F6E\u3002" })] }), (0, jsx_runtime_1.jsxs)(Block, { order: 1, title: "\u8BBF\u95EE\u63A7\u5236", hint: status.passwordSet ? '已设置访问密码;修改后全部网关会话失效。' : '尚未设置密码;设置后才能启用网关。', dirty: accessDirty, busy: busy, saveLabel: "\u8BBE\u7F6E\u5BC6\u7801", onSave: savePassword, status: blockStatus.access, extra: (0, jsx_runtime_1.jsx)("button", { style: { ...styles.button, ...styles.danger }, disabled: busy || !status.passwordSet, onClick: logoutAll, children: "\u9000\u51FA\u5168\u90E8\u7F51\u5173\u4F1A\u8BDD" }), children: [(0, jsx_runtime_1.jsxs)("div", { className: "enh-grid2", children: [(0, jsx_runtime_1.jsx)(Row, { label: "\u65B0\u5BC6\u7801", children: (0, jsx_runtime_1.jsx)(PasswordInput, { value: newPassword, onChange: setNewPassword, autoComplete: "new-password", show: showNewPassword, onToggle: () => setShowNewPassword((value) => !value) }) }), (0, jsx_runtime_1.jsx)(Row, { label: "\u786E\u8BA4\u65B0\u5BC6\u7801", children: (0, jsx_runtime_1.jsx)(PasswordInput, { value: confirmPassword, onChange: setConfirmPassword, autoComplete: "new-password", show: showConfirmPassword, onToggle: () => setShowConfirmPassword((value) => !value) }) })] }), newPassword.length > 0 ? ((0, jsx_runtime_1.jsx)("div", { style: { ...styles.error, marginTop: -4 }, children: newPassword.length < 6 ? '新密码至少 6 个字符' : confirmPassword.length > 0 && newPassword !== confirmPassword ? '两次输入的新密码不一致' : '' })) : null, (0, jsx_runtime_1.jsx)("div", { style: styles.muted, children: "\u81F3\u5C11 6 \u4E2A\u5B57\u7B26\uFF1B\u5BC6\u7801\u7B49\u4EF7\u4E8E\u672C\u673A DSH \u7684\u8BBF\u95EE\u6743\u9650\uFF0C\u8BF7\u4F7F\u7528\u5F3A\u5BC6\u7801\u3002" })] }), (0, jsx_runtime_1.jsxs)(Block, { order: 3, title: "PWA\uFF08\u6DFB\u52A0\u5230\u684C\u9762 / \u4E3B\u5C4F\uFF09", hint: "\u542F\u7528\u540E\u63A5\u7BA1 /manifest.webmanifest\u3001\u56FE\u6807\u3001/sw.js \u4E0E\u5B89\u88C5\u63D0\u793A\uFF1BHTTP \u4E0B\u5B89\u88C5\u5165\u53E3\u548C Service Worker \u4E0D\u751F\u6548\u3002", dirty: pwaDirty, busy: busy, saveLabel: "\u4FDD\u5B58 PWA \u914D\u7F6E", onSave: savePwa, status: blockStatus.pwa, extra: (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("a", { style: { ...styles.button, textDecoration: 'none', display: 'inline-flex', alignItems: 'center' }, href: "/manifest.webmanifest", target: "_blank", rel: "noreferrer", children: "\u67E5\u770B manifest" }), (0, jsx_runtime_1.jsx)("a", { style: { ...styles.button, textDecoration: 'none', display: 'inline-flex', alignItems: 'center' }, href: "/sw.js", target: "_blank", rel: "noreferrer", children: "\u67E5\u770B Service Worker" })] }), children: [(0, jsx_runtime_1.jsx)(Row, { label: "\u542F\u7528 PWA", children: (0, jsx_runtime_1.jsxs)("label", { style: { display: 'inline-flex', alignItems: 'center', gap: 8 }, children: [(0, jsx_runtime_1.jsx)("input", { type: "checkbox", checked: pwaEnabled, onChange: (event) => setPwaEnabled(event.target.checked) }), (0, jsx_runtime_1.jsx)("span", { children: pwaEnabled ? '启用' : '停用' })] }) }), status.https.mode === 'off' ? (0, jsx_runtime_1.jsx)("div", { style: styles.error, children: "\u5F53\u524D\u662F HTTP\uFF1A\u5B89\u88C5\u63D0\u793A\u3001Service Worker\u3001Web Push \u90FD\u4E0D\u4F1A\u751F\u6548\u3002\u8BF7\u5148\u5728\u7F51\u5173\u914D\u7F6E\u542F\u7528 HTTPS\u3002" }) : null, status.pwa.error ? (0, jsx_runtime_1.jsxs)("div", { style: styles.error, children: ["PWA \u6CE8\u518C\u5931\u8D25\uFF1A", status.pwa.error] }) : null, pwaEnabled ? ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsxs)("div", { className: "enh-grid2", children: [(0, jsx_runtime_1.jsx)(Row, { label: "\u5E94\u7528\u540D", children: (0, jsx_runtime_1.jsx)("input", { style: styles.input, value: pwaName, onChange: (event) => setPwaName(event.target.value) }) }), (0, jsx_runtime_1.jsx)(Row, { label: "\u77ED\u540D\u79F0", children: (0, jsx_runtime_1.jsx)("input", { style: styles.input, value: pwaShortName, onChange: (event) => setPwaShortName(event.target.value) }) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "enh-grid3", children: [(0, jsx_runtime_1.jsx)(Row, { label: "\u663E\u793A\u6A21\u5F0F", children: (0, jsx_runtime_1.jsxs)("select", { style: styles.select, value: pwaDisplay, onChange: (event) => setPwaDisplay(event.target.value), children: [(0, jsx_runtime_1.jsx)("option", { value: "standalone", children: "standalone" }), (0, jsx_runtime_1.jsx)("option", { value: "fullscreen", children: "fullscreen" }), (0, jsx_runtime_1.jsx)("option", { value: "minimal-ui", children: "minimal-ui" })] }) }), (0, jsx_runtime_1.jsx)(Row, { label: "\u4E3B\u9898\u8272", children: (0, jsx_runtime_1.jsx)("input", { style: styles.input, value: pwaThemeColor, onChange: (event) => setPwaThemeColor(event.target.value) }) }), (0, jsx_runtime_1.jsx)(Row, { label: "\u80CC\u666F\u8272", children: (0, jsx_runtime_1.jsx)("input", { style: styles.input, value: pwaBackgroundColor, onChange: (event) => setPwaBackgroundColor(event.target.value) }) })] }), (0, jsx_runtime_1.jsx)("div", { style: { ...styles.label, marginTop: 8 }, children: "\u5206\u5E73\u53F0\u56FE\u6807" }), (0, jsx_runtime_1.jsx)("div", { style: styles.muted, children: "\u4E0A\u4F20\u540E\u7ACB\u5373\u751F\u6548\uFF1BAndroid \u81EA\u9002\u5E94\u56FE\u6807\u8BF7\u5355\u72EC\u4E0A\u4F20 maskable \u56FE\u3002\u5EFA\u8BAE 512 \u56FE\u6807\u5185\u5BB9\u7559\u5728\u4E2D\u5FC3 80% \u5B89\u5168\u533A\u3002" }), (0, jsx_runtime_1.jsx)("div", { className: "enh-icon-grid", children: [
|
|
591
592
|
['icon-192.png', '通用 192 × 192', 'any'],
|
|
592
593
|
['icon-512.png', '通用 512 × 512', 'any'],
|
|
593
594
|
['icon-maskable-512.png', 'Android 自适应 512 × 512', 'maskable'],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-enhancement",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "DSH 增强套件:远程安全访问网关(密码/HTTPS)、PWA 安装与图标、桌面通知与 Web Push、设置页。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -26,6 +26,10 @@
|
|
|
26
26
|
"README.md",
|
|
27
27
|
"README.en.md"
|
|
28
28
|
],
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "tsc -p tsconfig.json && tsc -p tsconfig.client.json && node scripts/build-client.mjs",
|
|
31
|
+
"verify": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.client.json --noEmit"
|
|
32
|
+
},
|
|
29
33
|
"dsh": {
|
|
30
34
|
"bundle": {
|
|
31
35
|
"patch": "./cordis.patch.yml"
|
|
@@ -72,9 +76,5 @@
|
|
|
72
76
|
"web-push": "^3.6.7",
|
|
73
77
|
"dsh-web-mobile": "^3.1.0",
|
|
74
78
|
"dsh-vertical-layout": "^1.1.0"
|
|
75
|
-
},
|
|
76
|
-
"scripts": {
|
|
77
|
-
"build": "tsc -p tsconfig.json && tsc -p tsconfig.client.json && node scripts/build-client.mjs",
|
|
78
|
-
"verify": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.client.json --noEmit"
|
|
79
79
|
}
|
|
80
|
-
}
|
|
80
|
+
}
|
package/src/client/index.tsx
CHANGED
|
@@ -190,7 +190,9 @@ const CARD_CSS = `
|
|
|
190
190
|
.enh-root input[type="checkbox"] { appearance:none; flex:none; width:36px; height:20px; border-radius:999px; background:var(--dsw-alias-border-l2, #d0d5dd); position:relative; cursor:pointer; transition:background .15s; margin:0; }
|
|
191
191
|
.enh-root input[type="checkbox"]::after { content:""; position:absolute; top:2px; left:2px; width:16px; height:16px; border-radius:50%; background:#fff; box-shadow:0 1px 2px rgba(0,0,0,.2); transition:transform .15s; }
|
|
192
192
|
.enh-root input[type="checkbox"]:checked { background:var(--dsw-alias-state-business-primary, #4d8dff); }
|
|
193
|
+
|
|
193
194
|
.enh-root input[type="checkbox"]:checked::after { transform:translateX(16px); }
|
|
195
|
+
.enh-root input[type="checkbox"]:disabled { opacity:.45; cursor:not-allowed; }
|
|
194
196
|
.enh-root input[type="checkbox"]:focus-visible { outline:2px solid var(--dsw-alias-state-business-primary, #4d8dff); outline-offset:2px; }
|
|
195
197
|
|
|
196
198
|
.enh-root .enh-block-head { display:flex; align-items:flex-start; justify-content:space-between; gap:12px; }
|
|
@@ -574,7 +576,7 @@ function Card(): React.ReactElement {
|
|
|
574
576
|
await restartGateway()
|
|
575
577
|
}, '网关已重启')
|
|
576
578
|
|
|
577
|
-
const copyToClipboard = (text: string
|
|
579
|
+
const copyToClipboard = (text: string) => {
|
|
578
580
|
setError(null)
|
|
579
581
|
const fallback = () => {
|
|
580
582
|
const area = document.createElement('textarea')
|
|
@@ -585,7 +587,6 @@ function Card(): React.ReactElement {
|
|
|
585
587
|
area.select()
|
|
586
588
|
try {
|
|
587
589
|
document.execCommand('copy')
|
|
588
|
-
setNotice(`${label}已复制`)
|
|
589
590
|
} catch (err) {
|
|
590
591
|
setError(err instanceof Error ? err.message : String(err))
|
|
591
592
|
} finally {
|
|
@@ -593,7 +594,7 @@ function Card(): React.ReactElement {
|
|
|
593
594
|
}
|
|
594
595
|
}
|
|
595
596
|
if (navigator.clipboard?.writeText) {
|
|
596
|
-
void navigator.clipboard.writeText(text).
|
|
597
|
+
void navigator.clipboard.writeText(text).catch(fallback)
|
|
597
598
|
} else {
|
|
598
599
|
fallback()
|
|
599
600
|
}
|
|
@@ -767,7 +768,7 @@ function Card(): React.ReactElement {
|
|
|
767
768
|
{notice ? <div style={styles.success}>{notice}</div> : null}
|
|
768
769
|
|
|
769
770
|
<Block
|
|
770
|
-
order={
|
|
771
|
+
order={2}
|
|
771
772
|
title="网关配置"
|
|
772
773
|
hint="密码网关的监听、访问与会话设置;HTTPS 配置也在本块内,保存时按顺序应用。"
|
|
773
774
|
dirty={gatewayDirty}
|
|
@@ -779,14 +780,14 @@ function Card(): React.ReactElement {
|
|
|
779
780
|
>
|
|
780
781
|
<InlineRow label="DSH 上游">
|
|
781
782
|
<code style={{ ...styles.code, flex: '1 1 260px', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{status.dshAccessUrl || status.dshOrigin}</code>
|
|
782
|
-
<button style={styles.iconButton} title="复制" aria-label="复制 DSH 上游地址(含 token)" onClick={() => copyToClipboard(status.dshAccessUrl || status.dshOrigin
|
|
783
|
+
<button style={styles.iconButton} title="复制" aria-label="复制 DSH 上游地址(含 token)" onClick={() => copyToClipboard(status.dshAccessUrl || status.dshOrigin)}><CopyIcon /></button>
|
|
783
784
|
<a style={styles.iconButton} title="打开" aria-label="打开 DSH 上游(含 token)" href={status.dshAccessUrl || status.dshOrigin} target="_blank" rel="noreferrer"><OpenIcon /></a>
|
|
784
785
|
</InlineRow>
|
|
785
786
|
<InlineRow label="网关地址">
|
|
786
787
|
{status.url ? (
|
|
787
788
|
<>
|
|
788
789
|
<code style={{ ...styles.code, flex: '1 1 260px', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{status.url}</code>
|
|
789
|
-
<button style={styles.iconButton} title="复制" aria-label="复制网关地址" onClick={() => copyToClipboard(status.url
|
|
790
|
+
<button style={styles.iconButton} title="复制" aria-label="复制网关地址" onClick={() => copyToClipboard(status.url!)}><CopyIcon /></button>
|
|
790
791
|
<a style={styles.iconButton} title="打开" aria-label="打开网关" href={status.url} target="_blank" rel="noreferrer"><OpenIcon /></a>
|
|
791
792
|
</>
|
|
792
793
|
) : <span style={styles.muted}>未运行;设置密码并启用后生成</span>}
|
|
@@ -796,8 +797,8 @@ function Card(): React.ReactElement {
|
|
|
796
797
|
|
|
797
798
|
<Row label="启用网关">
|
|
798
799
|
<label style={{ display: 'inline-flex', alignItems: 'center', gap: 8 }}>
|
|
799
|
-
<input type="checkbox" checked={enabled} onChange={(event) => setEnabled(event.target.checked)} />
|
|
800
|
-
<span>{enabled ? '启用' : '停用'}</span>
|
|
800
|
+
<input type="checkbox" checked={enabled} disabled={!status.passwordSet} onChange={(event) => setEnabled(event.target.checked)} />
|
|
801
|
+
<span>{!status.passwordSet ? '请先设置密码' : enabled ? '启用' : '停用'}</span>
|
|
801
802
|
</label>
|
|
802
803
|
</Row>
|
|
803
804
|
|
|
@@ -910,7 +911,7 @@ function Card(): React.ReactElement {
|
|
|
910
911
|
</Block>
|
|
911
912
|
|
|
912
913
|
<Block
|
|
913
|
-
order={
|
|
914
|
+
order={1}
|
|
914
915
|
title="访问控制"
|
|
915
916
|
hint={status.passwordSet ? '已设置访问密码;修改后全部网关会话失效。' : '尚未设置密码;设置后才能启用网关。'}
|
|
916
917
|
dirty={accessDirty}
|