mn-ws 0.1.0 → 0.2.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/assets/groups.json +10 -0
- package/assets/mn-ws/SKILL.md +55 -0
- package/assets/mn-ws/hooks.json +14 -0
- package/assets/mn-ws/install/inject-root.mjs +26 -0
- package/bin/ws.mjs +13 -0
- package/dist/core/auth.d.ts +194 -0
- package/dist/core/auth.d.ts.map +1 -0
- package/dist/core/auth.js +331 -0
- package/dist/core/auth.js.map +1 -0
- package/dist/core/brand.d.ts +11 -0
- package/dist/core/brand.d.ts.map +1 -0
- package/dist/core/brand.js +7 -0
- package/dist/core/brand.js.map +1 -0
- package/dist/core/bridge.d.ts +106 -0
- package/dist/core/bridge.d.ts.map +1 -0
- package/dist/core/bridge.js +308 -0
- package/dist/core/bridge.js.map +1 -0
- package/dist/core/config.d.ts +174 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +417 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/controller.d.ts +64 -0
- package/dist/core/controller.d.ts.map +1 -0
- package/dist/core/controller.js +100 -0
- package/dist/core/controller.js.map +1 -0
- package/dist/core/errors/index.d.ts +30 -0
- package/dist/core/errors/index.d.ts.map +1 -0
- package/dist/core/errors/index.js +88 -0
- package/dist/core/errors/index.js.map +1 -0
- package/dist/core/open.d.ts +3 -0
- package/dist/core/open.d.ts.map +1 -0
- package/dist/core/open.js +27 -0
- package/dist/core/open.js.map +1 -0
- package/dist/core/paths.d.ts +15 -0
- package/dist/core/paths.d.ts.map +1 -0
- package/dist/core/paths.js +50 -0
- package/dist/core/paths.js.map +1 -0
- package/dist/core/serve-port.d.ts +73 -0
- package/dist/core/serve-port.d.ts.map +1 -0
- package/dist/core/serve-port.js +134 -0
- package/dist/core/serve-port.js.map +1 -0
- package/dist/core/session.d.ts +78 -0
- package/dist/core/session.d.ts.map +1 -0
- package/dist/core/session.js +124 -0
- package/dist/core/session.js.map +1 -0
- package/dist/core/skills/bus.d.ts +29 -0
- package/dist/core/skills/bus.d.ts.map +1 -0
- package/dist/core/skills/bus.js +114 -0
- package/dist/core/skills/bus.js.map +1 -0
- package/dist/core/skills/get.d.ts +17 -0
- package/dist/core/skills/get.d.ts.map +1 -0
- package/dist/core/skills/get.js +87 -0
- package/dist/core/skills/get.js.map +1 -0
- package/dist/core/skills/handlers.d.ts +22 -0
- package/dist/core/skills/handlers.d.ts.map +1 -0
- package/dist/core/skills/handlers.js +86 -0
- package/dist/core/skills/handlers.js.map +1 -0
- package/dist/core/skills/index.d.ts +47 -0
- package/dist/core/skills/index.d.ts.map +1 -0
- package/dist/core/skills/index.js +268 -0
- package/dist/core/skills/index.js.map +1 -0
- package/dist/core/skills/install.d.ts +18 -0
- package/dist/core/skills/install.d.ts.map +1 -0
- package/dist/core/skills/install.js +123 -0
- package/dist/core/skills/install.js.map +1 -0
- package/dist/core/skills/text-util.d.ts +2 -0
- package/dist/core/skills/text-util.d.ts.map +1 -0
- package/dist/core/skills/text-util.js +14 -0
- package/dist/core/skills/text-util.js.map +1 -0
- package/dist/core/store.d.ts +29 -0
- package/dist/core/store.d.ts.map +1 -0
- package/dist/core/store.js +100 -0
- package/dist/core/store.js.map +1 -0
- package/dist/core/views.d.ts +11 -0
- package/dist/core/views.d.ts.map +1 -0
- package/dist/core/views.js +20 -0
- package/dist/core/views.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/modules/auth/index.d.ts +73 -0
- package/dist/modules/auth/index.d.ts.map +1 -0
- package/dist/modules/auth/index.js +64 -0
- package/dist/modules/auth/index.js.map +1 -0
- package/dist/modules/auth/service.d.ts +98 -0
- package/dist/modules/auth/service.d.ts.map +1 -0
- package/dist/modules/auth/service.js +200 -0
- package/dist/modules/auth/service.js.map +1 -0
- package/dist/modules/bridge/index.d.ts +73 -0
- package/dist/modules/bridge/index.d.ts.map +1 -0
- package/dist/modules/bridge/index.js +89 -0
- package/dist/modules/bridge/index.js.map +1 -0
- package/dist/modules/bridge/service.d.ts +75 -0
- package/dist/modules/bridge/service.d.ts.map +1 -0
- package/dist/modules/bridge/service.js +134 -0
- package/dist/modules/bridge/service.js.map +1 -0
- package/dist/modules/home/index.d.ts +95 -0
- package/dist/modules/home/index.d.ts.map +1 -0
- package/dist/modules/home/index.js +123 -0
- package/dist/modules/home/index.js.map +1 -0
- package/dist/modules/home/service.d.ts +48 -0
- package/dist/modules/home/service.d.ts.map +1 -0
- package/dist/modules/home/service.js +195 -0
- package/dist/modules/home/service.js.map +1 -0
- package/dist/modules/skill/index.d.ts +78 -0
- package/dist/modules/skill/index.d.ts.map +1 -0
- package/dist/modules/skill/index.js +132 -0
- package/dist/modules/skill/index.js.map +1 -0
- package/dist/runtime/api.d.ts +24 -0
- package/dist/runtime/api.d.ts.map +1 -0
- package/dist/runtime/api.js +140 -0
- package/dist/runtime/api.js.map +1 -0
- package/dist/runtime/cli.d.ts +23 -0
- package/dist/runtime/cli.d.ts.map +1 -0
- package/dist/runtime/cli.js +410 -0
- package/dist/runtime/cli.js.map +1 -0
- package/dist/runtime/help.d.ts +29 -0
- package/dist/runtime/help.d.ts.map +1 -0
- package/dist/runtime/help.js +89 -0
- package/dist/runtime/help.js.map +1 -0
- package/dist/runtime/registry.d.ts +318 -0
- package/dist/runtime/registry.d.ts.map +1 -0
- package/dist/runtime/registry.js +75 -0
- package/dist/runtime/registry.js.map +1 -0
- package/dist/runtime/server.d.ts +10 -0
- package/dist/runtime/server.d.ts.map +1 -0
- package/dist/runtime/server.js +127 -0
- package/dist/runtime/server.js.map +1 -0
- package/dist/runtime/spec.d.ts +32 -0
- package/dist/runtime/spec.d.ts.map +1 -0
- package/dist/runtime/spec.js +180 -0
- package/dist/runtime/spec.js.map +1 -0
- package/dist/runtime/types/action.d.ts +101 -0
- package/dist/runtime/types/action.d.ts.map +1 -0
- package/dist/runtime/types/action.js +19 -0
- package/dist/runtime/types/action.js.map +1 -0
- package/dist/runtime/types/client.d.ts +39 -0
- package/dist/runtime/types/client.d.ts.map +1 -0
- package/dist/runtime/types/client.js +2 -0
- package/dist/runtime/types/client.js.map +1 -0
- package/dist/runtime/types/http.d.ts +22 -0
- package/dist/runtime/types/http.d.ts.map +1 -0
- package/dist/runtime/types/http.js +6 -0
- package/dist/runtime/types/http.js.map +1 -0
- package/dist/runtime/types/module.d.ts +72 -0
- package/dist/runtime/types/module.d.ts.map +1 -0
- package/dist/runtime/types/module.js +15 -0
- package/dist/runtime/types/module.js.map +1 -0
- package/dist/web/public/assets/CliHints-BOxU-Duc.js +1 -0
- package/dist/web/public/assets/index-BTRKdMIW.js +50 -0
- package/dist/web/public/assets/index-BjGe8ggI.css +1 -0
- package/dist/web/public/assets/view-CE4tnCuy.js +1 -0
- package/dist/web/public/assets/view-CEQ2uPxE.js +1 -0
- package/dist/web/public/favicon-16.png +0 -0
- package/dist/web/public/favicon-32.png +0 -0
- package/dist/web/public/favicon-48.png +0 -0
- package/dist/web/public/favicon.ico +0 -0
- package/dist/web/public/index.html +19 -0
- package/dist/web/public/logo-rounded.png +0 -0
- package/dist/web/public/logo-rounded.svg +6 -0
- package/dist/web/public/logo.png +0 -0
- package/dist/web/public/logo.svg +6 -0
- package/package.json +52 -31
- package/README.md +0 -251
- package/bin/index.js +0 -714
- package/client/bridge-client.js +0 -160
- package/client/web.js +0 -234
- package/client/ws-client.js +0 -191
- package/lib/apps.js +0 -237
- package/lib/auth.js +0 -319
- package/lib/config.js +0 -534
- package/lib/index.js +0 -15
- package/lib/logger.js +0 -63
- package/lib/protocol.js +0 -208
- package/lib/store.js +0 -66
- package/server/auth.js +0 -274
- package/server/server.js +0 -752
|
@@ -0,0 +1 @@
|
|
|
1
|
+
:root{--ink: #14161a;--paper: #fff;--soft: #f5f6f7;--soft-2: #ebedf0;--mid: #8b9096;--bad: #b3261e;--on-bad: #fff;--border: 1px solid var(--soft-2);--overlay: rgba(0, 0, 0, .28);--row-h: 28px;--btn-h: 32px;--fs-core: 12px;--radius-btn: 8px;--shadow-relief: -1px -1px 2px rgba(255, 255, 255, .85), 2px 2px 6px rgba(20, 22, 26, .07);--shadow-relief-press: inset 1px 1px 2px rgba(20, 22, 26, .1), inset -1px -1px 2px rgba(255, 255, 255, .7);--shadow-relief-inset: inset 1px 1px 2px rgba(20, 22, 26, .06), inset -1px -1px 2px rgba(255, 255, 255, .9)}[data-theme=dark]{--ink: #e6e8eb;--paper: #15171a;--soft: #1d2024;--soft-2: #2b2f36;--mid: #8d949e;--bad: #f28b82;--on-bad: #3b1410;--overlay: rgba(0, 0, 0, .55);--shadow-relief: -1px -1px 2px rgba(255, 255, 255, .05), 2px 2px 6px rgba(0, 0, 0, .45);--shadow-relief-press: inset 1px 1px 2px rgba(0, 0, 0, .5), inset -1px -1px 2px rgba(255, 255, 255, .06);--shadow-relief-inset: inset 1px 1px 2px rgba(0, 0, 0, .4), inset -1px -1px 2px rgba(255, 255, 255, .05)}@media(prefers-color-scheme:dark){[data-theme=auto]{--ink: #e6e8eb;--paper: #15171a;--soft: #1d2024;--soft-2: #2b2f36;--mid: #8d949e;--bad: #f28b82;--on-bad: #3b1410;--overlay: rgba(0, 0, 0, .55);--shadow-relief: -1px -1px 2px rgba(255, 255, 255, .05), 2px 2px 6px rgba(0, 0, 0, .45);--shadow-relief-press: inset 1px 1px 2px rgba(0, 0, 0, .5), inset -1px -1px 2px rgba(255, 255, 255, .06);--shadow-relief-inset: inset 1px 1px 2px rgba(0, 0, 0, .4), inset -1px -1px 2px rgba(255, 255, 255, .05)}}*{box-sizing:border-box}body{margin:0;background:var(--paper);color:var(--ink);font-family:system-ui,-apple-system,Segoe UI,Microsoft YaHei,sans-serif;font-size:14px;line-height:1.5}.mono,code,pre{font-family:ui-monospace,Consolas,monospace}.muted{color:var(--mid)}.bad{color:var(--bad)}.nowrap{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.btn{font:inherit;cursor:pointer;padding:0 12px;height:var(--btn-h);font-size:var(--fs-core);border:0;border-radius:var(--radius-btn);background:var(--ink);color:var(--paper);box-shadow:var(--shadow-relief);display:inline-flex;align-items:center;gap:4px;transition:box-shadow .12s,transform .08s,background .12s}.btn:active:not(:disabled){box-shadow:var(--shadow-relief-press);transform:translateY(1px)}.btn:disabled{background:var(--soft);color:var(--mid);box-shadow:var(--shadow-relief-inset);cursor:not-allowed}.btn.ghost{background:var(--soft);color:var(--ink)}.btn.small{height:22px;padding:0 8px;font-size:11px;border-radius:6px}.btn.danger{background:var(--bad);color:var(--on-bad)}input,select{height:var(--row-h);font-size:var(--fs-core);padding:0 8px;border:1px solid var(--soft-2);border-radius:4px;background:var(--paper);font-family:inherit}input:focus,select:focus{outline:none;box-shadow:var(--shadow-relief-inset)}.card{padding:0;border:var(--border);border-radius:4px}.stack>.card+.card{margin-top:14px}.cols>.card+.card{margin-top:0}.colhead{display:flex;align-items:center;justify-content:space-between;padding:0 12px;height:28px;border-bottom:var(--border);font-size:13px;font-weight:600}.row{display:flex;align-items:center;height:var(--row-h);padding:0 12px;border-bottom:1px solid var(--soft-2);transition:background .12s}.row:hover{background:var(--soft)}.row:last-child{border-bottom:0}.row .name{font-weight:600;min-width:22ch;max-width:40%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.row .desc{color:var(--mid);flex:1;min-width:0;padding-left:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.row .acts{margin-left:auto;display:flex;align-items:center;gap:6px;flex-shrink:0}.tag{display:inline-flex;align-items:center;height:22px;padding:0 8px;border-radius:999px;background:var(--soft);color:var(--mid);font-size:11px}.tag.strong{background:var(--ink);color:var(--paper)}.tag.bad{background:var(--bad);color:var(--on-bad)}.kv{margin:0}.kv-row{display:flex;align-items:center;gap:10px;border-bottom:1px solid var(--soft-2)}.kv-row:last-child{border-bottom:0}.kv dt{color:var(--mid);font-size:11px;width:80px;flex-shrink:0;padding-left:12px}.kv dd{margin:0;min-width:0;padding:6px 12px 6px 0;font-size:var(--fs-core);display:flex;align-items:center}.empty{color:var(--mid);padding:18px 12px}.empty code{background:var(--soft);padding:1px 5px;border-radius:4px}.copyable{cursor:copy}.copyable:hover{background:var(--soft)}.toolbar{display:flex;flex-wrap:wrap;gap:8px;align-items:center}.cli-hint{color:var(--mid);font-size:11px;display:flex;flex-wrap:wrap;align-items:center;gap:2px;padding:6px 12px;border-top:var(--border)}.cli-cmd{font-family:ui-monospace,Consolas,monospace;color:var(--ink)}.snippet-box{display:block;border:1px solid var(--soft-2);border-radius:4px;background:var(--soft);padding:8px 10px;font-family:ui-monospace,Consolas,monospace;font-size:12px}.snippet-box pre{font:inherit;margin:0;white-space:pre-wrap}table.levels{border-collapse:collapse;width:100%;font-size:var(--fs-core)}table.levels th{text-align:left;color:var(--mid);font-weight:600}table.levels th,table.levels td{height:var(--row-h);padding:0 8px 0 0;border-bottom:1px solid var(--soft-2)}table.levels tbody tr:last-child td{border-bottom:0}.toast-stack{position:fixed;right:16px;bottom:16px;display:flex;flex-direction:column;gap:8px;z-index:60}.toast{background:var(--ink);color:var(--paper);padding:8px 12px;border-radius:6px;font-size:13px;box-shadow:var(--shadow-relief)}.dlg,.modal{position:fixed;top:0;right:0;bottom:0;left:0;background:var(--overlay);display:flex;align-items:center;justify-content:center;z-index:50;padding:20px}.dlg-box{background:var(--paper);border-radius:10px;padding:16px;width:min(460px,100%);box-shadow:0 8px 30px #0003}.dlg-title{font-weight:600;margin-bottom:6px}.dlg-msg{color:var(--mid);margin-bottom:12px;word-break:break-word}.dlg-input{width:100%;margin-bottom:14px}.dlg-acts{display:flex;justify-content:flex-end;gap:8px}.modal-box{background:var(--paper);border-radius:10px;width:min(900px,100%);max-height:84vh;display:flex;flex-direction:column;box-shadow:0 8px 30px #0003}.modal-title{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:var(--border);font-weight:600}.modal-body{padding:16px;overflow:auto}header{display:flex;flex-wrap:wrap;gap:12px;align-items:center;padding:10px 16px;border-bottom:var(--border);position:sticky;top:0;background:var(--paper);z-index:5}.brand{font-weight:700;letter-spacing:.5px;display:inline-flex;align-items:center;gap:8px}.brand img{width:22px;height:22px;border-radius:6px;display:block}.brand .ver-tag{font-weight:600;font-size:11px;font-family:ui-monospace,Consolas,monospace;color:var(--mid);background:var(--soft);border:1px solid var(--soft-2);border-radius:5px;padding:1px 6px;line-height:1.4}.brand .sub{font-weight:400;color:var(--mid);margin-left:8px;font-size:12px}header nav{display:inline-flex;flex-wrap:wrap;gap:2px;padding:3px;background:var(--soft);border-radius:16px;min-width:0}header .head-acts{margin-left:auto;display:flex;align-items:center;gap:8px;min-width:0}header .meta{min-width:0;max-width:46vw;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--mid);font-size:12px;font-family:ui-monospace,Consolas,monospace}.theme-toggle{height:var(--btn-h);width:var(--btn-h);padding:0;display:inline-flex;align-items:center;justify-content:center;border:1px solid var(--soft-2);border-radius:6px;background:var(--soft);color:var(--ink);cursor:pointer;transition:box-shadow .12s}.theme-toggle:hover{box-shadow:var(--shadow-relief)}.theme-toggle:active{box-shadow:var(--shadow-relief-press)}.theme-toggle svg{width:16px;height:16px;display:block}.seg{display:inline-flex;border:1px solid var(--soft-2);border-radius:6px;overflow:hidden}.seg button{border:0;background:var(--paper);color:var(--mid);font:inherit;font-size:var(--fs-core);height:26px;padding:0 12px;cursor:pointer;display:inline-flex;align-items:center;gap:6px}.seg button+button{border-left:1px solid var(--soft-2)}.seg button:hover{background:var(--soft)}.seg button.active{background:var(--ink);color:var(--paper);font-weight:600}.seg button svg{width:13px;height:13px}.tab{flex:0 0 auto;display:inline-flex;align-items:center;gap:5px;border:0;background:transparent;padding:0 14px;height:28px;border-radius:999px;cursor:pointer;color:var(--mid);font-size:13px;white-space:nowrap;transition:background .15s,color .15s}.tab:hover{color:var(--ink);background:transparent}.tab.active{color:var(--ink);background:var(--paper);font-weight:600;box-shadow:var(--shadow-relief)}main{padding:16px}.panel{display:none}.panel.active{display:block}.cols{display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:14px;align-items:start}.create-grid{display:grid;grid-template-columns:minmax(200px,240px) minmax(320px,1fr) minmax(240px,300px);gap:14px;align-items:start}@media(max-width:900px){.create-grid{grid-template-columns:1fr}}.tpl-item{display:block;width:100%;text-align:left;border:0;border-bottom:1px solid var(--soft-2);background:none;padding:9px 12px;cursor:pointer;font:inherit}.tpl-item:last-child{border-bottom:0}.tpl-item:hover{background:var(--soft)}.tpl-item.active{background:var(--soft);box-shadow:inset 3px 0 0 var(--ink)}.tpl-name{font-weight:600;font-size:13px}.tpl-desc{font-size:11px;margin-top:2px;line-height:1.4}.opt-body{padding:12px}.opt-row{margin-bottom:14px}.opt-row:last-child{margin-bottom:0}.opt-label{display:block;font-size:12px;font-weight:600;margin-bottom:4px}.opt-label .req{color:var(--bad);margin-left:3px}.opt-row input[type=text],.opt-row input[type=number],.opt-row select{width:100%}.opt-hint{font-size:11px;color:var(--mid);margin-top:3px;line-height:1.4}.opt-actions{display:flex;justify-content:flex-end;gap:8px;padding:12px;border-top:var(--border)}.preview-body{padding:12px;display:flex;flex-direction:column;align-items:center}.logo-preview{display:block}.preview-name{font-size:15px;font-weight:700;margin-top:10px}.preview-sep{width:100%;border-top:var(--border);margin:12px 0}.file-list{width:100%;max-height:220px;overflow:auto;font-size:11px;line-height:1.6;color:var(--mid)}.ok-line{color:var(--ink);font-weight:600;font-size:12px;margin-bottom:4px}.readme-body{padding:12px;max-height:60vh;overflow:auto}.tab-group{position:relative;display:inline-flex;flex:0 0 auto}.tab-group .tab{display:inline-flex;align-items:center;gap:5px}.tab-group .count{font-size:10px;font-weight:600;line-height:1;color:var(--mid);background:var(--soft-2);border-radius:999px;padding:2px 5px;min-width:16px;text-align:center;font-variant-numeric:tabular-nums}.tab-group .tab.active .count{color:var(--ink);background:var(--soft)}.tab-group .caret{width:0;height:0;border-left:4px solid transparent;border-right:4px solid transparent;border-top:5px solid currentColor;margin-left:1px;opacity:.7}.tab-group .tab.active .caret{border-top-color:var(--ink)}.tab-menu{position:absolute;top:calc(100% + 4px);left:0;z-index:40;min-width:172px;padding:4px;display:flex;flex-direction:column;gap:2px;background:var(--paper);border:var(--border);border-radius:var(--radius-btn);box-shadow:var(--shadow-relief)}.tab-menu:before{content:"";position:absolute;left:0;right:0;top:-5px;height:5px}.tab-menu-row{display:flex;align-items:center;gap:8px;height:var(--row-h);padding:0 8px;border:0;border-radius:6px;background:none;color:var(--ink);font:inherit;font-size:var(--fs-core);text-align:left;cursor:pointer}.tab-menu-row:hover{background:var(--soft)}.tab-menu-row.current{font-weight:600}.grip{width:10px;height:14px;flex:0 0 auto;cursor:grab;background-image:radial-gradient(circle,var(--mid) 1px,transparent 1px);background-size:4px 4px;opacity:.5}.grip:active{cursor:grabbing}.nav-title{font:inherit;font-size:var(--fs-core);font-weight:600;min-width:12ch;padding:2px 0;background:none;border:0;border-bottom:1px dashed var(--soft-2)}.nav-title:focus{outline:none;border-bottom-color:var(--ink)}.nav-group .colhead{display:flex;align-items:center;gap:8px}.nav-pool{padding:8px 10px;background:var(--paper);border:var(--border);border-radius:8px}.nav-pool.drop-target,.nav-group.drop-target{outline:2px dashed var(--ink);outline-offset:-2px;background:var(--soft)}.nav-group .colhead.drop-target{background:var(--soft)}.nav-row{padding:0 8px}.nav-row.dragging{opacity:.45}.nav-list{display:flex;flex-direction:column}.drop-ind{height:0;border-top:2px solid var(--ink);margin:1px 6px;border-radius:1px;flex:0 0 auto}.nav-hidden .colhead,.nav-hidden .row .name{color:var(--mid)}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{u as S,r as d,j as s}from"./index-BTRKdMIW.js";import{C as L}from"./CliHints-BOxU-Duc.js";function M(){const{client:l}=S(),[a,y]=d.useState(null),[t,k]=d.useState(null),[o,f]=d.useState(null),[g,u]=d.useState([]),[n,h]=d.useState(!1),[v,x]=d.useState(""),m=d.useCallback(async()=>{try{const e=await l["bridge.status"]({}),c=await l["bridge.apps"]({});y(e),k(c)}catch(e){x(String((e==null?void 0:e.message)||e))}},[l]),j=d.useCallback(async()=>{try{const e=await l["auth.list"]({});u(e.accounts)}catch{u([])}},[l]);d.useEffect(()=>{m(),j();const e=window.setInterval(()=>{m()},3e3);return()=>window.clearInterval(e)},[m,j]);async function i(e){h(!0),x("");try{await e(),await m(),await j()}catch(c){x(String((c==null?void 0:c.message)||c))}finally{h(!1)}}const r=(t==null?void 0:t.configured)??[],N=(t==null?void 0:t.candidates)??[],b=new Set(r.map(e=>e.app));async function w(e,c){const p=new Set(b);c?p.add(e):p.delete(e),await i(()=>l["bridge.whitelist.set"]({apps:[...p].join(",")}))}async function C(){h(!0);try{const e=await l["bridge.catalog"]({});f(e)}catch(e){x(String((e==null?void 0:e.message)||e))}finally{h(!1)}}const I={idle:"未启动",connecting:"连接中",connected:"已连接(注册中)",registered:"已注册",reconnecting:"重连中",stopped:"已停止",shutdown:"服务端要求下线",error:"出错"};return s.jsxs("div",{className:"stack",style:{padding:16},children:[s.jsxs("div",{className:"card",children:[s.jsxs("div",{className:"colhead",children:["连接状态",s.jsxs("span",{className:"right muted",children:["CLI 等价:",s.jsx("code",{children:"mn-ws status"})]})]}),s.jsxs("dl",{className:"kv",children:[s.jsxs("div",{className:"kv-row",children:[s.jsx("dt",{children:"状态"}),s.jsxs("dd",{children:[s.jsx("strong",{children:a?I[a.state]||a.state:"…"}),a!=null&&a.retryInMs?`(${Math.round(a.retryInMs/1e3)}s 后重试)`:"",a!=null&&a.reason?` · ${a.reason}`:""]})]}),s.jsxs("div",{className:"kv-row",children:[s.jsx("dt",{children:"server"}),s.jsx("dd",{children:(a==null?void 0:a.url)||"…"})]}),s.jsxs("div",{className:"kv-row",children:[s.jsx("dt",{children:"本机标识"}),s.jsx("dd",{children:(a==null?void 0:a.clientId)||"…"})]}),s.jsxs("div",{className:"kv-row",children:[s.jsx("dt",{children:"身份"}),s.jsxs("dd",{children:[(a==null?void 0:a.identity)||"…",a!=null&&a.loggedIn?`(${a.via==="cli"?"复用 CLI 登录态":"面板登录"})`:""]})]})]}),s.jsxs("div",{className:"toolbar",style:{padding:12},children:[s.jsx("button",{className:"btn",disabled:n,onClick:()=>void i(()=>l["bridge.start"]({})),children:"起桥"}),s.jsx("button",{className:"btn ghost",disabled:n,onClick:()=>void i(()=>l["bridge.stop"]({})),children:"停桥"}),s.jsx("button",{className:"btn ghost",disabled:n,onClick:()=>void C(),children:"拉远端目录"})]}),v&&s.jsx("div",{className:"bad",style:{padding:"0 14px 12px"},children:v})]}),s.jsxs("div",{className:"card",children:[s.jsxs("div",{className:"colhead",children:["白名单",s.jsxs("span",{className:"right muted",children:["CLI 等价:",s.jsx("code",{children:"mn-ws apps set --apps nx-kv,nx-rh"})]})]}),s.jsxs("div",{style:{padding:12},className:"stack",children:[s.jsx("div",{className:"muted",children:"只有勾上的应用会被桥。端口在监听 ≠ 可以桥 —— 白名单说了算, 否则扫到端口就自动开桥等于把整台机器暴露出去。"}),r.length>0&&s.jsxs("div",{className:"stack",children:[s.jsx("div",{className:"muted",children:"已桥接:"}),r.map(e=>s.jsxs("label",{style:{display:"inline-flex",gap:6,alignItems:"center"},children:[s.jsx("input",{type:"checkbox",checked:!0,disabled:!0,readOnly:!0}),s.jsxs("span",{children:[e.app," ",s.jsx("span",{className:"muted",children:e.port??"—"})," ",e.alive?s.jsx("span",{className:"tag",children:"在监听"}):s.jsx("span",{className:"tag warn",children:"未运行"})]})]},e.app))]}),N.length>0&&s.jsxs("div",{className:"stack",children:[s.jsx("div",{className:"muted",children:"本机在跑、还没桥:"}),N.map(e=>s.jsxs("label",{style:{display:"inline-flex",gap:6,alignItems:"center"},children:[s.jsx("input",{type:"checkbox",checked:b.has(e.app),disabled:n,onChange:c=>{w(e.app,c.target.checked)}}),s.jsxs("span",{children:[e.app," ",s.jsx("span",{className:"muted",children:e.port})," ",e.title]})]},e.app))]}),r.length>0&&s.jsx("div",{className:"toolbar",children:r.map(e=>s.jsxs("button",{className:"btn small ghost",disabled:n,onClick:()=>{w(e.app,!1)},children:["取消 ",e.app]},e.app))})]})]}),o&&s.jsxs("div",{className:"card",children:[s.jsxs("div",{className:"colhead",children:["远端目录",s.jsxs("span",{className:"right muted",children:["CLI 等价:",s.jsx("code",{children:"mn-ws catalog"})]})]}),o.ok?s.jsx("dl",{className:"kv",children:o.apps.map(e=>s.jsxs("div",{className:"kv-row",children:[s.jsx("dt",{children:e.app}),s.jsxs("dd",{children:[e.title," ",s.jsx("span",{className:"muted",children:e.port??"—"})," · ",e.bridgeId]})]},e.app))}):s.jsxs("div",{className:"bad",style:{padding:14},children:["拉目录失败:",o.error]})]}),s.jsxs("div",{className:"card",children:[s.jsxs("div",{className:"colhead",children:["账号",s.jsxs("span",{className:"right muted",children:["CLI 等价:",s.jsx("code",{children:"mn-ws auth list"})," / ",s.jsx("code",{children:"mn-ws auth switch <账号>"})]})]}),s.jsx("div",{style:{padding:12},className:"stack",children:g.length===0?s.jsxs("div",{className:"muted",children:["本机还没有已存账号。登录一次:",s.jsx("code",{children:"mn-ws auth login"})]}):s.jsxs(s.Fragment,{children:[g.map(e=>s.jsxs("div",{className:"toolbar",children:[s.jsxs("span",{children:[e.label,e.active?s.jsx("span",{className:"tag",children:"当前"}):null,e.expired?s.jsx("span",{className:"tag warn",children:"已过期"}):null]}),!e.active&&s.jsx("button",{className:"btn small ghost",disabled:n,onClick:()=>{i(()=>l["auth.switch"]({account:e.key}))},children:"切到它"})]},e.key)),s.jsx("div",{className:"toolbar",children:s.jsx("button",{className:"btn small ghost",disabled:n,onClick:()=>{i(()=>l["auth.logout"]({}))},children:"退出当前账号"})})]})})]}),s.jsx(L,{module:"bridge"})]})}export{M as default};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{r as f,j as e,u as E,a as G,b as H,C as I,T as L,c as F}from"./index-BTRKdMIW.js";import{C as R}from"./CliHints-BOxU-Duc.js";const O=()=>({groups:[],hidden:[],order:[]});function V(s){return{groups:((s==null?void 0:s.navGroups)||[]).map(n=>({title:String(n.title||""),views:[...n.views||[]]})),hidden:[...(s==null?void 0:s.navHidden)||[]],order:[...(s==null?void 0:s.navOrder)||[]]}}function B(s){return{navGroups:s.groups.map(n=>({title:n.title.trim(),views:[...n.views]})),navHidden:[...s.hidden],navOrder:[...s.order]}}const _=(s,n)=>JSON.stringify(s)!==JSON.stringify(n),A=(s,n="")=>({...s,groups:[...s.groups,{title:n,views:[]}]}),J=(s,n)=>({...s,groups:s.groups.filter((r,t)=>t!==n)}),q=(s,n,r)=>({...s,groups:s.groups.map((t,o)=>o===n?{...t,title:r}:t)});function z(s,n,r){const t=n+r;if(t<0||t>=s.groups.length)return s;const o=[...s.groups],i=o[n],c=o[t];return!i||!c?s:(o[n]=c,o[t]=i,{...s,groups:o})}function K(s,n,r=[]){const t=s.hidden.indexOf(n);if(t>=0)return{kind:"hidden",index:t};const o=s.groups.findIndex(i=>i.views.includes(n));if(o>=0){const i=s.groups[o];return{kind:"group",gi:o,index:i.views.indexOf(n)}}return{kind:"pool",index:P(s,r).indexOf(n)}}function T(s,n){const r=s.order.filter(o=>n.includes(o)),t=n.filter(o=>!r.includes(o));return[...r,...t]}function Y(s,n,r,t,o=[]){if(t==null)return null;const i=K(s,n,o);return r.kind===i.kind&&(r.kind!=="group"||i.kind==="group"&&r.gi===i.gi)&&typeof i.index=="number"&&i.index<t?t-1:t}function M(s,n,r,t=null,o=[]){const i=s.groups.map(g=>({...g,views:g.views.filter(j=>j!==n)})),c=s.hidden.filter(g=>g!==n),p={...s,groups:i,hidden:c,order:s.order.filter(g=>g!==n)};if(r.kind==="hidden")return{...p,hidden:[...c,n]};if(r.kind==="pool"){const g=x=>!i.some(b=>b.views.includes(x))&&!c.includes(x),j=T(p,o).filter(x=>x!==n&&g(x)),y=t===null?j.length:Math.max(0,Math.min(t,j.length));return{...p,order:[...j.slice(0,y),n,...j.slice(y)]}}const m=i[r.gi];if(!m)return s;const k=t===null?m.views.length:Math.max(0,Math.min(t,m.views.length)),N=[...m.views.slice(0,k),n,...m.views.slice(k)];return{...p,groups:i.map((g,j)=>j===r.gi?{...m,views:N}:g)}}const Q=(s,n,r=[])=>M(s,n,{kind:"pool"},null,r),U=s=>s.groups.flatMap(n=>n.views),P=(s,n)=>{const r=new Set([...U(s),...s.hidden]);return T(s,n).filter(t=>!r.has(t))};function W(s,n){const r=new Set(n),t=[],o=new Map;s.groups.forEach((i,c)=>{const p=i.title.trim()||`第 ${c+1} 个分组`;i.title.trim()||t.push({field:`groups.${c}.title`,message:`${p}:每个分组都要有组名`}),i.views.length||t.push({field:`groups.${c}.views`,message:`${p}:还没有页面——加一个,或删掉这个分组`});for(const m of i.views){if(!r.has(m)){t.push({field:`groups.${c}.views.${m}`,message:`${p}:页面 ${m} 已不存在,请移出`});continue}const k=o.get(m);if(k!==void 0){t.push({field:`groups.${c}.views.${m}`,message:`页面 ${m} 同时出现在「${k}」与「${p}」里——一个页面只能属于一个分组`});continue}o.set(m,p)}});for(const i of s.hidden)r.has(i)||t.push({field:`hidden.${i}`,message:`要隐藏的页面 ${i} 已不存在`});for(const i of s.order)r.has(i)||t.push({field:`order.${i}`,message:`平铺顺序里的页面 ${i} 已不存在`});return t}function X({sections:s,value:n,onChange:r}){const[t,o]=f.useState(null),[i,c]=f.useState(null),p=s.map(a=>a.id),m=a=>s.find(l=>l.id===a)||null,k=a=>{if(a==="pool")return{kind:"pool"};if(a==="hidden")return{kind:"hidden"};const l=Number(a.slice(1));return Number.isFinite(l)?{kind:"group",gi:l}:null},N=(t==null?void 0:t.kind)==="view"?t.vid:null,g=()=>{o(null),c(null)},j=a=>c(l=>l&&l.key===a.key&&l.index===a.index&&l.group===a.group?l:a),y=()=>{if((t==null?void 0:t.kind)==="view"&&i){const a=k(i.key);if(a){const l=Y(n,t.vid,a,i.index,p),h=M(n,t.vid,a,l,p);h!==n&&r(h)}}g()},x=(a,l="")=>({className:`${l} ${(i==null?void 0:i.key)===a&&!i.group?"drop-target":""}`,onDragOver:h=>{N&&(h.preventDefault(),j({key:a,index:null}))},onDragLeave:h=>{const u=h.relatedTarget;(!u||!h.currentTarget.contains(u))&&c(v=>(v==null?void 0:v.key)===a?null:v)},onDrop:h=>{h.preventDefault(),y()}}),b=(a,l,h)=>({onDragOver:u=>{if(!N||(u.preventDefault(),u.stopPropagation(),N===l))return;const v=u.currentTarget.getBoundingClientRect(),w=u.clientY>v.top+v.height/2;j({key:a,index:h+(w?1:0)})},onDrop:u=>{u.preventDefault(),u.stopPropagation(),y()},onDragStart:()=>o({kind:"view",vid:l})}),$=(a,l,h)=>{const v=(i==null?void 0:i.key)===a&&!i.group?i.index===null?l.length:i.index:null;return e.jsxs("div",{className:"nav-list",children:[v===0&&e.jsx(S,{}),l.map((w,d)=>e.jsxs(f.Fragment,{children:[e.jsx(Z,{section:m(w),vid:w,dragging:N===w,...b(a,w,d),onDragEnd:g,actions:h(w)}),v===d+1&&e.jsx(S,{})]},w))]})},C=(a,l)=>e.jsxs("div",{className:"row",children:[e.jsx("span",{className:"desc",children:l}),(i==null?void 0:i.key)===a&&!i.group&&e.jsx(S,{})]}),D=P(n,p);return e.jsxs("div",{className:"stack",children:[e.jsxs("section",{...x("pool","nav-pool"),children:[e.jsx("div",{className:"hint",children:"平铺:不进分组也不隐藏的页面,直接显示头部(拖到这里 = 移出分组 / 取消隐藏)"}),D.length?$("pool",D,()=>null):C("pool","没有平铺的页面。")]}),n.groups.map((a,l)=>{const h=`g${l}`;return e.jsxs("section",{...x(h,"card nav-group"),children:[e.jsxs("div",{className:`colhead ${(i==null?void 0:i.key)===h&&i.group?"drop-target":""}`,onDragOver:u=>{(t==null?void 0:t.kind)==="group"&&(u.preventDefault(),j({key:h,index:null,group:!0}))},onDrop:u=>{u.preventDefault(),(t==null?void 0:t.kind)==="group"&&t.gi!==l&&r(z(n,t.gi,l-t.gi)),g()},children:[e.jsx("span",{className:"grip",title:"拖动调整分组顺序",draggable:!0,onDragStart:()=>o({kind:"group",gi:l}),onDragEnd:g}),e.jsx("input",{className:"nav-title",placeholder:"组名,如「求职」",value:a.title,onChange:u=>r(q(n,l,u.target.value))}),e.jsxs("span",{className:"muted",children:[a.views.length," 个页面"]}),e.jsx("span",{className:"right rowgap",children:e.jsx("button",{className:"btn small ghost",onClick:()=>r(J(n,l)),children:"删除组"})})]}),a.views.length?$(h,a.views,u=>e.jsx("button",{className:"btn small ghost",onClick:()=>r(Q(n,u,p)),children:"移出"})):C(h,"空分组——把页面拖进来。")]},l)}),e.jsxs("section",{...x("hidden","card nav-group nav-hidden"),children:[e.jsxs("div",{className:"colhead",children:["隐藏",e.jsxs("span",{className:"muted",children:[n.hidden.length," 个页面"]}),e.jsx("span",{className:"right muted",children:"不进头部 · 深链仍可用"})]}),n.hidden.length?$("hidden",n.hidden,()=>null):C("hidden","没有隐藏的页面——把不常用的拖进来。")]}),e.jsxs("div",{className:"rowgap",children:[e.jsx("button",{className:"btn",onClick:()=>r(A(n)),children:"新增分组"}),e.jsx("span",{className:"hint",children:"组顺序 = 头部显示顺序;组内顺序 = 展开列表顺序"})]})]})}function S(){return e.jsx("div",{className:"drop-ind","aria-hidden":"true"})}function Z({section:s,vid:n,dragging:r,actions:t,onDragStart:o,onDragEnd:i,onDragOver:c,onDrop:p}){return e.jsxs("div",{className:`row nav-row ${r?"dragging":""}`,onDragOver:c,onDrop:p,children:[e.jsx("span",{className:"grip",title:"拖动:换位 / 换容器",draggable:!0,onDragStart:o,onDragEnd:i}),e.jsx("span",{className:"name",children:s?s.title:`页面已删除(${n})`}),e.jsx("span",{className:"desc",children:s?n:"保存前请移出"}),e.jsx("span",{className:"acts",children:t})]})}const ee=`mn-ws setting set --set 'navGroups=[{"title":"求职","views":["home"]}]' / --set navHidden=home / --set navOrder=["home"]`;function te(){const{boot:s,client:n,settings:r,refreshSettings:t}=E(),o=G(),i=H(),[c,p]=f.useState(""),[m,k]=f.useState(!1),[N,g]=f.useState(null),[j,y]=f.useState(!1),[x,b]=f.useState(O()),[$,C]=f.useState(O()),D=f.useMemo(()=>((r==null?void 0:r.views)||[]).map(d=>({id:d.id,title:d.title})),[r]),a=f.useMemo(()=>D.map(d=>d.id),[D]);f.useEffect(()=>{if(!r)return;const d=V(r.settings);b(d),C(d)},[r]);const l=f.useMemo(()=>W(x,a),[x,a]),h=_(x,$);async function u(){if(!c.trim()){o("请先填写名字");return}y(!0);try{const d=await n["home.greet"]({name:c.trim(),loud:m});g(d)}catch(d){o(String((d==null?void 0:d.message)||d))}finally{y(!1)}}const v=()=>i(async()=>{if(l.length){o(`保存失败:${l[0].message}`);return}await F("/api/settings",{method:"PATCH",body:{set:B(x)}}),await t(),o("设置已保存")}),w=()=>b(structuredClone($));return e.jsxs("div",{className:"stack",style:{padding:16},children:[e.jsxs("div",{className:"card",children:[e.jsx("div",{className:"colhead",children:"问候(演示动作)"}),e.jsxs("div",{style:{padding:14},children:[e.jsxs("div",{className:"toolbar",children:[e.jsx("input",{value:c,placeholder:"你的名字",onChange:d=>p(d.target.value),onKeyDown:d=>{d.key==="Enter"&&u()}}),e.jsxs("label",{style:{display:"inline-flex",gap:6,alignItems:"center"},children:[e.jsx("input",{type:"checkbox",checked:m,onChange:d=>k(d.target.checked)}),"大声"]}),e.jsx("button",{className:"btn",disabled:j,onClick:()=>void u(),children:j?"问候中…":"打招呼"})]}),N&&e.jsxs("div",{style:{marginTop:12},children:[e.jsx("strong",{children:N.greeting}),e.jsxs("div",{className:"muted",style:{marginTop:4},children:["第 ",N.count," 次 · ",N.at]})]})]})]}),e.jsxs("div",{className:"card",children:[e.jsx("div",{className:"colhead",children:"运行信息"}),e.jsxs("dl",{className:"kv",children:[e.jsxs("div",{className:"kv-row",children:[e.jsx("dt",{children:"应用"}),e.jsx("dd",{children:s?`${s.app.name} v${s.app.version}`:"加载中…"})]}),e.jsxs("div",{className:"kv-row",children:[e.jsx("dt",{children:"存储"}),e.jsx("dd",{children:e.jsx(I,{text:(s==null?void 0:s.storePath)||"",children:(s==null?void 0:s.storePath)||"…"})})]}),e.jsxs("div",{className:"kv-row",children:[e.jsx("dt",{children:"环境变量"}),e.jsx("dd",{children:s==null?void 0:s.storeEnv})]})]})]}),e.jsxs("div",{className:"card",children:[e.jsxs("div",{className:"colhead",children:["外观",e.jsx("span",{className:"right muted",children:"色彩主题 · 点击即保存"})]}),e.jsx("div",{style:{padding:12},className:"stack",children:e.jsxs("div",{className:"toolbar",children:[e.jsx(L,{}),e.jsxs("span",{className:"muted",children:["浅色 / 深色 / 跟随系统(深色下自动切换为暗色 token);CLI 等价:",e.jsx("code",{children:"mn-ws setting set --key theme --value dark"})]})]})})]}),e.jsxs("div",{className:"card",children:[e.jsxs("div",{className:"colhead",children:["栏目管理",e.jsxs("span",{className:"right muted",children:["头部显示 ",a.length-x.hidden.length," 个 · 隐藏 ",x.hidden.length," 个",x.groups.length?` · ${x.groups.length} 个分组`:""]})]}),e.jsxs("div",{style:{padding:12},className:"stack",children:[e.jsxs("div",{className:"toolbar",children:[e.jsxs("span",{className:"muted",children:["拖动就是把结构摆成你要的样子:页面可以拖进分组、组内换位、拖回平铺区即移出;拖分组把手换分组顺序。 被隐藏的页面只是头部不显示入口,用 ",e.jsx("code",{children:"#/页面id"})," 直达仍然可用。"]}),e.jsx("span",{className:"right rowgap",children:h?e.jsxs(e.Fragment,{children:[e.jsx("span",{className:"tag",children:"有未保存的改动"}),e.jsx("button",{className:"btn small",onClick:()=>{v()},children:"保存"}),e.jsx("button",{className:"btn small ghost",onClick:w,children:"放弃改动"})]}):e.jsx("span",{className:"muted",children:"已保存"})})]}),e.jsx(X,{sections:D,value:x,onChange:b}),l.map(d=>e.jsxs("div",{className:"bad",children:["· ",d.message]},d.field))]})]}),e.jsxs("div",{className:"muted",style:{padding:"0 2px"},children:["CLI 等价:",e.jsx("code",{children:ee})]}),e.jsx(R,{module:"home"})]})}export{te as default};
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>mn-ws</title>
|
|
7
|
+
<!-- favicon 四连:浏览器/系统各取所需。图标由生成器的 logo-gen 产出,
|
|
8
|
+
这里不预置二进制——每次生成都是新算的,不会有「图与配置对不上」的漂移。 -->
|
|
9
|
+
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32.png">
|
|
10
|
+
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16.png">
|
|
11
|
+
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
|
12
|
+
<link rel="apple-touch-icon" href="/logo-rounded.png">
|
|
13
|
+
<script type="module" crossorigin src="/assets/index-BTRKdMIW.js"></script>
|
|
14
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BjGe8ggI.css">
|
|
15
|
+
</head>
|
|
16
|
+
<body>
|
|
17
|
+
<div id="root"></div>
|
|
18
|
+
</body>
|
|
19
|
+
</html>
|
|
Binary file
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512" role="img" aria-label="ws">
|
|
2
|
+
<rect width="512" height="512" rx="113" ry="113" fill="#01847F"/>
|
|
3
|
+
<text x="50%" y="50%" fill="#F9D2E4" font-family="Verdana, 'DejaVu Sans', 'Segoe UI', Arial, 'Helvetica Neue', 'Microsoft YaHei', sans-serif"
|
|
4
|
+
font-size="297" font-weight="bold" text-anchor="middle"
|
|
5
|
+
dominant-baseline="central">ws</text>
|
|
6
|
+
</svg>
|
|
Binary file
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512" role="img" aria-label="ws">
|
|
2
|
+
<rect width="512" height="512" rx="0" ry="0" fill="#01847F"/>
|
|
3
|
+
<text x="50%" y="50%" fill="#F9D2E4" font-family="Verdana, 'DejaVu Sans', 'Segoe UI', Arial, 'Helvetica Neue', 'Microsoft YaHei', sans-serif"
|
|
4
|
+
font-size="297" font-weight="bold" text-anchor="middle"
|
|
5
|
+
dominant-baseline="central">ws</text>
|
|
6
|
+
</svg>
|
package/package.json
CHANGED
|
@@ -1,45 +1,66 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mn-ws",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "HTTP 与 WebSocket 双向桥的客户端半:把本机 nx 服务桥给远端 HTTP 客户端",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
5
8
|
"bin": {
|
|
6
|
-
"mn-ws": "./bin/
|
|
9
|
+
"mn-ws": "./bin/ws.mjs"
|
|
10
|
+
},
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.js"
|
|
15
|
+
}
|
|
7
16
|
},
|
|
8
|
-
"main": "lib/index.js",
|
|
9
|
-
"type": "commonjs",
|
|
10
17
|
"files": [
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"client/",
|
|
15
|
-
"README.md"
|
|
18
|
+
"dist",
|
|
19
|
+
"bin",
|
|
20
|
+
"assets"
|
|
16
21
|
],
|
|
17
22
|
"scripts": {
|
|
18
|
-
"start": "node bin/
|
|
19
|
-
"
|
|
20
|
-
"
|
|
23
|
+
"start": "pnpm build && node bin/ws.mjs serve",
|
|
24
|
+
"dev": "node --import tsx scripts/dev.mjs",
|
|
25
|
+
"dev:serve": "node --import tsx src/runtime/cli.ts serve --no-open",
|
|
26
|
+
"link:local": "node scripts/link-local.mjs",
|
|
27
|
+
"unlink:local": "node scripts/link-local.mjs --unlink",
|
|
28
|
+
"build": "tsc -b tsconfig.node.json && vite build",
|
|
29
|
+
"typecheck": "tsc -b tsconfig.node.json tsconfig.web.json",
|
|
30
|
+
"lint": "eslint .",
|
|
31
|
+
"test:smoke": "node tests/smoke.mjs",
|
|
32
|
+
"test:unit": "node --import tsx --test \"tests/unit/*.test.ts\" \"tests/unit/*.test.mjs\"",
|
|
33
|
+
"test:e2e": "node tests/e2e-remote.js",
|
|
34
|
+
"test": "pnpm run lint && pnpm run typecheck && pnpm run build && pnpm run test:smoke && pnpm run test:unit"
|
|
21
35
|
},
|
|
22
36
|
"dependencies": {
|
|
37
|
+
"mn-ws-server": "workspace:*",
|
|
38
|
+
"react": "^19.0.0",
|
|
39
|
+
"react-dom": "^19.0.0",
|
|
23
40
|
"ws": "^8.18.0"
|
|
24
41
|
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/node": "^22.10.0",
|
|
44
|
+
"@types/react": "^19.0.0",
|
|
45
|
+
"@types/react-dom": "^19.0.0",
|
|
46
|
+
"@types/ws": "^8.5.13",
|
|
47
|
+
"@vitejs/plugin-react": "^4.3.0",
|
|
48
|
+
"eslint": "^10.0.0",
|
|
49
|
+
"tsx": "^4.19.0",
|
|
50
|
+
"typescript": "~5.9.0",
|
|
51
|
+
"typescript-eslint": "^8.71.0",
|
|
52
|
+
"vite": "^6.0.0"
|
|
53
|
+
},
|
|
25
54
|
"engines": {
|
|
26
|
-
"node": ">=18
|
|
55
|
+
"node": ">=18"
|
|
27
56
|
},
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
|
|
33
|
-
"
|
|
34
|
-
|
|
35
|
-
"
|
|
36
|
-
|
|
37
|
-
"keywords": [
|
|
38
|
-
"websocket",
|
|
39
|
-
"bridge",
|
|
40
|
-
"http",
|
|
41
|
-
"proxy",
|
|
42
|
-
"nx"
|
|
43
|
-
],
|
|
44
|
-
"license": "MIT"
|
|
57
|
+
"repository": {
|
|
58
|
+
"type": "git",
|
|
59
|
+
"url": "git+https://github.com/On-DevPlan/mn-ws.git",
|
|
60
|
+
"directory": "client"
|
|
61
|
+
},
|
|
62
|
+
"homepage": "https://github.com/On-DevPlan/mn-ws#readme",
|
|
63
|
+
"bugs": {
|
|
64
|
+
"url": "https://github.com/On-DevPlan/mn-ws/issues"
|
|
65
|
+
}
|
|
45
66
|
}
|
package/README.md
DELETED
|
@@ -1,251 +0,0 @@
|
|
|
1
|
-
# mn-ws —— HTTP ⇄ WebSocket 双向桥
|
|
2
|
-
|
|
3
|
-
把本机跑的 **nx 系列服务**(nx-kv / nx-rh / nx-sk …)经一条 WebSocket 长连接,
|
|
4
|
-
桥给**只能发普通 HTTP 的远端客户端**。
|
|
5
|
-
|
|
6
|
-
**不修改任何现有 nx 服务。**
|
|
7
|
-
|
|
8
|
-
```
|
|
9
|
-
┌──────────────────────────┐
|
|
10
|
-
│ 客户端(curl / fr / Worker)│
|
|
11
|
-
└────────────┬─────────────┘
|
|
12
|
-
│ HTTP(带鉴权头 + 应用标识)
|
|
13
|
-
▼
|
|
14
|
-
┌────────────────────────────────────────┐
|
|
15
|
-
│ mn-ws server(公网机) :7777 │
|
|
16
|
-
│ HTTP 面:/health /stats /clients │
|
|
17
|
-
│ /<app>/* ← 桥接入口 │
|
|
18
|
-
│ WS 面:/ws(与 HTTP 同端口共存) │
|
|
19
|
-
└────────────┬───────────────────────────┘
|
|
20
|
-
│ WebSocket(出向连接,穿 NAT)
|
|
21
|
-
▼
|
|
22
|
-
┌────────────────────────────────────────┐
|
|
23
|
-
│ mn-ws client(本机,跑在 nx 那台机器) │
|
|
24
|
-
│ 收到帧 → 还原 HTTP → 打 127.0.0.1:端口 │
|
|
25
|
-
└────────────┬───────────────────────────┘
|
|
26
|
-
▼ HTTP (loopback)
|
|
27
|
-
nx-kv / nx-rh / …
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
---
|
|
31
|
-
|
|
32
|
-
## 快速上手
|
|
33
|
-
|
|
34
|
-
```bash
|
|
35
|
-
# ── 公网机:起中继服务(必须有鉴权,否则拒绝启动)──
|
|
36
|
-
npx mn-ws server --host 0.0.0.0 --port 7777 --token <门禁TOKEN>
|
|
37
|
-
|
|
38
|
-
# ── 本机:起转发,把要桥的应用列成白名单 ──
|
|
39
|
-
npx mn-ws client --url ws://<服务器>:7777/ws --token <门禁TOKEN> \
|
|
40
|
-
--apps nx-kv,nx-rh
|
|
41
|
-
|
|
42
|
-
# ── 远端客户端:直接当普通 HTTP 服务用 ──
|
|
43
|
-
curl -H 'X-Mn-Ws-Token: <门禁TOKEN>' \
|
|
44
|
-
'http://<服务器>:7777/nx-kv/api/todo/list'
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
**本地开发**(还没发布到 npm 时):
|
|
48
|
-
|
|
49
|
-
```bash
|
|
50
|
-
cd mn-ws && npm install
|
|
51
|
-
node bin/index.js server --port 7777 --token dev
|
|
52
|
-
node bin/index.js client --url ws://127.0.0.1:7777/ws --token dev --apps nx-kv
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
---
|
|
56
|
-
|
|
57
|
-
## 命令
|
|
58
|
-
|
|
59
|
-
| 命令 | 说明 |
|
|
60
|
-
|---|---|
|
|
61
|
-
| `mn-ws server` | 启动中继服务(公网半):HTTP 与 WS 同端口共存 |
|
|
62
|
-
| `mn-ws client` | 启动本机转发:连上 server 并注册可桥接的应用 |
|
|
63
|
-
| `mn-ws client web` | 本地 Web 面板(只监听 127.0.0.1):可桥接清单 + 状态 + API 试验台 |
|
|
64
|
-
| `mn-ws client apps` | 列出可桥接的应用(白名单 + 本地端口存活探测) |
|
|
65
|
-
| `mn-ws client catalog` | 拉取远端的元信息目录(登录后看哪些能路由) |
|
|
66
|
-
| `mn-ws client status` | 查看桥接连接状态 |
|
|
67
|
-
| `mn-ws auth login` | 统一登录(go-ah auth-hub),与 mn-rt 的 `auth login` 同款流程 |
|
|
68
|
-
| `mn-ws auth list\|switch` | 查看 / 切换本机已存账号 |
|
|
69
|
-
| `mn-ws auth status\|logout` | 查看 / 清除登录凭据 |
|
|
70
|
-
| `mn-ws doctor` | 环境自检:端口占用 / 鉴权 / 应用可达性 / server 连通性 |
|
|
71
|
-
|
|
72
|
-
常用选项:
|
|
73
|
-
|
|
74
|
-
```
|
|
75
|
-
--url <ws://...> server 半地址
|
|
76
|
-
--server-http <url> 统一登录用的 HTTP 基址(默认从 --url 推导)
|
|
77
|
-
--host <h> 监听地址(server 默认 0.0.0.0;本地面板固定 127.0.0.1)
|
|
78
|
-
--port <n> server 端口(默认 7777)
|
|
79
|
-
--web-port <n> 本地面板端口(默认 7777)
|
|
80
|
-
--token <TOKEN> 注册门禁(server 与 client 配同一个)
|
|
81
|
-
--apps <a,b,c> 只桥接这些应用(白名单;不给就一个都不桥)
|
|
82
|
-
--ports <p,q> 只桥接这些本地端口(从已知表反查应用名)
|
|
83
|
-
--app-map <a=url,...> 指定应用的本地基址(如 nx-kv=http://127.0.0.1:7999)
|
|
84
|
-
--device-name <名> 桥的显示名
|
|
85
|
-
--no-open auth login 只打印授权地址、不弹浏览器
|
|
86
|
-
--insecure server 允许无鉴权启动(**仅限本机自测**)
|
|
87
|
-
--log-level <level> debug | info | warn | error
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
---
|
|
91
|
-
|
|
92
|
-
## 远端怎么用:先看目录,再选,再路由
|
|
93
|
-
|
|
94
|
-
远端客户端拿到凭据后的标准动作是**先拉目录**(知道能路由到什么),再按应用标识发起请求:
|
|
95
|
-
|
|
96
|
-
```bash
|
|
97
|
-
# ① 拉元信息目录(哪些能路由、分别在哪个端口、由哪台桥承载)
|
|
98
|
-
curl -H 'X-Mn-Ws-Token: <门禁TOKEN>' http://<服务器>:7777/api/catalog
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
```json
|
|
102
|
-
{
|
|
103
|
-
"ok": true,
|
|
104
|
-
"count": 2,
|
|
105
|
-
"apps": [
|
|
106
|
-
{"app":"nx-kv","title":"KV 清单中枢","port":7877,"base":"http://127.0.0.1:7877",
|
|
107
|
-
"bridgeId":"host-a","connectedAt":"2026-11-12T11:00:00.000Z"},
|
|
108
|
-
{"app":"nx-rh","title":"仓库与 Skill 中枢","port":7800,"base":"http://127.0.0.1:7800",
|
|
109
|
-
"bridgeId":"host-b","connectedAt":"2026-11-12T11:00:01.000Z"}
|
|
110
|
-
]
|
|
111
|
-
}
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
命令行等价物:`npx mn-ws client catalog --url ws://<服务器>:7777/ws --token <TOKEN>`
|
|
115
|
-
|
|
116
|
-
② 按目录里选中的应用发起请求 —— **路径前缀**与**请求头**两种写法等价:
|
|
117
|
-
|
|
118
|
-
```bash
|
|
119
|
-
# 路径前缀
|
|
120
|
-
curl -H 'X-Mn-Ws-Token: $TOKEN' 'http://<服务器>:7777/nx-kv/api/todo/list'
|
|
121
|
-
|
|
122
|
-
# 请求头(路径原样,适合已有客户端不想改 URL)
|
|
123
|
-
curl -H 'X-Mn-Ws-Token: $TOKEN' -H 'X-Nx-App: nx-kv' \
|
|
124
|
-
'http://<服务器>:7777/api/todo/list'
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
两者冲突 → `400 app_conflict`(**不猜**:猜错等于把请求打到别的服务上)。
|
|
128
|
-
|
|
129
|
-
### 多路复用
|
|
130
|
-
|
|
131
|
-
每条 client half 的 WS 连接是一个独立的「桥」。server 半维护 `app → ws` 的路由表,
|
|
132
|
-
**请求按应用标识落到对应的那条 WS 上**:
|
|
133
|
-
|
|
134
|
-
```
|
|
135
|
-
┌── WS#1(host-a:nx-kv)──▶ 127.0.0.1:7877
|
|
136
|
-
客户端 ──HTTP──▶ server ─┤
|
|
137
|
-
└── WS#2(host-b:nx-rh)──▶ 127.0.0.1:7800
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
同机跑两个 client、或多台机器各跑一个,都由这张表分流,互不串台。
|
|
141
|
-
多个 client 声明同一个 app 时**后来者顶替前者**(同机跑两份 nx 是异常状态,
|
|
142
|
-
顶替比随机命中更可预期),被顶替的 client 会收到 `replaced` 通知。
|
|
143
|
-
|
|
144
|
-
---
|
|
145
|
-
|
|
146
|
-
## 应用标识(app)
|
|
147
|
-
|
|
148
|
-
一个请求该去哪个本地端口,靠**应用标识**区分。两种都支持,**路径前缀优先**:
|
|
149
|
-
|
|
150
|
-
```
|
|
151
|
-
① POST /nx-kv/api/todo/add → app=nx-kv, path=/api/todo/add
|
|
152
|
-
② POST /api/todo/add (X-Nx-App: nx-kv) → app=nx-kv, path=/api/todo/add
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
- 路径前缀优先:客户端零改造(curl 直接打)、自定义头不会被中间反代剥掉、一个请求只对应一个 app
|
|
156
|
-
- 两者冲突 → `400 app_conflict`(**不猜**:猜错等于把请求打到别的服务上)
|
|
157
|
-
- 都缺 → `400 app_required`
|
|
158
|
-
|
|
159
|
-
### 哪些可以桥接
|
|
160
|
-
|
|
161
|
-
**白名单说了算**:只有 `--apps` / `--app-map` 里列出的应用才会被桥。
|
|
162
|
-
端口探测(`mn-ws client apps`)**只用来显示状态,不会自动开桥** —— 扫到端口在监听就自动开桥,等于把整台机器暴露出去。
|
|
163
|
-
|
|
164
|
-
已知 nx 服务与默认端口(2026-11-12 实测各仓 `core/paths.js`):
|
|
165
|
-
|
|
166
|
-
| app | 端口 | 服务 |
|
|
167
|
-
|---|---|---|
|
|
168
|
-
| nx-rh | 7800 | 仓库与 Skill 中枢 |
|
|
169
|
-
| nx-rp | 7820 | 外部上下文 |
|
|
170
|
-
| nx-sk | 7866 | 个人资源与密钥 |
|
|
171
|
-
| nx-ft | 7869 | 前端资源管理 |
|
|
172
|
-
| nx-kv | 7877 | KV 清单中枢 |
|
|
173
|
-
| nx-nx | 7880 | 项目生成器 |
|
|
174
|
-
| nx-kn | 7881 | Obsidian 知识库 |
|
|
175
|
-
| nx-ditto | 7883 | ditto |
|
|
176
|
-
| nx-pg | 7888 | 画布工作台 |
|
|
177
|
-
|
|
178
|
-
端口变了就用 `--app-map nx-kv=http://127.0.0.1:7999` 覆盖。
|
|
179
|
-
|
|
180
|
-
---
|
|
181
|
-
|
|
182
|
-
## 三层鉴权
|
|
183
|
-
|
|
184
|
-
| 层 | 管什么 | 载体 |
|
|
185
|
-
|---|---|---|
|
|
186
|
-
| **L1 通道层** | 谁能用这个桥 | `X-Mn-Ws-Token` 头(设备令牌 或 静态门禁) |
|
|
187
|
-
| **L2 路由层** | 请求的 app 必须在该桥的注册表里,且属主匹配 | 应用标识 |
|
|
188
|
-
| **L3 应用层** | nx 自己的那套鉴权 | `Authorization` / `Cookie` **原样透传** |
|
|
189
|
-
|
|
190
|
-
**两层凭据必须分开** —— `Authorization` 是 nx 的地盘(nx-kv 就是读 `Authorization: Bearer`),
|
|
191
|
-
L1 走独立头 `X-Mn-Ws-Token`,否则两边令牌互相打架 → 401。
|
|
192
|
-
|
|
193
|
-
`X-Mn-Ws-Token` 是桥接层内部事务,**不会**被透传给目标服务。
|
|
194
|
-
|
|
195
|
-
### 设备令牌 vs 静态门禁
|
|
196
|
-
|
|
197
|
-
| | 设备令牌(统一登录) | 静态门禁 `--token` |
|
|
198
|
-
|---|---|---|
|
|
199
|
-
| 隔离 | 按用户 —— 只能访问自己名下的桥 | 共享密钥 —— 等价全量访问 |
|
|
200
|
-
| 吊销 | 可单独吊销 | 换密钥才算换 |
|
|
201
|
-
| 建议 | **生产用这个** | 仅自测 / 单人使用 |
|
|
202
|
-
|
|
203
|
-
---
|
|
204
|
-
|
|
205
|
-
## 安全
|
|
206
|
-
|
|
207
|
-
- **server 无鉴权直接拒绝启动**(`--insecure` 才放行,且会打醒目警告)
|
|
208
|
-
- **无鉴权时不允许绑 0.0.0.0**
|
|
209
|
-
- **端口占用预检**:被占就明确报错退出,**不静默顺延**(顺延会让客户端连错端口,两边对不上账)
|
|
210
|
-
- **本地面板只绑 127.0.0.1**
|
|
211
|
-
- **每次经桥的请求落审计**(`~/.mn-ws/data/requests.jsonl`)
|
|
212
|
-
- **跨站防护不被绕过**:Origin 头在桥接时被剥离 —— 桥接请求本来就来自本机,
|
|
213
|
-
剥掉是恢复真实语义(不剥的话 nx-kv 的 `originAllowed()` 会 403)
|
|
214
|
-
|
|
215
|
-
---
|
|
216
|
-
|
|
217
|
-
## 开发
|
|
218
|
-
|
|
219
|
-
```bash
|
|
220
|
-
npm install
|
|
221
|
-
node test/unit.js # 协议 + 端到端透传 + 多路复用 + 元信息目录 + 默认端口(34 例)
|
|
222
|
-
node test/smoke-cli.js # 真实起两个进程打穿(11 例)
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
设计文档:[docs/design.md](docs/design.md) · 部署手册:[docs/DEPLOY.md](docs/DEPLOY.md)
|
|
226
|
-
|
|
227
|
-
---
|
|
228
|
-
|
|
229
|
-
## 部署(server 半)
|
|
230
|
-
|
|
231
|
-
```bash
|
|
232
|
-
# 本地构建镜像
|
|
233
|
-
docker build -f Dockerfile -t mn-ws-server .
|
|
234
|
-
|
|
235
|
-
# 起服务(没有鉴权会被拒绝启动)
|
|
236
|
-
docker run -d --name mn_ws_server -p 7777:7777 \
|
|
237
|
-
-e MN_WS_TOKEN=<门禁> -e MN_WS_JWT_SECRET=<随机> \
|
|
238
|
-
-e MN_WS_AUTH_ENABLED=1 -e MN_WS_ISSUER=http://47.110.80.47:8082 \
|
|
239
|
-
-v mn_ws_data:/app/data mn-ws-server:latest
|
|
240
|
-
```
|
|
241
|
-
|
|
242
|
-
线上部署走 GitHub Actions(`.github/workflows/deploy.yml`):
|
|
243
|
-
push 到 `main` → 构建 → `docker save` + gzip → scp → 服务器 `docker load` → 重启 → 验活。
|
|
244
|
-
**镜像不走 GHCR** —— 服务器 pull 实测太慢,改成镜像直传,部署时间可控。
|
|
245
|
-
|
|
246
|
-
发版走附注 tag(`.github/workflows/release.yml`):
|
|
247
|
-
|
|
248
|
-
```bash
|
|
249
|
-
git tag -a v0.1.0 -m "v0.1.0" && git push origin v0.1.0
|
|
250
|
-
# → 校验 tag 与 package.json 版本一致 → 跑测试 → npm publish → 建 GitHub Release
|
|
251
|
-
```
|