后台任务需先生成方案并明确启用。
按已确认的用途和连接方式,核对本次证据是否达到目标。
展开后查看具体影响与处理步骤。
仅经指定代理检测 Claude 网页与 API,区分连接失败、需认证、拒绝访问、限流及服务错误。代理失败不回退直连。
点击后在下方显示,不会打开额外窗口。设备和浏览器信息用于排障,不推断账号风险。
选择一项查看;无需先安装维护核心。
EnvScope 原生提供逐项检查、配置预览与恢复、独立账号资料、加密存档、临时浏览器、托盘和完整软件更新。持续维护提供基线恢复、单独批准的 10 秒进程守护,以及可授权的 Mac 60 秒系统时区守护。
Clash Verge 置顶路由、Mac 系统时区服务和原版加密 DMG 兼容已原生实现。系统网络修复、客户端适配和旧组件迁移提供原生方案;不支持的操作会保留具体手动指引。
上游功能核对至 92cd62f;随附源码固定在 870c4ca。各平台实现方式、需手动处理的项目和验收范围见完整核对表。
完整功能升级核对表EnvScope 原生提供逐项检查、配置预览与恢复、独立账号资料、加密存档、临时浏览器、托盘和完整软件更新。持续维护提供基线恢复、单独批准的 10 秒进程守护,以及可授权的 Windows / macOS 60 秒系统时区守护。
Clash Verge 置顶路由、系统时区服务和原版加密 DMG 兼容已原生实现。系统网络修复、客户端适配和旧组件迁移提供原生方案;不支持的操作会保留具体手动指引。
上游功能核对至 92cd62f;随附源码固定在 870c4ca。各平台实现方式、需手动处理的项目和验收范围见完整核对表。
完整功能升级核对表最近 20 份可识别恢复记录,关闭后仍保留。撤销会先停用持续维护,并校验备份和当前文件;后续已有修改时拒绝覆盖。原文位于 ~/.envscope/config-backups,可能含密钥,不可分享。
独立资料,不带入日常 Cookie 或扩展。关闭后清理,应用退出后由独立进程继续检查。
diff --git a/envscope/web/inspection.mjs b/envscope/web/inspection.mjs index 24af2a8..720e2c1 100644 --- a/envscope/web/inspection.mjs +++ b/envscope/web/inspection.mjs @@ -7,6 +7,8 @@ const explained={passed:'通过',review:'需要复核',problem:'发现问题',pe export function initInspection({run,operate,isBusy,retest}){ let catalog=[],report=null,phase='not_run',selection=new Set(),visible=[],groups=[],source='current',initialized=false,defaultsKey=null; const selected=()=>[...selection]; + let selectionKey=''; + const openRows=new Set(); const output=$('checks-output'); const button=(text,fn)=>{const b=el('button',text,'link');b.type='button';b.disabled=phase==='running'||isBusy()||$('start').disabled;b.addEventListener('click',fn);return b;}; const operateHere=fn=>operate(fn,output); @@ -17,7 +19,7 @@ export function initInspection({run,operate,isBusy,retest}){ for(const id of ['checks-select-all','checks-clear','checks-select-problems','checks-select-visible','checks-load-nodes','checks-node-group','checks-node','checks-region-undo'])$(id).disabled=blocked; for(const node of document.querySelectorAll('#all-checks-list button, #all-checks-list input[type=checkbox], #checks-plan button'))node.disabled=blocked; } - function invalidate(){$('checks-plan').replaceChildren();$('checks-selected-count').textContent=`已勾选 ${selection.size} 项。所需依赖自动包含;未选项目不算通过。`;updateButtons();} + function invalidate(){$('checks-plan').replaceChildren();$('checks-selected-count').textContent=`已勾选 ${selection.size} 项。所需依赖自动包含;未选项目不算通过。`;updateButtons();const key=JSON.stringify([...selection].sort());if(key!==selectionKey){selectionKey=key;document.dispatchEvent(new CustomEvent('envscope-selection-changed'));}} const busyObserver=new MutationObserver(updateButtons); busyObserver.observe(document.documentElement,{attributes:true,attributeFilter:['data-operation-busy']}); busyObserver.observe($('start'),{attributes:true,attributeFilter:['disabled']}); @@ -85,6 +87,13 @@ export function initInspection({run,operate,isBusy,retest}){ $('checks-return').addEventListener('click',()=>document.querySelector('.tab[data-page="audit"]')?.click()); for(const id of ['check-search','check-group','check-status'])$(id).addEventListener(id==='check-search'?'input':'change',render); function render(){ + // A native details toggle event may still be queued when a filter renders. + // Capture the live state before replacing rows so the user's click survives. + for(const row of $('all-checks-list').querySelectorAll(':scope > details[data-inspection]')){ + if(row.open)openRows.add(row.dataset.inspection);else openRows.delete(row.dataset.inspection); + } + const active=document.activeElement,activeRow=active?.closest('[data-inspection]'); + const focused=activeRow?{id:activeRow.dataset.inspection,index:[...activeRow.querySelectorAll('summary,input,button')].indexOf(active)}:null; const found=new Map((report?.findings||[]).map(f=>[f.id,f])); const known=new Set(catalog.map(c=>c.id)); const items=[...catalog,...(report?.findings||[]).filter(f=>!known.has(f.id)).map(f=>({id:f.id,title:f.title,label:f.title,group:f.id.startsWith('target.')?'target':'measurement',why:f.id.startsWith('target.')?'按已确认的用途和连接方式,核对本次证据是否达到目标。':'独立记录测量来源和范围;检测目录与补充测量分别列出。'}))]; @@ -98,8 +107,9 @@ export function initInspection({run,operate,isBusy,retest}){ $('check-count').textContent=`显示 ${selected.length} / ${rows.length} 项。基础 ${basics} 项,扩展 ${extras} 项,其余为目标验收及补充测量。`; const nodes=selected.map(c=>{ const row=el('details',undefined,'inspection-row');row.dataset.inspection=c.id; + row.open=openRows.has(c.id);row.addEventListener('toggle',()=>{if(row.isConnected){if(row.open)openRows.add(c.id);else openRows.delete(c.id);}}); row.dataset.manual=String(!!c.remedy?.manual); - const summary=el('summary'),name=el('span',undefined,'inspection-name'),state=el('span',undefined,'inspection-state'),disclosure=el('small','查看说明'); + const summary=el('summary'),name=el('span',undefined,'inspection-name'),state=el('span',undefined,'inspection-state'),disclosure=el('small',row.open?'收起说明':'查看说明'); const label=el('label',undefined,'inspection-select'),checkbox=el('input');checkbox.type='checkbox';checkbox.checked=selection.has(c.id);checkbox.setAttribute('aria-label','选择 '+c.label);checkbox.disabled=phase==='running'; label.append(checkbox);label.addEventListener('click',event=>event.stopPropagation());checkbox.addEventListener('change',()=>{if(checkbox.checked)selection.add(c.id);else selection.delete(c.id);invalidate();});summary.append(label); const statusText=c.finding?.scope==='not-selected'?'未勾选 · 未检测':c.remedy?.manual?(c.annotation?.state==='permission'?'需要读取权限 · 需手动处理':'需手动处理'):explained[c.annotation?.state]||states[c.status]||'未取得结果'; @@ -122,6 +132,7 @@ export function initInspection({run,operate,isBusy,retest}){ }); $('all-checks-list').replaceChildren(...(nodes.length?nodes:[el('p',catalog.length?'没有符合条件的项目,试试清除搜索或切换筛选。':'检查目录尚未取得,请检查本地服务连接。','note')])); updateButtons(); + if(focused){const row=nodes.find(n=>n.dataset.inspection===focused.id);row?.querySelectorAll('summary,input,button')[focused.index]?.focus({preventScroll:true});} } const applyDefaults=config=>{ if(!config)return false; diff --git a/envscope/web/mark.svg b/envscope/web/mark.svg new file mode 100644 index 0000000..f7e3677 --- /dev/null +++ b/envscope/web/mark.svg @@ -0,0 +1 @@ + diff --git a/envscope/web/permission_flow.mjs b/envscope/web/permission_flow.mjs new file mode 100644 index 0000000..3b7725e --- /dev/null +++ b/envscope/web/permission_flow.mjs @@ -0,0 +1,67 @@ +// Opening an OS pane is never evidence that permission was granted. +const key=context=>JSON.stringify([context?.report?.report_id,context?.report?.environment?.fingerprint, + context?.report?.selection?.selected,context?.revision]); +export function permissionContinuation({getContext,recheck,notify,isBusy=()=>false}){ + let pending=null,running=false; + return { + arm(id,context,left=false){pending={id,key:key(context),checks:context.report.selection?.selected?.slice()||null,left};notify('waiting');}, + leave(){if(pending)pending.left=true;}, + cancel(){pending=null;notify('cancelled');}, + invalidate(){if(pending){pending=null;notify('stale');}}, + async resume(manual=false){ + if(!pending||running||isBusy()||(!manual&&!pending.left))return false; + const step=pending;pending=null; + if(step.key!==key(getContext())){notify('stale');return false;} + running=true;notify('checking'); + try{ + const report=await recheck(step.checks); + const item=report?.assessment?.items?.find(row=>row.id===step.id); + if(!report||!item)notify('incomplete'); + else if(item.state==='permission')notify('denied'); + else if(item.state==='passed')notify('verified'); + else notify('review'); + }catch{notify('incomplete');} + finally{running=false;} + return true; + } + }; +} + +export function initPermissionFlow({run,operate,recheck,getContext,isBusy}){ + const panel=document.getElementById('permission-status'),text=document.getElementById('permission-message'); + const resume=document.getElementById('permission-resume'),cancel=document.getElementById('permission-cancel'); + const messages={waiting:'已打开系统入口。处理后返回本窗口,将按原勾选范围自动复检一次。',checking:'正在重新读取权限和检测结果…', + cancelled:'已停止自动复检。系统中已做的操作不会被撤销。',stale:'检测目标、范围或报告已改变,请按当前目标重新检测。', + denied:'复检仍被系统拒绝。请按该项说明处理;不会重复弹出授权窗口。',verified:'本项已重新读取并通过检测,修复方案已更新。', + review:'复检已完成,请查看新的检测结果和处理方案。',incomplete:'复检未取得本项的完整结果,请重新检测。'}; + function notify(state){panel.hidden=false;panel.dataset.state=state;text.textContent=messages[state];resume.hidden=cancel.hidden=state!=='waiting';} + const flow=permissionContinuation({getContext,recheck,isBusy,notify}); + let opening=false,leftDuringOpen=false; + const leave=()=>{if(opening)leftDuringOpen=true;flow.leave();}; + window.addEventListener('blur',leave); + window.addEventListener('focus',()=>{void flow.resume();}); + document.addEventListener('visibilitychange',()=>{if(document.hidden)leave();else void flow.resume();}); + resume.addEventListener('click',()=>void flow.resume(true)); + cancel.addEventListener('click',()=>flow.cancel()); + for(const name of ['envscope-target-changed','envscope-selection-changed','envscope-check-preferences-changed'])document.addEventListener(name,()=>flow.invalidate()); + document.addEventListener('envscope-permission',event=>{ + const context=getContext();if(!context?.report){notify('stale');return;} + void operate(async()=>{ + const {id,button,output}=event.detail;button.disabled=true; + try{ + const params={report_id:context.report.report_id,finding_id:id}; + let value=await run('permission-help',params); + if(value.state==='confirmation_required'){ + if(!window.confirm(value.reason)){output.textContent='已取消,未打开权限入口。';return;} + opening=true;leftDuringOpen=false; + value=await run('permission-help',params,true); + } + output.textContent=value.reason||'请根据本项说明处理后复检。'; + if(value.state==='opened'&&value.recheck_on_return===true){ + flow.arm(id,context,leftDuringOpen); + } + }finally{opening=false;button.disabled=false;} + },event.detail.output); + }); + return flow; +} diff --git a/envscope/web/scan.svg b/envscope/web/scan.svg new file mode 100644 index 0000000..4f66185 --- /dev/null +++ b/envscope/web/scan.svg @@ -0,0 +1 @@ + diff --git a/envscope/web/target.mjs b/envscope/web/target.mjs index bd7d3c9..04f145f 100644 --- a/envscope/web/target.mjs +++ b/envscope/web/target.mjs @@ -69,7 +69,17 @@ export function initTargetEnvironment({sessionPromise,run,retest,isBusy}){ const params={connection_mode:field('connection_mode').value,target_proxy:field('connection_mode').value==='http_proxy'?field('target_proxy').value.trim():''}; lookupBusy=true;controls();$('target-ip-status').textContent='正在按所选连接读取出口 IP…'; try{ - const value=await run('environment-location',params); + let value; + for(let attempt=0;attempt<4;attempt++){ + if(revision!==lookupRevision||form.hidden)return; + try{value=await run('environment-location',params);break;} + catch(error){ + // Only retry this read, never a configuration change or consent action. + if(error.code!=='audit_running'||attempt===3)throw error; + $('target-ip-status').textContent='等待当前操作完成,随后自动读取出口 IP…'; + await new Promise(resolve=>setTimeout(resolve,300*(attempt+1))); + } + } if(revision!==lookupRevision||form.hidden)return; if(edited!==editRevision||manualTarget){$('target-ip-status').textContent=`${value.country?'本次出口 '+value.country+'。':''}已保留手动选择或已保存目标。点“重新读取 IP”可重新匹配。`;return;} const country=countryCode(value.country),actual=countryCode(field('usage_country').value); diff --git a/envscope/web/tray.png b/envscope/web/tray.png new file mode 100644 index 0000000..d02214a Binary files /dev/null and b/envscope/web/tray.png differ diff --git a/envscope/web/watchdog_ui.mjs b/envscope/web/watchdog_ui.mjs index 23b3b7e..39400e7 100644 --- a/envscope/web/watchdog_ui.mjs +++ b/envscope/web/watchdog_ui.mjs @@ -14,7 +14,8 @@ export function initWatchdogForms(run){ const select=$(p+'-guard-client'),previous=select.value; select.replaceChildren(new Option('不重开客户端,仅恢复可读取节点',''),...(value.clients||[]).map(c=>new Option(c.label+' · '+c.path,c.id))); if([...select.options].some(o=>o.value===previous))select.value=previous; - $(p+'-guard-note').textContent=value.note+(!(value.clients||[]).length?' 未找到支持的已安装客户端;代理离线需手动处理。':''); + const capabilityNote=Object.values(value.capabilities||{}).map(c=>c.reason).filter(Boolean).join(' '); + $(p+'-guard-note').textContent=[capabilityNote,value.note,!(value.clients||[]).length?'未找到支持的已安装客户端;代理离线需手动处理。':''].filter(Boolean).join(' '); $(p+'-plan').replaceChildren(); } }catch(e){$(prefix+'-guard-note').textContent='未读取客户端:'+e.message;}finally{button.disabled=false;} diff --git a/envscope/web/workspace.css b/envscope/web/workspace.css new file mode 100644 index 0000000..3341f5e --- /dev/null +++ b/envscope/web/workspace.css @@ -0,0 +1,142 @@ +/* Desktop task surface. The existing modules retain their semantic states. */ +:root{--paper:#fff;--rail:#f5f6f4;--ink:#202322;--muted:#626763;--line:#dde1dc;--selected:#e5e9e3;--rail-width:204px;--danger:#ad3030;--radius:7px} +body{font-size:14px;line-height:1.65;min-width:960px;caret-color:var(--ink)} +::selection{background:#dce4d8;color:#171b17} +.skip-link{position:fixed;left:220px;top:-60px;z-index:80;padding:10px 16px;background:var(--ink);color:#fff;border-radius:var(--radius)} +.skip-link:focus{top:12px} +.app-header{position:fixed;inset:0 auto 0 0;width:var(--rail-width);max-width:none;margin:0;padding:28px 16px 180px;display:flex;flex-direction:column;align-items:stretch;justify-content:flex-start;gap:36px;background:var(--rail);border:0;border-right:1px solid var(--line);z-index:20} +.app-header{overflow-y:auto;scrollbar-width:thin;scrollbar-color:#bac2b5 var(--rail)} +header{display:flex;flex-direction:column;align-items:flex-start;gap:10px;padding:0 8px} +header a{display:flex;align-items:center;gap:10px;font-size:19px;letter-spacing:-.5px;font-weight:650} +header img{flex-shrink:0} +header span{padding-left:42px;font-size:11px;line-height:1.3} +nav{display:flex;flex-direction:column;align-items:stretch;gap:5px} +.tab{display:flex;align-items:center;gap:10px;min-height:42px;width:100%;border-radius:var(--radius);padding:10px 12px;font-size:13px;line-height:1.5;white-space:normal} +.tab[aria-current=page]{background:var(--selected);color:var(--ink)} +.tab:hover:not(:disabled){background:#ecefea} +.ui-icon{width:19px;height:19px;flex-shrink:0;fill:none;stroke:currentColor;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round} +#more-menu{margin:18px 0 0;padding-top:12px;border-top:1px solid var(--line)} +#more-menu>summary{display:flex;align-items:center;gap:10px;list-style:none;padding:10px 12px;width:100%;border-radius:var(--radius);font-size:13px;color:var(--muted)} +#more-menu>summary::-webkit-details-marker{display:none} +#more-menu>summary::after{content:'';width:6px;height:6px;border-right:1px solid currentColor;border-bottom:1px solid currentColor;transform:rotate(45deg);margin:0 0 3px auto} +#more-menu[open]>summary::after{transform:rotate(225deg);margin-bottom:-3px} +#more-menu>summary:hover{background:#ecefea;color:var(--ink)} +.more-menu-items{position:static;display:flex;align-items:stretch;border:0;border-radius:0;padding:4px 0;min-width:0;background:transparent;gap:3px} +main{margin:0 0 0 var(--rail-width);max-width:none;padding:26px 40px 48px;min-height:100vh} +.page,.connection-status,.connection-banner{max-width:1024px;margin-left:auto;margin-right:auto} +.page{scroll-margin-top:24px} +main:focus{outline:none} +h1{font-size:24px;line-height:1.4;letter-spacing:-.5px;font-weight:650;margin:0 0 10px} +h2{font-size:16px;line-height:1.5;font-weight:600} +h3{font-size:14px;line-height:1.6} +p{margin-bottom:14px} +.note,.limits p{font-size:12.5px;line-height:1.75;max-width:80ch;color:var(--muted)} +.subtle{font-size:13px;line-height:1.75;margin-bottom:24px;max-width:78ch} +.connection-status{height:22px;margin-top:0;margin-bottom:22px;font-size:11px;line-height:22px} +.connection-banner{padding:16px 20px;background:#fafbf9;border:1px solid #aeb6aa;border-radius:var(--radius);margin-bottom:20px} +button,input,select,textarea{border-radius:var(--radius);font-size:13px} +button{min-height:36px;padding:8px 14px;border-color:#bdc5b9;line-height:1.45;transition:background-color .12s,border-color .12s} +button:hover:not(:disabled){background:#f1f4ef;border-color:#919c89} +button:active:not(:disabled){background:#e8ede4} +button:focus-visible,a:focus-visible,summary:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible{outline:2px solid var(--ink);outline-offset:3px} +input,select,textarea{border:1px solid #b9c1b5;padding:9px 11px;line-height:1.5;background:#fff;color:var(--ink)} +input::placeholder,textarea::placeholder{color:#6d7269;opacity:1} +select,input:not([type=checkbox]):not([type=radio]){min-height:40px} +input[type=checkbox],input[type=radio]{accent-color:var(--ink);width:16px;height:16px;flex-shrink:0;margin:0 8px 0 0} +label{color:var(--ink);font-size:13px} +.checkbox{display:flex;align-items:center;gap:0;min-height:34px;margin:5px 0;font-size:13px} +.checkbox .note{margin-left:6px} +.form-grid{gap:16px 24px} +.actions{gap:12px 18px;margin:18px 0 14px} +.link{display:inline-flex;align-items:center;gap:8px;min-height:28px;padding:2px 0;line-height:1.55;text-decoration:underline;text-underline-offset:3px;border-radius:2px} +.primary,#start,#prepare-solution{min-height:40px;background:var(--ink);color:#fff;border-color:var(--ink);border-radius:var(--radius);font-weight:550} +#prepare-solution{padding:10px 18px;text-decoration:none} +.primary:hover:not(:disabled),#start:hover:not(:disabled),#prepare-solution:hover:not(:disabled){background:#394038;border-color:#394038} +#start{display:inline-flex;align-items:center;justify-content:center;gap:10px;min-width:140px;padding:11px 20px} +#start::before{content:'';width:18px;height:18px;flex-shrink:0;background:url('scan.svg') center/contain no-repeat} +#start:disabled{opacity:.7} +.intro-heading{align-items:center;gap:24px} +.intro-heading .actions{gap:6px} +#session-state{font-size:11px;line-height:1.6;font-variant-numeric:tabular-nums} +#mode-note{margin-top:8px;max-width:64ch} +.network-note{font-size:11px;margin-top:9px} +.target-environment{padding:18px 20px;margin:22px 0 24px;background:#f7f9f5;border:1px solid var(--line);border-radius:var(--radius)} +.target-heading{align-items:center;gap:18px} +.target-heading h2{font-size:14px;margin-bottom:5px} +.target-heading .note{margin:0} +#target-form{padding-top:18px;margin-top:16px;border-top:1px solid var(--line)} +.target-primary-fields{margin-top:0} +.target-ip{display:flex;align-items:center;justify-content:space-between;gap:16px} +.target-ip p{margin:0} +#target-form details{margin-top:16px} +.flow-progress{margin:20px 0 14px;gap:20px;border-bottom:1px solid var(--line)} +.flow-progress li{padding:11px 0;font-size:12px} +.result-summary{align-items:center;min-height:34px} +.result-summary h2{font-size:18px;line-height:1.5} +#verdict-note{margin:9px 0 20px;max-width:78ch} +.problem{padding:18px 0;border-left:0;border-right:0} +.problem-header{align-items:center} +.problem-header h3{font-size:15px} +.problem p{font-size:13px;line-height:1.75;max-width:80ch} +.problem-actions{gap:12px} +.problem .cause{margin-bottom:10px} +.manual-guidance,.inspection-error{color:var(--danger)} +.inspection-row[data-manual=true]>.inspection-state,.inspection-row[data-manual=true]>summary .inspection-state{color:var(--danger)} +.inspection-row{padding:13px 12px;border-top:1px solid var(--line);scroll-margin-top:20px} +.inspection-row[open]{background:#fafbf9;padding-bottom:20px} +.inspection-row>summary{gap:14px;align-items:center;font-size:13px} +.inspection-row>summary strong{font-size:13px;font-weight:550} +.inspection-row>summary small{margin-top:3px;font-size:11px} +.inspection-row>p,.inspection-row>h3,.inspection-row>.actions,.inspection-row>details{margin-left:34px} +.inspection-row>p{font-size:13px;line-height:1.75} +.inspection-state{font-size:12px;min-width:120px} +.inspection-state small{font-size:11px} +.inspection-toolbar{gap:10px 16px;margin:20px 0} +.check-filters{background:var(--rail);padding:16px;border-radius:var(--radius);gap:16px;margin-top:20px} +.check-filters label{font-size:12px} +.check-filters input,.check-filters select{margin-top:6px} +.tool-section{margin-top:26px;padding-top:22px} +.tool-section>h2{margin-bottom:12px} +.tool-section h3{margin-top:20px} +.tool-section .actions{margin-top:16px} +.upkeep-state{padding:18px 20px;border:1px solid var(--line);border-radius:var(--radius);background:#f7f9f5} +.upkeep-state p{margin:6px 0} +#upkeep-setup{margin-top:22px;border-top:0;padding-top:0} +#upkeep-setup>label:not(.checkbox){max-width:480px} +#upkeep-setup>.checkbox{display:inline-flex;margin:6px 24px 6px 0} +#upkeep-setup>h2{font-size:17px} +#advanced-maintenance{border-top:1px solid var(--line);padding-top:18px;margin-top:28px;font-size:13px} +#advanced-maintenance>summary{color:var(--ink);padding:4px 0;font-weight:550} +#advanced-maintenance .tool-section{margin-top:18px;padding-top:18px} +details>summary{max-width:100%;font-size:12px} +#opensource-tools>summary{font-size:14px;color:var(--ink)} +.account-guide{margin-top:20px} +table{font-size:13px;line-height:1.65;font-variant-numeric:tabular-nums} +th{font-size:12px;color:var(--muted);font-weight:500;background:var(--rail)} +td,th{padding:12px 14px;border-color:var(--line)} +.information-output{border:1px solid var(--line);border-radius:var(--radius);padding:18px 20px;margin-top:18px} +.information-output:empty{display:none} +.information-output dl div{grid-template-columns:160px minmax(0,1fr);padding:8px 0;margin:0;border-bottom:1px solid #e8ece5} +.information-output dl div:last-child{border-bottom:0} +#check-preferences-panel{border-top:0;margin-top:26px;padding-top:0} +#check-preferences-form{display:grid;grid-template-columns:1fr 1fr;gap:4px 20px} +#check-preferences-form>p,#check-preferences-form>.checkbox:has(#optional-failures),#check-preferences-form>.checkbox:has(#diagnostic-logging),#check-preferences-form>button{grid-column:1/-1} +#check-preferences-form>button{justify-self:start;margin-top:8px} +#check-preferences-form>p{margin:6px 0 10px} +.desktop-preferences{max-width:none;padding:22px 0;margin:24px 0;border-top:1px solid var(--line);border-bottom:1px solid var(--line)} +.desktop-preferences h2{font-size:16px} +.desktop-preferences h3{margin-top:22px} +.learning{max-width:780px;margin:24px auto} +.learning-top{font-size:12px} +.learning h1{font-size:24px} +.learning-inline{margin:0 0 24px;padding:14px 18px;border:1px solid var(--line);border-radius:var(--radius);background:#fafbf9} +.learning-inline h1{font-size:14px} +.learning-inline #learning-body p{font-size:12px} +footer{position:fixed;left:0;bottom:0;width:var(--rail-width);max-width:none;margin:0;padding:18px 24px;display:flex;flex-direction:column;align-items:flex-start;gap:12px;border:0;background:var(--rail);z-index:21;font-size:11px} +footer span{margin:0;text-align:left;line-height:1.8;font-size:11px;color:var(--muted)} +footer br{display:block} +footer .link{font-size:12px;min-height:26px} +footer .ui-icon{width:16px;height:16px} +#quit{color:var(--muted)} +@media (max-width:1100px){:root{--rail-width:184px}main{padding:24px 28px 40px}.app-header{padding-left:12px;padding-right:12px}footer{padding-left:20px}.tab{font-size:12.5px}.check-filters{grid-template-columns:1.5fr 1fr 1fr}.inspection-row>summary{gap:10px}.result{grid-template-columns:135px minmax(0,1fr) 80px;gap:16px}.form-grid{gap:14px 20px}} +@media (prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important;scroll-behavior:auto!important}} diff --git a/envscope/web/workspace_ui.mjs b/envscope/web/workspace_ui.mjs new file mode 100644 index 0000000..87dd12e --- /dev/null +++ b/envscope/web/workspace_ui.mjs @@ -0,0 +1,21 @@ +// Reorder existing controls without duplicating their state or action handlers. +export function initWorkspace(){ + const setup=document.getElementById('upkeep-prepare')?.closest('section'); + const status=document.querySelector('#page-upkeep>.upkeep-state'); + if(setup&&status){setup.id='upkeep-setup';status.after(setup);} + const advanced=document.createElement('details');advanced.id='advanced-maintenance'; + const title=document.createElement('summary');title.textContent='系统守护与高级修复';advanced.append(title); + for(const id of ['timezone-service-panel','process-guard-panel','client-panel','language-panel','network-panel','routing-panel','components-panel','software-panel']){ + const section=document.getElementById(id);if(section)advanced.append(section); + } + setup?.after(advanced); + const tools=document.getElementById('environment-tools'),heading=document.querySelector('#page-tools>h1'); + if(tools&&heading)heading.after(tools); + const sources=document.getElementById('opensource-tools'); + if(sources){ + const disclosure=document.createElement('details'),summary=document.createElement('summary'); + disclosure.id=sources.id;disclosure.className=sources.className;summary.textContent='开源方案使用说明'; + sources.querySelector('h2')?.remove();disclosure.append(summary,...sources.childNodes);sources.replaceWith(disclosure); + document.getElementById('tool-output')?.before(disclosure); + } +} diff --git a/native/timezone_windows.c b/native/timezone_windows.c new file mode 100644 index 0000000..bb4b430 --- /dev/null +++ b/native/timezone_windows.c @@ -0,0 +1,214 @@ +/* Restricted Windows timezone worker. No shell, user scripts or network. */ +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0A00 +#endif +#ifndef WINVER +#define WINVER 0x0A00 +#endif +#ifndef UNICODE +#define UNICODE +#endif +#ifndef _UNICODE +#define _UNICODE +#endif +#define WIN32_LEAN_AND_MEAN +#include