rexp-web 0.0.0-stage → 0.1.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.
@@ -0,0 +1 @@
1
+ :root{--text: #263239;--muted: #737d81;--line: #e1e5e4;--glass: #fff;--accent: #287a64;font-family:-apple-system,BlinkMacSystemFont,SF Pro Text,PingFang SC,Microsoft YaHei,Segoe UI,sans-serif;font-size:16px;line-height:1.45;color:var(--text);background:#f4f5f3;font-synthesis:none;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased}*{box-sizing:border-box}html,body,#root{height:100%}body{margin:0;min-width:320px;overflow:hidden}button{font:inherit}button:focus-visible{outline:2px solid #167e84;outline-offset:2px}.app-shell{position:relative;display:flex;height:100vh;height:100dvh;overflow:hidden;color:var(--text);background:#f4f5f3}.ambient{position:absolute;border-radius:50%;filter:blur(14px);pointer-events:none}.ambient-one{width:300px;height:300px;top:120px;right:-60px;background:#5ab2c81f}.ambient-two{width:300px;height:300px;bottom:-130px;left:16%;background:#9c89cf1f}.sidebar{position:relative;z-index:2;display:flex;flex-direction:column;flex:0 0 clamp(13.5rem,18vw,15rem);width:clamp(13.5rem,18vw,15rem);height:100%;min-height:0;overflow:hidden;padding:28px 14px 18px;border-right:1px solid #263239;background:#1b252b;transition:flex-basis .25s ease,width .25s ease,padding .25s ease}.sidebar-collapsed .sidebar{flex-basis:0;width:0;padding-inline:0;border-color:transparent}.brand{display:flex;align-items:center;gap:11px;margin:0 8px 38px}.brand-symbol{display:grid;place-items:center;flex:none;width:36px;height:36px;border:1px solid #627b70;border-radius:8px;background:#304a3e;color:#cee4d3}.brand strong{display:block;color:#f2f5f3;font-size:20px;font-weight:600;letter-spacing:-.025em}.brand small{display:block;margin-top:3px;color:#a2afb6;font-size:12px}.sidebar-group-label,.sidebar-section-head{color:#a2afb6;font-size:12px;font-weight:500;letter-spacing:.06em}.sidebar-group-label{padding:0 11px 10px}.nav-item{display:flex;align-items:center;gap:11px;width:100%;padding:12px 13px;margin-bottom:3px;border:1px solid transparent;border-radius:7px;background:transparent;color:#b6c2c7;text-align:left;cursor:pointer;font-size:14px;font-weight:500}.nav-item:hover,.nav-item.active{border-color:#43554e;background:#2f413a;color:#edf3ed}.sidebar-section-head{display:flex;align-items:center;justify-content:space-between;margin:28px 0 10px;padding:0 10px}.sidebar-section-head button{display:grid;place-items:center;width:27px;height:27px;padding:0;border:0;border-radius:6px;background:transparent;color:#a2afb6;cursor:pointer}.sidebar-section-head button:hover{background:#236f7e17}.server-list{display:grid;gap:5px}.sidebar-node-picker>.server-list{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;align-content:start;padding-right:3px}.server-item{display:flex;flex-direction:column;gap:11px;width:100%;padding:14px 11px;border:1px solid transparent;border-radius:7px;background:transparent;color:var(--text);text-align:left;cursor:pointer;transition:background .16s,border-color .16s}.server-item:hover{border-color:#4a5b53;background:#2a383c}.server-item.selected{border-color:#50675b;background:#2c3c35;box-shadow:none}.server-item-top{display:flex;align-items:center;gap:8px;min-width:0}.server-icon{display:grid;place-items:center;flex:none;width:28px;height:28px;border-radius:5px;background:#ffffff0c;color:#b2c8bc}.server-item-names{display:block;min-width:0}.server-item-names strong,.server-item-names small{display:block;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.server-item-names strong{color:#eef2ef;font-size:14px;font-weight:500}.server-item-names small{margin-top:4px;color:#a2afb6;font-size:12px}.server-item-bottom{display:flex;justify-content:space-between;align-items:center;gap:6px}.status{display:inline-flex;align-items:center;gap:6px;white-space:nowrap;color:#168669;font-size:12px;font-weight:700}.status-dot{width:7px;height:7px;border-radius:50%;background:currentColor;box-shadow:0 0 0 3px #1886691a}.status-busy{color:#a96c1f}.status-busy .status-dot{box-shadow:0 0 0 3px #a96c1f1c}.status-offline{color:#b4564b}.status-offline .status-dot{box-shadow:0 0 0 3px #b250621a}.status-checking{color:#6b7785}.status-checking .status-dot{box-shadow:0 0 0 3px #6b77851a}.server-secondary{overflow:hidden;color:#b0bebf;font-size:12px;white-space:nowrap;text-overflow:ellipsis}.sidebar-foot{display:flex;align-items:center;flex:none;gap:8px;margin-top:auto;padding:18px 9px 2px;border-top:1px solid #ffffff13;color:#a2afb6;font-size:12px}.foot-light{width:6px;height:6px;border-radius:50%;background:#3caea4}.main{position:relative;z-index:1;display:flex;flex:1;flex-direction:column;min-width:0;min-height:0;height:100%;overflow:hidden;padding-inline:clamp(1rem,2.3vw,2.5rem)}.topbar{display:flex;flex:none;align-items:center;justify-content:space-between;height:73px;border-bottom:1px solid var(--line)}.topbar-actions{display:flex;align-items:center;gap:9px}.web-settings{position:relative}.web-settings-trigger{display:grid;place-items:center;padding:.5rem;border:1px solid var(--line);border-radius:8px;background:#fff;color:#52695c;cursor:pointer}.web-settings-popover{position:absolute;top:calc(100% + .75rem);right:0;z-index:100;width:min(24rem,calc(100vw - 2rem));padding:1.2rem;background:#fff;border:1px solid var(--line);border-radius:14px;box-shadow:0 16px 48px #1c30252b}.web-settings-heading,.web-settings-row{display:flex;align-items:center;justify-content:space-between;gap:1rem}.web-settings-heading{margin-bottom:1.2rem}.web-settings-heading button{border:0;background:transparent;color:#65766e;cursor:pointer}.web-settings-popover strong{font-size:14px}.web-settings-popover p{font-size:13px;line-height:1.6;margin:.35rem 0 0;color:#6d7b74}.web-settings-popover .web-settings-error{color:#a83d36}.web-autostart-switch{flex:none;width:42px;height:25px;padding:3px;border:0;border-radius:20px;background:#cbd3cd;cursor:pointer}.web-autostart-switch span{display:block;height:19px;width:19px;background:#fff;border-radius:50%;transition:transform .18s;box-shadow:0 1px 3px #0002}.web-autostart-switch[aria-checked=true]{background:#377555}.web-autostart-switch[aria-checked=true] span{transform:translate(17px)}.web-autostart-switch:disabled{cursor:wait;opacity:.6}.web-bookmark{display:flex;align-items:flex-start;gap:.65rem;margin-top:1rem;padding-top:1rem;border-top:1px solid var(--line);color:#52695c}.notification-toggle{display:inline-flex;align-items:center;justify-content:center;gap:7px;min-height:34px;padding:7px 11px;border:1px solid var(--line);border-radius:6px;background:#fff;color:#626e72;cursor:pointer;font-size:13px;font-weight:500;white-space:nowrap}.notification-toggle:hover{border-color:#187c8459;color:#176e78}.notification-toggle.enabled{border-color:#b7cebf;background:#edf4ee;color:#346c52}.notification-error{flex:none;padding:7px 14px;border-bottom:1px solid rgba(178,80,98,.15);background:#fff4f6e6;color:#a34053;font-size:12px}.content-scroll{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;scrollbar-gutter:stable;padding-bottom:36px}.topbar-path{display:flex;align-items:center;gap:9px;color:#6d8693;font-size:13px}.sidebar-toggle{display:grid;place-items:center;flex:none;width:36px;height:36px;margin-left:-5px;border:1px solid transparent;border-radius:9px;background:transparent;color:#638595;cursor:pointer}.sidebar-toggle:hover{border-color:#187c8429;background:#ffffff8f;color:#1b7380}.topbar-path strong{color:#263239;font-weight:700}.path-divider{color:#9aafb8}.preview-pill{display:flex;align-items:center;gap:7px;padding:7px 10px;border:0;border-radius:6px;background:transparent;color:#737e80;font-size:12px}.preview-pill span{width:6px;height:6px;border-radius:50%;background:#38a9a0}.dashboard,.detail-page{width:100%;max-width:1120px;margin:0 auto;animation:page-enter .32s cubic-bezier(.2,.8,.2,1) both}.page-title{display:flex;align-items:flex-end;justify-content:space-between;gap:18px;margin:32px 0 26px}.eyebrow{display:block;margin-bottom:9px;color:#7c8583;font-size:12px;font-weight:500;letter-spacing:.08em}.page-title h1{margin:0;color:#263239;font-size:clamp(1.8rem,2.6vw,2.35rem);line-height:1.25;font-weight:600;letter-spacing:-.035em}.page-title p{margin:6px 0 0;color:#737d81;font-size:14px}.page-title>.status{padding:7px 11px;border:1px solid var(--line);border-radius:99px;background:#ffffff94}.dashboard-grid{display:grid;gap:20px}.dashboard-grid>.glass-panel{min-height:clamp(16rem,30dvh,22rem)}.glass-panel{display:flex;flex-direction:column;border:1px solid #dce2de;border-radius:10px;background:#fff;box-shadow:0 2px 3px #20332a03;padding:clamp(1.1rem,2vw,1.7rem)}.gpu-card{container-type:inline-size}.clickable-panel{cursor:pointer;transition:border-color .22s,box-shadow .22s,transform .22s}.clickable-panel:hover,.clickable-panel:focus-visible{border-color:#9cb7a7;box-shadow:0 4px 14px #253d3110;transform:none}.clickable-panel:focus-visible{outline:2px solid #16888d;outline-offset:3px}.panel-expand-mark{display:grid;place-items:center;flex:none;width:27px;height:27px;border-radius:8px;color:#6d9ba5;transition:transform .22s,background .22s}.clickable-panel:hover .panel-expand-mark,.clickable-panel:focus-visible .panel-expand-mark{transform:translate(2px,-2px);background:#2d89911a;color:#187f85}.panel-heading{display:flex;align-items:center;justify-content:space-between;gap:11px;margin-bottom:20px;padding-bottom:17px;border-bottom:1px solid #e9ece9}.panel-heading-title{display:flex;align-items:center;gap:9px;min-width:0}.panel-icon{display:grid;place-items:center;flex:none;width:28px;height:28px;border:0;border-radius:0;background:transparent;color:#64776a}.panel-heading h2{margin:0;color:#263239;font-size:17px;line-height:1.3;font-weight:600;letter-spacing:-.02em}.panel-subtitle{display:block;margin-top:2px;color:#737d81;font-size:12px}.back-button{display:inline-flex;align-items:center;justify-content:center;gap:5px;flex:none;min-height:31px;padding:6px 10px;border:1px solid rgba(41,89,109,.18);border-radius:9px;background:#ffffff8f;color:#426777;cursor:pointer;font-size:12px}.back-button:hover{border-color:#1b7d855e;background:#e4f6f5;color:#1b6d73}.gpu-list{display:grid;gap:10px}.gpu-more{padding:11px 2px 0;color:#5c7d8b;font-size:13px;text-align:center}.gpu-item{display:grid;grid-template-columns:minmax(0,.8fr) minmax(0,2fr) minmax(0,.7fr);align-items:center;gap:clamp(.7rem,1.5vw,1.2rem);min-width:0;padding:clamp(.85rem,1.2vw,1.1rem);border:1px solid #e5e9e5;border-radius:6px;background:#fafbf9}.gpu-summary-identity{display:flex;flex-direction:column;gap:5px;min-width:0}.gpu-summary-identity strong{overflow:hidden;color:#29373d;font-size:14px;white-space:nowrap;text-overflow:ellipsis}.gpu-summary-meters{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,12rem),1fr));gap:clamp(.6rem,1.4vw,1.1rem);min-width:0}.gpu-summary-facts{display:flex;flex-direction:column;align-items:flex-end;gap:4px;color:#557483;font-size:12px;font-variant-numeric:tabular-nums;white-space:nowrap}.gpu-summary-facts span{display:inline-flex;align-items:center;gap:4px}.gpu-index{color:#73909c;font-size:12px;letter-spacing:.05em}.gpu-meter-label{display:flex;justify-content:space-between;gap:8px;color:#737d81;font-size:12px}.gpu-meter-label strong{color:#287a64;font-size:13px;font-weight:700;font-variant-numeric:tabular-nums;white-space:nowrap}.usage-track,.table-progress{overflow:hidden;border-radius:2px;background:#e8ede8}.usage-track{height:7px;margin:7px 0 0}.usage-track span,.table-progress span{display:block;height:100%;border-radius:2px;background:#5b9b7d}.table-wrap{overflow-x:auto}.runs-table{width:100%;min-width:900px;table-layout:fixed;border-collapse:collapse;text-align:left}.runs-table th{padding:9px 10px;border-bottom:1px solid rgba(43,88,106,.16);color:#526f7e;font-size:13px;font-weight:600}.runs-table td{padding:13px 10px;border-bottom:1px solid rgba(43,88,106,.1);color:#536168;font-size:14px;white-space:nowrap}.runs-table th:nth-child(1){width:11%}.runs-table th:nth-child(2){width:32%}.runs-table th:nth-child(3){width:8%}.runs-table th:nth-child(4){width:14%}.runs-table th:nth-child(5){width:15%}.runs-table th:nth-child(6){width:20%}.runs-table tr:last-child td{border-bottom:0}.runs-table .run-selectable{cursor:pointer;transition:background-color .18s ease}.runs-table .run-selectable:hover,.runs-table .run-selectable:focus-within{background:#3b9a9f17}.run-detail-trigger{display:inline-flex;align-items:center;gap:4px;padding:4px 3px;border:0;border-radius:5px;background:transparent;color:#287a64;cursor:pointer;font-size:inherit;font-weight:700;white-space:nowrap}.run-detail-trigger svg{transition:transform .18s ease}.run-selectable:hover .run-detail-trigger svg,.run-detail-trigger:focus-visible svg{transform:translate(3px)}.run-id{color:#48635b!important;font-weight:700}.run-description-cell{min-width:0}.run-description{display:block;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#29373d;font-weight:650}.run-description.missing,.runs-preview-desc.missing{color:#96aab2;font-weight:500}.run-gpu-pill{display:inline-flex;align-items:center;max-width:100%;padding:3px 8px;border:1px solid rgba(50,121,135,.15);border-radius:8px;background:#dcf0f2a3;color:#2f7080;font-size:12px;font-weight:700;font-variant-numeric:tabular-nums}.runs-preview-list{display:grid;gap:8px;min-width:0}.runs-preview-row{min-width:0;padding:10px 12px;border:1px solid rgba(43,88,106,.11);border-radius:12px;background:#fff6}.runs-preview-top,.runs-preview-meta{display:flex;justify-content:space-between;align-items:center;gap:12px}.runs-preview-top strong{color:#48635b;font-size:13px}.runs-preview-desc{display:block;min-width:0;margin:7px 0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#29373d;font-size:14px;font-weight:650}.runs-preview-meta{color:#7e888c;font-size:12px;font-variant-numeric:tabular-nums;flex-wrap:wrap}.runs-preview-submitted{flex-basis:100%}.run-submitted{font-variant-numeric:tabular-nums}.run-status{display:inline-flex;align-items:center;gap:6px;font-weight:700}.run-status:before{content:"";width:6px;height:6px;border-radius:50%;background:currentColor}.run-running{color:#287a64}.run-pending,.run-waiting_for_resource{color:#a47833}.run-completed{color:#718895}.run-failed,.run-cancelled{color:#b4564b}.run-paused,.run-recoverable{color:#6e7391}.progress-column{font-variant-numeric:tabular-nums}.table-progress{width:88px;height:4px;margin-top:6px}.detail-note{display:flex;align-items:center;gap:8px;margin:17px 2px;color:#7e888c;font-size:12px}.back-button{margin-top:24px}.detail-page .page-title{margin-top:21px}.detail-panel{min-height:min(65dvh,45rem)}.run-detail-overlay{position:fixed;top:0;right:0;bottom:0;left:0;z-index:20;display:flex;justify-content:flex-end;background:#17252c52;animation:run-detail-fade .2s ease both}.run-detail-drawer{display:flex;flex-direction:column;width:min(100%,36rem);height:100%;min-height:0;overflow:hidden;border-left:1px solid #dce2de;border-radius:0;background:#fafbf9;box-shadow:-12px 0 40px #1a292917;animation:run-detail-slide .25s cubic-bezier(.2,.8,.2,1) both}.run-detail-head{display:flex;align-items:flex-start;justify-content:space-between;gap:14px;flex:none;padding:clamp(1.2rem,2.4vw,1.7rem);border-bottom:1px solid rgba(42,94,109,.14)}.run-detail-head>div:first-child{min-width:0}.run-detail-head h2{margin:0 0 10px;color:#263239;font-size:clamp(1.1rem,1.5vw,1.35rem);line-height:1.35;overflow-wrap:anywhere}.run-detail-actions{display:flex;gap:6px;flex:none}.run-detail-actions button{display:grid;place-items:center;width:34px;height:34px;padding:0;border:1px solid rgba(42,94,109,.16);border-radius:9px;background:#ffffffb3;color:#4d7080;cursor:pointer}.run-detail-actions button:hover{color:#176c74;background:#e1f3f2}.run-detail-body{flex:1;min-height:0;overflow-y:auto;padding:0 clamp(1.2rem,2.4vw,1.7rem) 1.7rem}.run-detail-group{padding-top:1.35rem}.run-detail-group h3{margin:0 0 10px;color:#2c7280;font-size:13px;letter-spacing:.04em}.run-detail-group dl{margin:0;overflow:hidden;border:1px solid var(--line);border-radius:7px;background:#fff}.run-detail-field{display:grid;grid-template-columns:minmax(5.4rem,31%) minmax(0,1fr);gap:12px;align-items:start;padding:11px 13px;border-bottom:1px solid rgba(42,94,109,.1);font-size:13px}.run-detail-field:last-child{border-bottom:0}.run-detail-field dt{color:#698391}.run-detail-field dd{min-width:0;margin:0;color:#39484e;font-weight:600;overflow-wrap:anywhere;white-space:pre-wrap}.run-detail-description{grid-template-columns:1fr;gap:6px;background:#f3f6f1}.run-detail-description dd{color:#1e5260;line-height:1.55}.run-detail-error{display:flex;flex-direction:column;align-items:center;min-height:18rem;justify-content:center}.run-detail-error .back-button{margin-top:0}.run-detail-loading{display:grid;gap:16px;padding-top:1.5rem}.run-detail-skeleton-title{width:45%;height:18px;margin-bottom:8px}.run-detail-skeleton-line{width:100%;height:36px}.run-detail-skeleton-line:nth-child(3n){width:78%}@keyframes run-detail-fade{0%{opacity:0}to{opacity:1}}@keyframes run-detail-slide{0%{transform:translate(28px)}to{transform:translate(0)}}.gpu-detail-summary{display:flex;align-items:baseline;flex-wrap:wrap;gap:8px 16px;padding:2px 2px 15px;color:#728a96;font-size:13px}.gpu-detail-summary strong{color:#25485a;font-size:16px}.gpu-detail-list{display:grid;gap:10px}.gpu-detail-row{min-width:0;padding:17px 19px;border:1px solid rgba(38,86,105,.14);border-radius:13px;background:#ffffff82}.gpu-detail-header{display:flex;align-items:center;justify-content:space-between;gap:12px}.gpu-detail-header h3{margin:3px 0 0;color:#234658;font-size:16px}.gpu-detail-header>strong{color:#197f84;font-size:22px;font-variant-numeric:tabular-nums;white-space:nowrap}.gpu-detail-header>strong small{color:#6a8792;font-size:12px;font-weight:500}.gpu-detail-body{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,18rem),1fr));align-items:center;gap:clamp(1rem,2vw,1.6rem);margin-top:15px}.gpu-detail-meters{display:grid;gap:14px}.gpu-detail-meters .gpu-meter-label{font-size:13px}.gpu-detail-trend{min-width:0}.gpu-trend-label{display:flex;justify-content:space-between;color:#6f8995;font-size:12px}.gpu-trend-label span{color:#348b91}.gpu-trend-visual{min-width:0;margin-top:7px}.gpu-trend-values{display:flex;flex-wrap:wrap;gap:5px 15px;margin-bottom:7px;color:#597987;font-size:12px}.gpu-trend-values span{display:inline-flex;align-items:center;gap:5px;white-space:nowrap}.gpu-trend-values span:before{content:"";width:6px;height:6px;border-radius:50%;background:#249397}.gpu-trend-values .trend-memory:before{background:#7588c0}.gpu-trend-values strong{color:#2b5668;font-size:13px;font-variant-numeric:tabular-nums}.gpu-trend-plot{display:grid;grid-template-columns:33px minmax(0,1fr);align-items:stretch;gap:5px}.gpu-trend-y{display:flex;flex-direction:column;justify-content:space-between;padding:2px 0;color:#5f7b89;font-size:12px;font-variant-numeric:tabular-nums;text-align:right}.sparkline{display:block;width:100%;height:72px}.gpu-trend-times{display:flex;justify-content:space-between;margin:4px 1px 0 38px;color:#5f7b89;font-size:12px;font-variant-numeric:tabular-nums}.gpu-trend-pending{display:grid;place-items:center;min-height:66px;color:#7b929e;font-size:13px}.spark-grid{fill:none;stroke:#46728429;stroke-width:1}.spark-path{fill:none;stroke:#249397;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:round}.spark-path.secondary{stroke:#7588c0}.spark-hit{fill:transparent;stroke:none;pointer-events:all}.retry-link{padding:0;border:0;background:transparent;color:#1b7f88;cursor:pointer;font-size:inherit;font-weight:700;text-decoration:underline;text-underline-offset:2px}.gpu-facts{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,7.5rem),1fr));gap:12px;margin-top:16px;padding-top:14px;border-top:1px solid rgba(43,88,106,.12)}.gpu-facts div{min-width:0}.gpu-facts span{display:block;color:#7b929e;font-size:12px}.gpu-facts strong{display:block;overflow-wrap:anywhere;margin-top:5px;color:#31566a;font-size:13px;font-weight:700;font-variant-numeric:tabular-nums}.gpu-processes{margin-top:24px}.gpu-processes-head{display:flex;align-items:baseline;gap:10px;margin-bottom:10px}.gpu-processes-head h3{margin:0;color:#234658;font-size:16px}.gpu-processes-head span,.gpu-no-process{color:#708996;font-size:13px}.process-table{width:100%;min-width:1080px;border-collapse:collapse;text-align:left}.process-table th{padding:8px 10px;border-bottom:1px solid rgba(43,88,106,.16);color:#526f7e;font-size:13px;font-weight:600;white-space:nowrap}.process-table td{padding:10px;border-bottom:1px solid rgba(43,88,106,.1);color:#536168;font-size:13px;white-space:nowrap}.process-table .process-command{min-width:210px;max-width:310px;white-space:normal;overflow-wrap:anywhere}.gpu-no-process{margin:10px 0 0}.server-list.detailed{grid-template-columns:repeat(auto-fit,minmax(min(100%,15rem),1fr));gap:13px}.server-list.detailed .server-item{min-height:120px;padding:17px}.server-list.detailed .server-item-bottom{margin-top:auto}.empty-state{display:flex;flex:1;flex-direction:column;align-items:center;justify-content:center;min-height:0;width:100%;gap:.55rem;padding:clamp(1rem,3vw,1.5rem);color:#7e98a4;text-align:center}.empty-state>svg{width:2rem;height:2rem;padding:.4rem;border-radius:50%;background:#2e838d17;color:#438d95}.empty-state strong{color:#2b4a5a;font-size:clamp(.95rem,1.15vw,1.1rem)}.empty-state span{max-width:24rem;color:#708996;font-size:clamp(.82rem,1vw,.95rem)}.diagnostic{width:100%;max-width:44rem;text-align:left;font-size:.8rem;color:#7e888c}.diagnostic summary{cursor:pointer;width:fit-content;margin:.4rem auto;padding:.25rem .55rem;border-radius:.5rem}.diagnostic pre{margin:.5rem 0 0;max-height:14rem;overflow:auto;white-space:pre-wrap;overflow-wrap:anywhere;padding:.8rem;border:1px solid rgba(107,139,154,.15);border-radius:.7rem;background:#ffffff73;font-size:.75rem;line-height:1.6}.node-monitor-error{display:flex;flex-direction:column;gap:.4rem;margin:0 0 1rem;padding:1rem 1.2rem;border:1px solid rgba(176,128,64,.18);border-radius:1rem;background:#fff8ea99;color:#856c49;font-size:.9rem}.node-monitor-error .diagnostic summary{margin-left:0}.sidebar-empty{overflow-wrap:anywhere;padding:12px;color:#7e888c;font-size:13px;line-height:1.5}.startup-panel{max-width:1120px;margin:2rem auto 0}.skeleton{display:block;border-radius:7px;background:linear-gradient(100deg,#6991a11f 15%,#ffffffb8,#6991a11f 85%);background-size:220% 100%;animation:skeleton-shimmer 1.45s ease-in-out infinite}.skeleton-server{min-height:70px;justify-content:center}.skeleton-server-name{width:68%;height:12px}.skeleton-server-line{width:82%;height:9px}.skeleton-page-subtitle{width:min(275px,65vw);height:12px;margin-top:10px}.skeleton-page-status{width:70px;height:27px;border-radius:99px}.skeleton-gpu{display:flex;flex-direction:column;justify-content:center;min-height:99px}.skeleton-gpu-title{width:48%;height:12px}.skeleton-gpu-bar{width:100%;height:7px;margin-top:19px;border-radius:99px}.skeleton-gpu-meta{width:66%;height:9px;margin-top:13px}.gpu-skeleton-detail{display:grid;gap:10px}.skeleton-detail-row{display:flex;flex-direction:column;justify-content:center;gap:24px;min-height:190px}.skeleton-detail-row .skeleton-gpu-title{width:35%}.skeleton-detail-row .skeleton-gpu-bar{width:100%;margin:0}.skeleton-detail-row .skeleton-gpu-meta{width:60%;margin:0}.runs-skeleton{display:grid;gap:0}.skeleton-run-row{display:grid;grid-template-columns:1.5fr .7fr 1fr;align-items:center;gap:14px;min-height:52px;padding:9px 10px;border-bottom:1px solid rgba(43,88,106,.1)}.skeleton-run-row:last-child{border-bottom:0}.skeleton-run-name{width:72%;height:11px}.skeleton-run-status{width:60%;height:10px}.skeleton-run-progress{width:72%;height:10px}@keyframes skeleton-shimmer{to{background-position-x:-220%}}@keyframes page-enter{0%{transform:translateY(10px) scale(.994)}to{transform:translateY(0) scale(1)}}@media(prefers-reduced-motion:reduce){.clickable-panel,.panel-expand-mark,.sidebar{transition:none}.dashboard,.detail-page,.skeleton,.run-detail-overlay,.run-detail-drawer{animation:none}}@container (max-width: 50rem){.gpu-item{grid-template-columns:1fr;gap:10px}.gpu-summary-facts{flex-direction:row;align-items:center;justify-content:flex-start;flex-wrap:wrap;gap:8px 15px}}@media(max-width:1050px){.main{padding-inline:clamp(.9rem,2.2vw,1.5rem)}.content-scroll{padding-bottom:28px}}@media(max-width:800px){.app-shell{flex-direction:column}.sidebar{width:100%;height:auto;flex:none;min-height:0;padding:13px clamp(.9rem,2.5vw,1.1rem);border-right:0;border-bottom:1px solid var(--line)}.sidebar-collapsed .sidebar{width:100%;height:0;padding-block:0;border-bottom-color:transparent}.brand{margin:0 0 12px}.sidebar-group-label,.sidebar .nav-item,.sidebar-section-head,.sidebar-foot,.node-navigation{display:none}.sidebar-node-picker{max-height:none;flex:none}.sidebar-node-picker>.server-list{display:flex;flex:none;overflow-x:auto;overflow-y:hidden;gap:8px;padding:0 0 3px}.sidebar .server-item{flex:0 0 clamp(10rem,35vw,12rem);width:auto;padding:9px}.sidebar .server-secondary{display:none}.main{height:auto;padding-inline:clamp(.8rem,3vw,1.1rem)}.topbar{height:54px}}@media(max-width:560px){.run-detail-drawer{border-radius:0}.preview-pill{display:none}.notification-toggle{width:34px;padding-inline:0}.notification-toggle span{display:none}.glass-panel{padding:clamp(.8rem,3vw,1rem)}.runs-table{min-width:900px}.page-title>.status{display:none}.gpu-summary-meters,.gpu-detail-body{grid-template-columns:1fr}.gpu-summary-facts{flex-wrap:wrap}}.tune-card{min-width:0}.tune-panel{width:100%;min-width:0}.tune-skeleton{display:grid;gap:1rem;padding:1.5rem 0;width:100%}.tune-skeleton>span{height:2.3rem;border-radius:.6rem}.tune-skeleton>span:last-child{width:70%}.tune-preview{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,20rem),1fr));gap:1.2rem}.tune-search-summary{min-width:0;padding:.75rem 1rem;border:1px solid var(--line);background:#ffffff52;border-radius:.8rem}.tune-summary-line{display:flex;align-items:center;justify-content:space-between;gap:1rem;color:var(--muted);font-size:.85rem}.tune-summary-line strong{color:var(--text)}.tune-search-summary p{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;margin:.8rem 0;font-size:1rem}.tune-search-summary small{display:block;color:var(--muted);margin-top:.6rem;overflow-wrap:anywhere}.tune-search-summary .table-progress{margin-top:.6rem;height:.35rem}.tune-toolbar{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:.8rem;margin-bottom:1.2rem}.tune-toolbar label{display:flex;align-items:center;gap:.6rem;min-width:0;max-width:100%;font-size:.88rem;color:var(--muted)}.tune-toolbar select{max-width:min(32rem,65vw);min-width:6rem;padding:.6rem 1rem;border:1px solid var(--line);border-radius:.6rem;background:#ffffffb3;color:var(--text);font:inherit;text-overflow:ellipsis}.tune-toolbar>span{color:var(--muted);font-size:.85rem;overflow-wrap:anywhere}.tune-refresh{display:flex;align-items:center;gap:.4rem;color:var(--text);border:1px solid var(--line);border-radius:.6rem;padding:.55rem .8rem;background:#fff9;cursor:pointer;font:inherit;font-size:.85rem}.tune-results{display:grid;gap:1.1rem;min-width:0}.tune-results h3,.tune-results h4{margin:0;font-size:1rem}.tune-description{font-size:1.1rem;line-height:1.65;color:var(--text);overflow-wrap:anywhere}.tune-stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,10rem),1fr));gap:0;padding-block:.5rem;border-block:1px solid var(--line)}.tune-stats>div{display:grid;gap:.6rem;padding:1rem 1.1rem;border-radius:0;background:transparent;border:0;border-right:1px solid var(--line)}.tune-stats span{color:var(--muted);font-size:.8rem}.tune-stats strong{font-size:1.6rem;font-weight:600;font-variant-numeric:tabular-nums}.tune-stats small{color:var(--muted);font-size:.85rem;font-weight:400}.tune-config-line{display:flex;flex-wrap:wrap;gap:.6rem 1.2rem;color:var(--muted);font-size:.85rem;align-items:center}.tune-inline-note{display:flex;align-items:flex-start;gap:.6rem;padding:.8rem;background:#c48f3614;border-radius:.6rem}.tune-inline-note pre,.tune-trial-detail pre{white-space:pre-wrap;overflow-wrap:anywhere;font-size:.85rem;line-height:1.6}.tune-charts{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,23rem),1fr));gap:1rem}.tune-chart{background:#fff;border:1px solid var(--line);border-radius:7px;padding:1rem;min-width:0}.tune-chart-caption{color:var(--muted);font-size:.9rem;overflow-wrap:anywhere}.tune-chart svg{display:block;width:100%;min-height:12rem;overflow:visible}.tune-chart text{fill:#788389;font-size:16px}.tune-chart-grid{stroke:#34697721;stroke-dasharray:3 4}.tune-chart-line{fill:none;stroke:#398773;stroke-width:2}.tune-point{fill:#398773;stroke:#fff;stroke-width:1}.tune-point.pareto{fill:#c38e3d}.tune-table{width:100%;table-layout:fixed;border-collapse:collapse;font-size:.85rem;text-align:left}.tune-table th{color:var(--muted);font-weight:500;background:#ffffff4d}.tune-table td,.tune-table th{padding:.85rem .7rem;border-bottom:1px solid var(--line);white-space:nowrap}.tune-table tbody tr{cursor:pointer}.tune-table tbody tr:hover,.tune-table tr.selected{background:#278c9114}.tune-table button{border:0;background:transparent;padding:0;color:#287a64;font:inherit;cursor:pointer}.tune-params span{display:block;max-width:20rem;overflow:hidden;text-overflow:ellipsis}.tune-pareto-badge{display:inline-block;margin-left:.5rem;padding:.1rem .35rem;border-radius:.3rem;font-size:.65rem;color:#986713;background:#c4912e1f}.tune-trial-detail{padding:1.1rem;border:1px solid var(--line);border-radius:.8rem;background:#ffffff4d;min-width:0}.tune-code-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,18rem),1fr));gap:1rem;margin:1rem 0}.tune-code-grid pre{margin:.5rem 0 0;max-height:16rem;overflow:auto}.tune-panel>.empty-state{min-height:18rem}.run-terminated{background:#e0f0e9;color:#34755a}.run-error{background:#f7e6df;color:#a05e45}@media(max-width:650px){.tune-toolbar{align-items:flex-start}.tune-chart{padding:.5rem}.tune-chart svg{min-height:10rem}.tune-stats{grid-template-columns:repeat(2,minmax(0,1fr))}}.tune-policy-row{display:flex;gap:1rem;align-items:center;justify-content:space-between;padding:1rem 1.2rem;border:1px solid var(--line);border-radius:7px;background:#f8faf7;flex-wrap:wrap}.tune-policy-row p{margin:.4rem 0;font-size:.9rem}.tune-policy-row small{color:var(--muted)}.tune-policy-switch{display:inline-flex;align-items:center;gap:.6rem;padding:.6rem .85rem;font:inherit;border-radius:2rem;border:1px solid var(--line);background:#ffffff80;color:var(--text);cursor:pointer;white-space:nowrap}.tune-policy-switch>span{width:2rem;height:1.1rem;border-radius:1rem;background:#84969c;position:relative;transition:background .2s}.tune-policy-switch>span:after{content:"";position:absolute;width:.8rem;height:.8rem;left:.15rem;top:.15rem;border-radius:50%;background:#fff;transition:transform .2s}.tune-policy-switch[aria-checked=true]>span{background:#287a64}.tune-policy-switch[aria-checked=true]>span:after{transform:translate(.9rem)}.tune-policy-switch:disabled{opacity:.6;cursor:default}.tune-policy-switch:focus-visible{outline:2px solid #287a64;outline-offset:3px}@media(prefers-reduced-motion:reduce){.tune-policy-switch>span,.tune-policy-switch>span:after{transition:none}}.list-pagination{position:sticky;top:0;z-index:3;background:#fff;border-bottom:1px solid var(--line);margin-bottom:.6rem;border-radius:.6rem;padding-inline:.6rem;display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:.8rem;padding:1rem 0 .2rem;font-size:.85rem;color:var(--muted)}.pagination-controls{display:flex;flex-wrap:wrap;align-items:center;gap:.5rem}.pagination-controls label{display:inline-flex;align-items:center;gap:.4rem}.pagination-controls button,.pagination-controls select{font:inherit;color:var(--text);border:1px solid var(--line);border-radius:.5rem;padding:.45rem .65rem;background:#ffffff8c;cursor:pointer}.pagination-controls button:disabled{opacity:.4;cursor:default}.runs-preview-row{width:100%;text-align:left;font:inherit;cursor:pointer;border-inline:0;border-top:0;background:transparent}.runs-preview-row:hover,.runs-preview-row:focus-visible{background:#278c910f}.run-kind-badge{display:inline-block;margin-left:.45rem;font-size:.65rem;font-weight:600;padding:.1rem .3rem;border-radius:.3rem;color:#287a64;background:#278c911a;vertical-align:middle}.run-detail-trigger{flex-wrap:wrap}.tune-table td,.tune-table th{overflow:hidden;text-overflow:ellipsis;padding-inline:clamp(.35rem,1vw,.75rem)}.tune-table th{white-space:normal;overflow-wrap:anywhere}.tune-table button{display:flex;flex-wrap:wrap;align-items:center;max-width:100%;text-align:left}.tune-table button>span:first-child,.tune-cell-text,.tune-params span{display:block;min-width:0;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tune-pareto-badge{margin:.2rem .2rem .1rem 0}.tune-detail-drawer{width:min(100%,42rem)}.tune-detail-drawer .tune-trial-detail{margin-top:1.2rem;padding:0;border:0;background:transparent}.tune-detail-drawer .run-detail-body{overscroll-behavior:contain}.tune-code-grid>div{min-width:0}.tune-code-grid pre{white-space:pre-wrap;overflow-wrap:anywhere}@media(max-width:700px){.list-pagination{align-items:flex-start}.pagination-controls{width:100%}.tune-table{font-size:.75rem}.tune-table .run-status{white-space:normal}}.pagination-anchor{display:block;height:0;scroll-margin-top:.5rem}.explorer-stats{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:0;margin:0 0 1.7rem;padding-bottom:1.4rem;border-bottom:1px solid var(--line)}.explorer-stats>div{display:flex;flex-direction:column;gap:.4rem;padding:.3rem 1.5rem;border:0;border-right:1px solid var(--line);border-radius:0;background:transparent}.explorer-stats span,.explorer-stats small{color:var(--muted);font-size:.8rem}.explorer-stats strong{font-size:1.9rem;font-weight:600}.explorer-viewbar{display:flex;align-items:center;justify-content:space-between;gap:1rem;margin-bottom:1rem}.segmented-control{display:flex;flex-wrap:wrap;gap:.25rem;padding:.25rem;border-radius:7px;border:1px solid var(--line);background:#f3f5f2}.segmented-control button{display:inline-flex;align-items:center;justify-content:center;gap:.45rem;padding:.55rem .85rem;font:inherit;font-size:.85rem;color:var(--muted);border:0;border-radius:.55rem;background:transparent;cursor:pointer}.segmented-control button[aria-pressed=true]{background:#fff;color:#315e48;box-shadow:0 1px 3px #1a352815}.explorer-filters{display:flex;flex-wrap:wrap;gap:.6rem;align-items:center}.explorer-search{display:flex;align-items:center;gap:.5rem;flex:1 1 22rem;min-width:0;padding:.6rem .8rem;border-radius:.6rem;border:1px solid var(--line);color:var(--muted);background:#fff9}.explorer-search input{width:100%;min-width:0;border:0;outline:none;background:transparent;font:inherit;font-size:.85rem;color:var(--text)}.explorer-filters select,.trial-search,.chart-secondary-select select{font:inherit;font-size:.85rem;max-width:100%;min-width:0;padding:.65rem;color:var(--text);border:1px solid var(--line);border-radius:.6rem;background:#fff9}.explorer-context{display:flex;align-items:center;justify-content:space-between;gap:1rem;flex-wrap:wrap;margin:1rem 0;font-size:.8rem;color:var(--muted)}.explorer-context button{border:0;background:transparent;color:#287a64;cursor:pointer}.research-topic-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,18rem),1fr));gap:1rem;margin-top:1.2rem}.research-topic-card{display:flex;flex-direction:column;min-width:0;text-align:left;padding:1.2rem;border:1px solid var(--line);border-radius:1rem;background:#fff6;font:inherit;color:var(--text);cursor:pointer;transition:background .18s,box-shadow .18s}.research-topic-card:hover{background:#ffffffb3;box-shadow:0 7px 20px #275b6712}.research-topic-card>div{display:flex;align-items:center;justify-content:space-between;color:#36828b}.topic-icon{display:grid;place-items:center;padding:.65rem;border-radius:.7rem;background:#2b8c941a}.research-topic-card h3{margin:1rem 0 .5rem;font-size:1.05rem;overflow-wrap:anywhere}.research-topic-card p{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;margin:0 0 1rem;color:var(--muted);font-size:.85rem}.research-topic-card footer{display:flex;justify-content:space-between;flex-wrap:wrap;gap:.5rem;margin-top:auto;font-size:.75rem;color:var(--muted)}.research-topic-card footer strong{color:#287a64;font-weight:500}.research-topic-card>small{margin-top:.7rem;color:var(--muted);font-size:.75rem}.help-tip{display:inline-grid;place-items:center;flex:none;width:1.2rem;height:1.2rem;padding:0;margin-inline-start:.35rem;border:1px solid #98b3bd;border-radius:50%;color:#587f8f;background:#fff9;font:600 .75rem/1 sans-serif;cursor:help;vertical-align:middle}.help-tip:hover,.help-tip:focus-visible{color:#1f777e;border-color:#1f777e;background:#f3ffff}.help-tooltip{position:fixed;z-index:60;width:min(23rem,calc(100vw - 1rem));padding:.9rem 1rem;border:1px solid rgba(52,106,122,.25);border-radius:.75rem;color:#dfeff3;background:#214553;box-shadow:0 7px 25px #122f3b33;font-size:.8rem;line-height:1.75;pointer-events:none}.chart-heading{display:flex;align-items:flex-start;justify-content:space-between;gap:.7rem;margin-bottom:.65rem}.chart-heading h4{margin:0;color:#59656a;font-size:.9rem;font-weight:500;overflow-wrap:anywhere}.tune-analytics{display:grid;gap:1rem}.tune-analytics .tune-toolbar{margin:0}.tune-charts>*{min-width:0}.chart-wide{grid-column:1 / -1}.chart-note{font-size:.75rem;color:var(--muted);margin:.4rem 0 .7rem}.chart-empty{min-height:12rem;display:grid;place-items:center;padding:1rem;text-align:center;color:var(--muted);font-size:.85rem}.chart-secondary-select{margin-bottom:.5rem;font-size:.8rem;color:var(--muted)}.chart-secondary-select label{display:flex;align-items:center;gap:.5rem}.chart-secondary-select select{max-width:75%}.parallel-controls{display:flex;flex-wrap:wrap;gap:.4rem;margin:.6rem 0}.parallel-controls button{max-width:100%;overflow:hidden;text-overflow:ellipsis;border:1px solid var(--line);border-radius:1rem;background:transparent;color:var(--muted);font:inherit;font-size:.75rem;padding:.3rem .65rem;cursor:pointer}.parallel-controls button[aria-pressed=true]{color:#287a64;background:#e4f1f1;border-color:#79b2b4}.parallel-scroll{max-width:100%;overflow-x:auto}.parallel-scroll svg{min-width:34rem}.parallel-line{stroke:#5c9aa6;stroke-width:1.2;opacity:.16;cursor:pointer}.parallel-line.top{stroke:#bf8938;stroke-width:2;opacity:.7}.parallel-line:hover,.parallel-line:focus-visible{opacity:1;stroke-width:3}.parallel-axis{stroke:#9eb5bd;stroke-width:1}.tune-table .trial-readable-trigger{display:grid;width:100%;gap:.35rem;text-align:left}.trial-readable-trigger>span:first-child{color:#476a5d;font-weight:600}.tune-table .trial-description{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;white-space:normal;overflow:hidden;overflow-wrap:anywhere;color:var(--text);font-size:.85rem;line-height:1.6}.trial-readable-trigger small{color:#8099a4;font-size:.7rem;overflow:hidden;text-overflow:ellipsis}.trial-detail-meta{display:flex;flex-wrap:wrap;gap:.7rem 1rem;color:var(--muted);font-size:.8rem;margin:.8rem 0 1.4rem}.trial-parameter-list{margin:.8rem 0;border:1px solid var(--line);border-radius:.6rem;overflow:hidden}.trial-parameter-list>div{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:.7rem;padding:.65rem;border-bottom:1px solid var(--line);font-size:.85rem}.trial-parameter-list>div:last-child{border-bottom:0}.trial-parameter-list dt{color:var(--muted);overflow-wrap:anywhere}.trial-parameter-list dd{margin:0;overflow-wrap:anywhere}@media(max-width:700px){.explorer-stats{gap:.4rem}.explorer-stats>div{padding:.7rem}.explorer-stats strong{font-size:1.4rem}.explorer-stats small{display:none}.segmented-control button{padding:.45rem .6rem}.tune-toolbar label{max-width:100%}.tune-toolbar select{flex:1;min-width:0}}.best-trial-link{font:inherit;color:#287a64;background:transparent;border:0;padding:0;cursor:pointer;text-decoration:underline;text-underline-offset:.2em}.tune-toolbar select{max-width:min(18rem,55vw)}.readable-trials th{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.chart-heading h4{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.trial-metric-list dt{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.active-trials{min-width:0;border:1px solid #c6d6c9;border-left:3px solid #68957a;border-radius:6px;background:#fbfcfa;padding:1rem}.active-trials-heading{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:.65rem;margin-bottom:.6rem}.active-trials-heading h3{display:flex;align-items:center;gap:.5rem}.active-trials-heading h3 span{font-size:.8rem;color:var(--muted)}.active-trials-heading small,.active-trials-empty{color:var(--muted);font-size:.83rem}.active-trials-empty{margin:.8rem 0;text-align:center}.trial-progress{display:grid;gap:.4rem;font-size:.8rem;font-variant-numeric:tabular-nums}.trial-progress .table-progress{margin:0;height:.3rem}.trial-submitted{display:flex;flex-wrap:wrap;gap:.15rem .45rem;font-size:.8rem;font-variant-numeric:tabular-nums;color:var(--muted)}.trial-metric-value{overflow:hidden;text-overflow:ellipsis}.trial-time-details{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,13rem),1fr));gap:.8rem;font-size:.85rem}.trial-time-details>div{display:grid;gap:.3rem}.trial-time-details dt{color:var(--muted)}.trial-time-details dd{margin:0;font-variant-numeric:tabular-nums}@media(max-width:1050px){.timed-trials thead,.timed-trials colgroup{display:none}.timed-trials tbody{display:grid;gap:.65rem}.timed-trials tr{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));padding:.7rem;border:1px solid var(--line);border-radius:.7rem}.timed-trials td{min-width:0;display:flex;flex-direction:column;align-items:flex-start;gap:.3rem;padding:.45rem;border:0;white-space:normal}.timed-trials td:before{content:attr(data-label);font-size:.72rem;color:var(--muted)}.timed-trials td:first-child{grid-column:1/-1}.timed-trials td:first-child:before{display:none}.timed-trials .trial-progress{width:100%}.timed-trials .trial-metric-value{overflow-wrap:anywhere}}.sidebar .status-online{color:#99c7ab}.sidebar .status-busy{color:#dfbe85}.sidebar .status-offline{color:#d5a199}.sidebar .status-checking{color:#b0beca}.sidebar .status-dot{box-shadow:none;width:6px;height:6px}.sidebar-empty{color:#b0bebf}.sidebar .skeleton{background:#ffffff15}.sidebar .nav-item svg{opacity:.8}.server-list.detailed .server-item{background:#fff;border:1px solid var(--line)}.server-list.detailed .server-item-names strong{color:var(--text)}.server-list.detailed .server-item-names small,.server-list.detailed .server-secondary{color:var(--muted)}.server-list.detailed .server-icon{background:#f1f4ef;color:#64776a}button:focus-visible,select:focus-visible,input:focus-visible{outline:2px solid #468867;outline-offset:3px}.explorer-stats>div:first-child{padding-left:0}.explorer-stats>div:last-child,.tune-stats>div:last-child{border-right:0}.explorer-stats strong{font-size:2.15rem;font-weight:500;letter-spacing:-.035em;font-variant-numeric:tabular-nums}.runs-table th,.tune-table th{background:#f7f8f5;font-size:.8rem;color:#77817b;font-weight:500}.runs-table td,.tune-table td{border-color:#e9ede8}.runs-table td{padding-block:17px}.run-gpu-pill{border:1px solid #e0e5de;border-radius:4px;background:#f5f7f2;color:#66776b;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-weight:500}.run-description{font-weight:500}.run-status{font-size:.8rem;font-weight:500}.run-pausing{color:#9a7738}.run-cancelled{color:#8b817b}.run-completed{color:#717f75}.runs-preview-row{border-radius:0;padding:14px 0}.runs-preview-row:not(:last-child){border-bottom:1px solid var(--line)}.runs-preview-desc{font-weight:500}.list-pagination{background:#fff;border-radius:0;padding-block:.8rem}.explorer-search,.explorer-filters select,.tune-toolbar select,.trial-search{background:#fff;border-radius:6px}.tune-search-summary{background:#fafbf8;border-radius:6px}.research-topic-card{border-radius:7px;background:#fff}.topic-icon{border-radius:6px;background:#f1f5ee;color:#6c886b}.help-tip{border-color:#bac4ba;color:#7c8d7c;background:transparent}.help-tooltip{background:#283a30;border-color:#536451;border-radius:7px}.run-detail-head{background:#fff}.run-detail-group h3{color:#667a64;font-weight:500}.run-detail-field dd{font-weight:400}.tune-description{font-size:1.15rem;font-weight:500}.tune-stats strong{letter-spacing:-.025em;font-weight:500}.gpu-detail-card{background:#fff;border-radius:7px;box-shadow:none}@media(min-width:1180px){.dashboard-grid{grid-template-columns:repeat(12,minmax(0,1fr))}.dashboard-grid>.gpu-card{grid-column:1 / -1}.dashboard-grid>.run-card{grid-column:span 7}.dashboard-grid>.tune-card{grid-column:span 5}}@media(max-width:650px){.explorer-stats>div{padding-inline:.7rem}.explorer-stats strong{font-size:1.6rem}.tune-stats>div{padding-inline:.7rem}}.run-controls{position:relative;margin-block:1.35rem .5rem;border:1px solid #dce4d9;border-radius:7px;background:#f2f5ef;padding:1rem 1.1rem}.management-heading,.management-heading>div{display:flex;flex-wrap:wrap;align-items:center;gap:.65rem}.management-heading{justify-content:space-between;color:#83907f}.management-heading strong{color:#394e3e;font:500 .9rem ui-monospace,SFMono-Regular,Menlo,monospace}.section-kicker{color:#6e7e6c;font-size:.75rem;letter-spacing:.05em}.management-buttons{display:flex;gap:.5rem;flex-wrap:wrap;margin-top:.9rem}.management-button{display:inline-flex;align-items:center;justify-content:center;gap:.45rem;padding:.6rem .85rem;min-height:2.45rem;border:1px solid #cfd8cb;border-radius:5px;color:#4e6250;background:#fff;font:inherit;font-size:.85rem;font-weight:500;line-height:1.3;cursor:pointer}.management-button.primary{background:#365d46;border-color:#365d46;color:#fff}.management-button:not(:disabled):hover{background:#e7ede3;border-color:#98ae94}.management-button.primary:not(:disabled):hover{background:#294d38;color:#fff}.management-button.danger-quiet{color:#936b60;background:transparent;border-color:transparent}.management-button.destructive{color:#fff;background:#a35243;border-color:#a35243}.management-button.destructive:not(:disabled):hover{background:#874234}.management-button:disabled{opacity:1;cursor:not-allowed;color:#7a858d;background:#e9edef;border-color:#d7dee2;box-shadow:none}.management-hint{color:#768270;font-size:.78rem;line-height:1.75;margin:.7rem 0 0;overflow-wrap:anywhere}.management-blocked{color:#9a773e;font-size:.8rem;line-height:1.65;margin:.55rem 0 0;overflow-wrap:anywhere}.management-feedback{display:flex;align-items:flex-start;gap:.55rem;margin-top:.9rem;font-size:.82rem;color:#38754e;line-height:1.65}.management-feedback>svg{flex:none;margin-top:.2rem}.management-feedback.is-error{color:#a55343}.management-feedback details{margin-top:.3rem;font-size:.78rem}.management-feedback pre{white-space:pre-wrap;overflow-wrap:anywhere;max-height:12rem;overflow:auto}.inline-retry{padding:0;margin-top:.4rem;border:0;background:transparent;color:#687b61;font:inherit;text-decoration:underline;cursor:pointer}.management-confirm{margin-top:1.2rem;background:#fff;border:1px solid #ddcfc1;border-radius:6px;padding:1.1rem}.confirm-heading{display:flex;justify-content:space-between;align-items:center;gap:1rem}.confirm-heading h3{margin:0;color:#4b4339;font-size:1rem;font-weight:600}.confirm-heading button{display:grid;place-items:center;border:0;color:#7f847a;background:transparent;cursor:pointer;padding:.3rem}.confirm-target{color:#827b6f;font:.75rem ui-monospace,SFMono-Regular,Menlo,monospace;margin:.5rem 0;overflow-wrap:anywhere}.confirm-description{font-size:.85rem;color:#5c6657;margin:.7rem 0 1.1rem;line-height:1.65;overflow-wrap:anywhere}.storage-breakdown{margin:1rem 0}.storage-breakdown>div{display:flex;justify-content:space-between;flex-wrap:wrap;gap:.4rem 1rem;border-bottom:1px solid var(--line);padding:.65rem 0;font-size:.85rem}.storage-breakdown dt{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.storage-breakdown small{font:.75rem -apple-system,sans-serif;color:var(--muted);margin-left:.5rem}.storage-breakdown dd{margin:0;font-variant-numeric:tabular-nums}.storage-total{display:flex;align-items:baseline;flex-wrap:wrap;justify-content:space-between;gap:.5rem;margin-top:1rem;font-size:.8rem;color:#7f7668}.storage-total strong{font-size:1.5rem;font-weight:500;letter-spacing:-.03em;color:#4c5142}.cleanup-scope{padding-top:.8rem;font-size:.8rem;line-height:1.8;color:#878070;overflow-wrap:anywhere}.cleanup-scope p{margin:.5rem 0}.cleanup-scope strong{font-weight:500;color:#5d6556;margin-right:.3rem}.confirm-buttons{display:flex;justify-content:flex-end;flex-wrap:wrap;gap:.6rem;margin-top:1rem}.storage-loading{display:grid;gap:.65rem;padding:1rem 0}.storage-loading>span{height:1.5rem;width:100%}.storage-loading p{font-size:.8rem;color:var(--muted)}.spin{animation:spin 1s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}@media(prefers-reduced-motion:reduce){.spin{animation:none}}.management-refresh{display:grid;place-items:center;flex:none;border:0;border-radius:4px;background:transparent;color:#7d8c77;cursor:pointer;padding:.25rem}.management-refresh:hover{background:#e3ebdf}.management-refresh:disabled{opacity:.4;cursor:default}.run-detail-head h2{display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}@media(max-width:1050px){.runs-table{display:block;min-width:0}.runs-table thead{display:none}.runs-table tbody{display:grid;gap:.8rem}.runs-table tr{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));border:1px solid var(--line);border-radius:7px;padding:.7rem}.runs-table td{display:flex;flex-direction:column;align-items:flex-start;gap:.35rem;min-width:0;padding:.45rem;border:0;white-space:normal;font-size:.85rem}.runs-table td:before{content:attr(data-label);font-size:.72rem;color:var(--muted)}.runs-table .run-id{grid-column:1/-1}.runs-table .run-id:before{display:none}.runs-table .run-description-cell,.runs-table .run-submitted{grid-column:1/-1}.runs-table .run-description{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;white-space:normal}}.sidebar-node-picker{display:flex;flex-direction:column;min-height:0;flex:0 1 auto;max-height:42%}.sidebar-node-picker .sidebar-section-head{margin-top:0;flex:none}.node-navigation{flex:none;margin-top:26px;padding-top:20px;border-top:1px solid #ffffff13;margin-bottom:20px}.node-navigation .sidebar-group-label strong{display:block;margin-top:7px;color:#d7e2db;font-size:13px;font-weight:500;letter-spacing:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.topbar-path{min-width:0}.topbar-node{max-width:clamp(6rem,20vw,20rem);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#52695c}.management-button.primary.action-pause:not(:disabled){background:#b96710;border-color:#a75b0b;color:#fff;box-shadow:0 1px 2px #82491224}.management-button.primary.action-pause:not(:disabled):hover{background:#a25709;border-color:#914c06}.management-button.primary.action-pause:not(:disabled):active{background:#874806}.management-button.primary.action-resume:not(:disabled){background:#287852;border-color:#246c49;color:#fff}.management-button.primary.action-resume:not(:disabled):hover{background:#20623f}@media(max-width:800px){.sidebar-node-picker{max-height:none;flex:none}.node-navigation{display:none}}.device-policy{margin-block-end:1.3rem;padding:1.1rem 1.25rem;border:1px solid var(--line, #dce2e9);border-radius:1rem;background:#f8fafcb3}.device-policy-heading{display:flex;align-items:center;flex-wrap:wrap;gap:.6rem}.device-policy-heading>span{margin-inline-start:auto;color:var(--muted, #667085);font-size:.85rem}.device-policy p{color:var(--muted, #667085);line-height:1.6;font-size:.9rem;margin-block:.7rem}.device-policy fieldset{padding:0;margin:0;border:0;min-width:0}.device-policy-modes{display:flex;flex-wrap:wrap;gap:.75rem 1.5rem;margin-block:1rem}.device-policy label{display:flex;align-items:center;gap:.65rem;cursor:pointer}.device-policy input{accent-color:#25766c;width:1rem;height:1rem;flex-shrink:0}.device-policy-list{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,19rem),1fr));gap:.65rem;margin-block-end:1rem}.device-policy-choice{padding:.8rem;border:1px solid var(--line, #dce2e9);border-radius:.65rem;background:#ffffffa6;min-width:0}.device-policy-choice span{min-width:0}.device-policy-choice strong,.device-policy-choice small{display:block;overflow-wrap:anywhere}.device-policy-choice strong{font-size:.9rem;font-weight:600}.device-policy-choice small{font-size:.75rem;color:var(--muted, #667085);margin-top:.3rem}.device-policy-missing{display:grid;gap:.6rem;margin-block:1rem;color:#92602a;font-size:.85rem;overflow-wrap:anywhere}.device-policy [role=status]{margin-block-start:.6rem;line-height:1.5;font-size:.9rem}.node-overview-actions{display:flex;align-items:center;justify-content:flex-end;gap:.75rem;flex-wrap:wrap;flex-shrink:0}.device-policy-entry{display:inline-flex;align-items:center;gap:.45rem;padding:.55rem .85rem;border:1px solid #287a64;border-radius:.65rem;color:#216650;background:#eef7f2;cursor:pointer;white-space:nowrap;font-weight:600}.device-policy-entry:hover{background:#e0f0e7}.node-overview-actions>.status{padding:7px 11px;border:1px solid var(--line);border-radius:99px;background:#ffffff94}@media(max-width:600px){.dashboard>.page-title{align-items:flex-start;flex-wrap:wrap}.node-overview-actions{justify-content:flex-start}}.trial-wait-reason{display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden;white-space:normal;margin-block-start:.4em;color:var(--muted);font-size:.75rem;line-height:1.55;overflow-wrap:anywhere}
Binary file
Binary file
Binary file
@@ -0,0 +1,17 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" role="img" aria-label="REXP Hub">
2
+ <defs>
3
+ <linearGradient id="background" x1="0" x2="1" y1="0" y2="1">
4
+ <stop offset="0" stop-color="#102c42"/>
5
+ <stop offset="1" stop-color="#187b80"/>
6
+ </linearGradient>
7
+ <linearGradient id="liquid" x1="0" x2="1" y1="0" y2="1">
8
+ <stop offset="0" stop-color="#73e3d5"/>
9
+ <stop offset="1" stop-color="#30b4b8"/>
10
+ </linearGradient>
11
+ </defs>
12
+ <rect x="4" y="4" width="120" height="120" rx="29" fill="url(#background)"/>
13
+ <path d="M43 29h42M51 29v28L35 88a10 10 0 0 0 9 14h40a10 10 0 0 0 9-14L77 57V29" fill="none" stroke="#efffff" stroke-width="7" stroke-linecap="round" stroke-linejoin="round"/>
14
+ <path d="M43 80c8-4 13-1 20 2 8 3 14 2 22-2l5 10a5 5 0 0 1-5 7H43a5 5 0 0 1-5-7z" fill="url(#liquid)"/>
15
+ <circle cx="58" cy="70" r="4" fill="#73e3d5"/>
16
+ <circle cx="72" cy="62" r="3" fill="#73e3d5"/>
17
+ </svg>
@@ -0,0 +1,17 @@
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.0" />
6
+ <meta name="theme-color" content="#0a1422" />
7
+ <link rel="icon" type="image/svg+xml" href="/icons/rexp-hub.svg" />
8
+ <link rel="apple-touch-icon" href="/icons/rexp-hub-180.png" />
9
+ <link rel="manifest" href="/manifest.webmanifest" />
10
+ <title>REXP Hub</title>
11
+ <script type="module" crossorigin src="/assets/index-Dvblx8d9.js"></script>
12
+ <link rel="stylesheet" crossorigin href="/assets/index-DzbrIT1Y.css">
13
+ </head>
14
+ <body>
15
+ <div id="root"></div>
16
+ </body>
17
+ </html>
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "REXP Hub",
3
+ "short_name": "REXP Hub",
4
+ "start_url": "/",
5
+ "display": "standalone",
6
+ "background_color": "#102c42",
7
+ "theme_color": "#102c42",
8
+ "icons": [
9
+ { "src": "/icons/rexp-hub-192.png", "sizes": "192x192", "type": "image/png" },
10
+ { "src": "/icons/rexp-hub-512.png", "sizes": "512x512", "type": "image/png" }
11
+ ]
12
+ }
package/dist/sw.js ADDED
@@ -0,0 +1,15 @@
1
+ self.addEventListener("notificationclick", (event) => {
2
+ event.notification.close();
3
+ const address = new URL(event.notification.data?.url || "/", self.location.origin);
4
+ if (address.origin !== self.location.origin) return;
5
+ event.waitUntil((async () => {
6
+ const clients = await self.clients.matchAll({ type: "window", includeUncontrolled: true });
7
+ const existing = clients.find((client) => new URL(client.url).origin === self.location.origin);
8
+ if (existing) {
9
+ await existing.focus();
10
+ existing.navigate(address.href);
11
+ } else {
12
+ await self.clients.openWindow(address.href);
13
+ }
14
+ })());
15
+ });
package/package.json CHANGED
@@ -1,6 +1,46 @@
1
1
  {
2
2
  "name": "rexp-web",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "scripts": {
6
+ "start": "node scripts/web.mjs start",
7
+ "web:stop": "node scripts/web.mjs stop",
8
+ "web:status": "node scripts/web.mjs status",
9
+ "web:logs": "node scripts/web.mjs logs",
10
+ "dev": "vite --host 127.0.0.1",
11
+ "build": "tsc --noEmit && vite build",
12
+ "preview": "vite preview --host 127.0.0.1",
13
+ "test": "node --test server/*.test.mjs",
14
+ "prepack": "npm run build",
15
+ "web:open": "node scripts/web.mjs open"
16
+ },
17
+ "dependencies": {
18
+ "@modelcontextprotocol/sdk": "^1.32.1"
19
+ },
20
+ "devDependencies": {
21
+ "@types/react": "^19.0.0",
22
+ "@types/react-dom": "^19.0.0",
23
+ "@vitejs/plugin-react": "^4.3.4",
24
+ "lucide-react": "^0.468.0",
25
+ "react": "^19.0.0",
26
+ "react-dom": "^19.0.0",
27
+ "typescript": "^5.7.3",
28
+ "vite": "^6.0.5"
29
+ },
30
+ "description": "A local experiment dashboard with GPU/NPU monitoring and SSH-MCP plugin integration.",
31
+ "bin": {
32
+ "rexp-web": "scripts/web.mjs"
33
+ },
34
+ "engines": {
35
+ "node": ">=22"
36
+ },
37
+ "files": [
38
+ "dist",
39
+ "scripts/web.mjs",
40
+ "server/*.mjs",
41
+ "!server/*.test.mjs",
42
+ "skills",
43
+ "README.md"
44
+ ],
45
+ "author": "Undulation"
46
+ }
@@ -0,0 +1,131 @@
1
+ #!/usr/bin/env node
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { spawn } from "node:child_process";
5
+ import { fileURLToPath } from "node:url";
6
+ import {webPaths,saveWebSettings} from '../server/web-paths.mjs';
7
+ import {autostartStatus,setAutostart} from '../server/autostart.mjs';
8
+
9
+ const projectDir = fileURLToPath(new URL("../", import.meta.url));
10
+ const {stateDir,pidFile,logFile} = webPaths();
11
+ const json = process.argv.includes("--json");
12
+ const version = JSON.parse(fs.readFileSync(path.join(projectDir,"package.json"),"utf8")).version;
13
+ const action = process.argv[2] || "status";
14
+
15
+ function state() {
16
+ try { return JSON.parse(fs.readFileSync(pidFile, "utf8")); }
17
+ catch { return null; }
18
+ }
19
+
20
+ async function health(port) {
21
+ try {
22
+ const response = await fetch(`http://127.0.0.1:${port}/api/web/health`, { signal: AbortSignal.timeout(1000) });
23
+ return response.ok ? await response.json() : null;
24
+ } catch { return null; }
25
+ }
26
+
27
+ function alive(pid) {
28
+ try { process.kill(pid, 0); return true; }
29
+ catch { return false; }
30
+ }
31
+
32
+ async function matchingService(info) {
33
+ if (!info || !Number.isInteger(info.pid) || !Number.isInteger(info.port)) return false;
34
+ const response = await health(info.port);
35
+ return response?.service === "rexp-hub-web" && response.pid === info.pid;
36
+ }
37
+
38
+ async function main() {
39
+ if (action === 'version') { console.log(json?JSON.stringify({version}):version); return; }
40
+ if (action === 'skills') {
41
+ const name=process.argv[4];
42
+ if(process.argv[3] === 'list') {
43
+ console.log(json?JSON.stringify({skills:[{name:'web-operations',description:'安装、自启动、启停及打开 Web 控制台'}]}):'web-operations');
44
+ } else if(process.argv[3] === 'show' && name === 'web-operations') {
45
+ const content=fs.readFileSync(path.join(projectDir,'skills','web-operations.md'),'utf8');
46
+ console.log(json?JSON.stringify({name,content}):content);
47
+ } else throw new Error('用法:rexp-web skills list | skills show web-operations');
48
+ return;
49
+ }
50
+ if (action === 'configure') {
51
+ const choice=process.argv[process.argv.indexOf('--autostart')+1];
52
+ if(!process.argv.includes('--autostart') || !['on','off'].includes(choice))
53
+ throw new Error('首次配置请先询问用户是否登录自启动,再执行 configure --autostart on|off');
54
+ const config={};
55
+ for(const [key,envKey] of Object.entries({mcpDir:'HANDFREE_MCP_DIR',mcpConfig:'HANDFREE_MCP_CONFIG',mcpPlugins:'HANDFREE_PLUGINS_CONFIG'}))
56
+ if(process.env[envKey]) config[key]=path.resolve(process.env[envKey]);
57
+ saveWebSettings(config);
58
+ console.log(JSON.stringify(setAutostart(choice==='on'))); return;
59
+ }
60
+ if(action === 'autostart') {
61
+ const choice=process.argv[3] || 'status';
62
+ if(!['on','off','status'].includes(choice)) throw new Error('用法:rexp-web autostart on|off|status');
63
+ console.log(JSON.stringify(choice==='status'?autostartStatus():setAutostart(choice==='on'))); return;
64
+ }
65
+ if(action === 'serve') {
66
+ const port=Number(process.env.REXP_WEB_PORT || 5173);
67
+ const running=await health(port);
68
+ if(running?.service==='rexp-hub-web') return;
69
+ await import('../server/web-service.mjs'); return;
70
+ }
71
+ const info = state();
72
+ if (action === "status") {
73
+ if(json) { const running=await matchingService(info); console.log(JSON.stringify({running,url:`http://127.0.0.1:${info?.port || Number(process.env.REXP_WEB_PORT || 5173)}/`,pid:running?info.pid:null,autostart:autostartStatus()})); return; }
74
+ if (await matchingService(info)) console.log(`Web 正在运行:http://127.0.0.1:${info.port}/(PID ${info.pid})`);
75
+ else console.log("Web 未运行");
76
+ return;
77
+ }
78
+ if (action === "stop") {
79
+ if (!await matchingService(info)) { console.log("Web 未运行,未关闭任何其他进程"); return; }
80
+ process.kill(info.pid, "SIGTERM");
81
+ for (let i = 0; i < 100; i++) {
82
+ if (!alive(info.pid)) { console.log("Web 已停止"); return; }
83
+ await new Promise((resolve) => setTimeout(resolve, 100));
84
+ }
85
+ throw new Error("Web 正在退出,请稍后查看状态");
86
+ }
87
+ if (action === "logs") {
88
+ if (!fs.existsSync(logFile)) { console.log("暂无 Web 日志"); return; }
89
+ console.log(fs.readFileSync(logFile, "utf8").split("\n").slice(-80).join("\n"));
90
+ return;
91
+ }
92
+ if (!['start','open'].includes(action)) throw new Error('用法:rexp-web start|open|stop|status|logs|configure|autostart|skills|version');
93
+ if (await matchingService(info)) { console.log(`Web 已在运行:http://127.0.0.1:${info.port}/`); if(action==='open') await openPage(info.port); return; }
94
+ if (info && alive(info.pid)) throw new Error("存在未识别的同 PID 进程;为避免误关,请先检查 ~/.cache/rexp-hub/web/service.json");
95
+ if (info) fs.rmSync(pidFile, { force: true });
96
+ const port = Number(process.env.REXP_WEB_PORT || 5173);
97
+ if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error("REXP_WEB_PORT 必须是有效端口");
98
+ const response = await health(port);
99
+ if (response) throw new Error(`端口 ${port} 已被其他 Web 服务占用`);
100
+ if(!fs.existsSync(path.join(projectDir,'dist','index.html'))) throw new Error('找不到构建好的页面;源码开发请先 npm run build,发行包应自带 dist。');
101
+ fs.mkdirSync(stateDir, { recursive: true, mode: 0o700 });
102
+ const fd = fs.openSync(logFile, "a", 0o600);
103
+ const child = spawn(process.execPath, [path.join(projectDir, "server", "web-service.mjs")], {
104
+ cwd: projectDir, detached: true, windowsHide:true, stdio: ["ignore", fd, fd], env: process.env,
105
+ });
106
+ fs.closeSync(fd);
107
+ child.unref();
108
+ for (let i = 0; i < 50; i++) {
109
+ await new Promise((resolve) => setTimeout(resolve, 100));
110
+ const current = state();
111
+ if (current?.pid === child.pid && await matchingService(current)) {
112
+ console.log(`Web 已在后台启动:http://127.0.0.1:${port}/`);
113
+ if(action==='open') await openPage(port);
114
+ return;
115
+ }
116
+ if (!alive(child.pid)) break;
117
+ }
118
+ throw new Error(`Web 启动失败,请运行 npm run web:logs 查看原因`);
119
+ }
120
+
121
+ async function openPage(port) {
122
+ const url=`http://127.0.0.1:${port}/`;
123
+ const command=process.platform==='darwin'?'open':process.platform==='win32'?'explorer.exe':'xdg-open';
124
+ await new Promise((resolve,reject)=> {
125
+ const child=spawn(command,[url],{detached:true,stdio:'ignore',windowsHide:true});
126
+ child.once('error',reject); child.once('spawn',()=>{child.unref();resolve();});
127
+ });
128
+ console.log(`请将 ${url} 加入浏览器书签,之后可以直接打开。`);
129
+ }
130
+
131
+ main().catch((error) => { console.error(error.message); process.exitCode = 1; });
@@ -0,0 +1,52 @@
1
+ // Monitoring uses vendor CLIs only; it never imports torch or initializes devices.
2
+ export const npuQuery = "bash -c 'export PATH=\"/usr/local/bin:/usr/local/Ascend/driver/tools:$PATH\"; export LD_LIBRARY_PATH=\"/usr/local/Ascend/driver/lib64:/usr/local/Ascend/driver/lib64/common:/usr/local/Ascend/driver/lib64/driver:${LD_LIBRARY_PATH:-}\"; npu-smi info -m && printf \"\\n__REXP_NPU_SNAPSHOT__\\n\" && npu-smi info'";
3
+
4
+ const numeric = value => /^\d+(?:\.\d+)?$/u.test(value || '') ? Number(value) : undefined;
5
+
6
+ export function parseNpuSnapshot(text) {
7
+ const [mapping, snapshot] = text.split('__REXP_NPU_SNAPSHOT__');
8
+ if (snapshot === undefined) throw new Error('NPU 采样缺少设备映射');
9
+ const devices = new Map();
10
+ const nameStart = /Chip\s+Phy-ID/iu.test(mapping) ? 4 : 3;
11
+ for (const line of mapping.split(/\r?\n/u)) {
12
+ const fields = line.replaceAll('|', ' ').trim().split(/\s+/u);
13
+ if (!fields.slice(0, 3).every(value => /^\d+$/u.test(value)) || fields.length <= nameStart) continue;
14
+ const [card, chip, logical] = fields.map(Number);
15
+ devices.set(`${card}:${chip}`, { index: logical, uuid: `npu-${card}-${chip}`, name: fields.slice(nameStart).join(' '), cardId: card, chipId: chip });
16
+ }
17
+ if (!devices.size) throw new Error('无法识别 NPU 逻辑设备编号');
18
+ const gpus = [], processes = [];
19
+ let board;
20
+ let processSection = false;
21
+ for (const line of snapshot.split(/\r?\n/u)) {
22
+ if (/Process\s+id/iu.test(line)) { processSection = true; continue; }
23
+ const cells = line.split('|').slice(1, -1).map(cell => cell.trim());
24
+ if (processSection) {
25
+ const ids = cells[0]?.match(/^(\d+)\s+(\d+)$/u);
26
+ const device = ids && devices.get(`${ids[1]}:${ids[2]}`);
27
+ if (device && numeric(cells[1]) !== undefined) processes.push({ device: device.index,
28
+ pid: numeric(cells[4]) ?? Number(cells[1]), command: cells[2] || '—', type: 'NPU',
29
+ gpuMemoryMiB: numeric(cells[3]) });
30
+ continue;
31
+ }
32
+ const identity = cells[0]?.match(/^(\d+)(?:\s+(.+))?$/u);
33
+ if (!identity || cells.length < 3) continue;
34
+ if (identity[2] && !/^\d+$/u.test(identity[2])) {
35
+ const facts = cells[2].split(/\s+/u);
36
+ board = { card: Number(identity[1]), powerWatts: numeric(facts[0]), temperature: numeric(facts[1]) };
37
+ } else if (board) {
38
+ const device = devices.get(`${board.card}:${identity[1]}`);
39
+ if (!device) continue;
40
+ const facts = cells[2].match(/^(\S+)\s+\S+\s*\/\s*\S+\s+(\d+)\s*\/\s*(\d+)$/u);
41
+ gpus.push({ ...device, powerWatts: board.powerWatts, temperature: board.temperature,
42
+ utilization: facts ? numeric(facts[1]) : undefined,
43
+ usedMemory: facts ? Number(facts[2]) / 1024 : undefined,
44
+ totalMemory: facts ? Number(facts[3]) / 1024 : undefined });
45
+ }
46
+ }
47
+ if (!gpus.length) throw new Error('无法识别 npu-smi 资源表,未返回虚构的零值');
48
+ // Unavailable measurements remain undefined instead of becoming zero.
49
+ for (const device of devices.values()) if (!gpus.some(gpu => gpu.index === device.index)) gpus.push(device);
50
+ gpus.sort((a, b) => a.index - b.index);
51
+ return { acceleratorBackend: 'npu', gpus, processes, sampledAt: new Date().toISOString() };
52
+ }
@@ -0,0 +1,70 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import {execFileSync} from 'node:child_process';
4
+ import {fileURLToPath} from 'node:url';
5
+ import {webPaths,readWebSettings,saveWebSettings} from './web-paths.mjs';
6
+
7
+ const xml = value => String(value).replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&apos;'}[c]));
8
+ const desktopQuote = value => '"'+String(value).replace(/[\\"`$]/g,'\\$&').replace(/%/g,'%%')+'"';
9
+
10
+ export function autostartDefinition({platform=process.platform,paths=webPaths(),node=process.execPath,
11
+ script=fileURLToPath(new URL('../scripts/web.mjs',import.meta.url)),env=process.env}={}) {
12
+ const args=[node,script,'start'];
13
+ if (platform === 'darwin') return {
14
+ file:path.join(paths.home,'Library','LaunchAgents','io.rexprun.rexp-web.plist'),
15
+ content:`<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n<plist version="1.0"><dict><key>Label</key><string>io.rexprun.rexp-web</string><key>ProgramArguments</key><array>${args.map(a=>`<string>${xml(a)}</string>`).join('')}</array><key>RunAtLoad</key><true/><key>StandardOutPath</key><string>${xml(paths.logFile)}</string><key>StandardErrorPath</key><string>${xml(paths.logFile)}</string></dict></plist>\n`,
16
+ manager:'macOS 登录启动',
17
+ };
18
+ if (platform === 'win32') {
19
+ const base=env.APPDATA || path.join(paths.home,'AppData','Roaming');
20
+ // Startup launcher needs no administrator permission and opens no terminal window.
21
+ const command=args.map(a=>'"'+a+'"').join(' ');
22
+ return {file:path.join(base,'Microsoft','Windows','Start Menu','Programs','Startup','rexp-web.vbs'),
23
+ content:`CreateObject("WScript.Shell").Run "${command.replace(/"/g,'""')}", 0, False\r\n`,manager:'Windows 登录启动'};
24
+ }
25
+ if (platform === 'linux' && (env.XDG_CURRENT_DESKTOP || env.DESKTOP_SESSION || env.DISPLAY || env.WAYLAND_DISPLAY)) return {
26
+ file:path.join(paths.home,'.config','autostart','rexp-web.desktop'),
27
+ content:`[Desktop Entry]\nType=Application\nName=REXP Web\nExec=${args.map(desktopQuote).join(' ')}\nTerminal=false\nX-GNOME-Autostart-enabled=true\n`,
28
+ manager:'Linux 桌面登录启动',
29
+ };
30
+ return null;
31
+ }
32
+
33
+ export function autostartStatus(options={}) {
34
+ const paths=options.paths || webPaths();
35
+ const definition=autostartDefinition({...options,paths});
36
+ const settings=readWebSettings(paths);
37
+ return {supported:!!definition,enabled:!!definition && fs.existsSync(definition.file),
38
+ configured:typeof settings.autostart === 'boolean',manager:definition?.manager || '此系统暂不支持自动配置'};
39
+ }
40
+
41
+ export function setAutostart(enabled,options={}) {
42
+ if(typeof enabled !== 'boolean') throw new Error('自启动设置必须是布尔值');
43
+ const paths=options.paths || webPaths();
44
+ const definition=autostartDefinition({...options,paths});
45
+ if(!definition) {
46
+ if(enabled) throw new Error('此系统暂不支持自动配置登录启动');
47
+ saveWebSettings({autostart:false},paths);
48
+ return autostartStatus({...options,paths});
49
+ }
50
+ const previous=fs.existsSync(definition.file)?fs.readFileSync(definition.file):null;
51
+ try {
52
+ if(enabled) {
53
+ fs.mkdirSync(path.dirname(definition.file),{recursive:true});
54
+ fs.mkdirSync(paths.stateDir,{recursive:true,mode:0o700});
55
+ const content=(options.platform || process.platform)==='win32'
56
+ ? Buffer.from('\uFEFF'+definition.content,'utf16le') : definition.content;
57
+ fs.writeFileSync(definition.file,content,{mode:0o600});
58
+ // A previously disabled launchd label overrides RunAtLoad across logins.
59
+ if((options.platform || process.platform)==='darwin')
60
+ (options.exec || execFileSync)('launchctl',['enable',`gui/${process.getuid()}/io.rexprun.rexp-web`],{stdio:'pipe',timeout:5000});
61
+ } else fs.rmSync(definition.file,{force:true});
62
+ // Removing the next-login launcher does not stop the current Web process.
63
+ saveWebSettings({autostart:enabled},paths);
64
+ } catch(error) {
65
+ if(previous) fs.writeFileSync(definition.file,previous);
66
+ else fs.rmSync(definition.file,{force:true});
67
+ throw error;
68
+ }
69
+ return autostartStatus({...options,paths});
70
+ }