chore: 验收脚本入库(矩阵/工作空间/滚动)+ 修正脱敏自检命令
- deploy/verify-matrix.js:6 尺寸 × 3 视图 × 深浅色矩阵,断言纵向滚动(仅
minimal/workspace)、文字亮度阈值、深色是否生效、零运行时异常
- deploy/verify-workspace.js:触发 workspace blocked 态,读弹窗卡片 computed style
- deploy/verify-scroll.js:多视口滚动复现(含 900x520 这类矮窗口)
三者只从环境变量 PORTAL_COOKIE 取会话,无任何硬编码凭据
- README 自检命令改为排除占位符(<...>、${VAR:-}、change-me),避免误报
- 自定义文件表登记上述脚本
This commit is contained in:
@ -430,6 +430,9 @@ NAVI_SSH_PASS='<密码>' bash deploy/zd-host.sh v50
|
||||
| `web/patch-index.py` | 幂等地把 `navi.css` / `navi.js` 注入 Dashy 的 index.html |
|
||||
| `web/extension/install.html` | 插件安装引导页(含同步令牌展示) |
|
||||
| `extension/chrome/` `extension/firefox/` | 浏览器扩展(MV3 / MV2) |
|
||||
| `deploy/verify-matrix.js` | 生产验收矩阵:6 尺寸 × 3 视图 × 深浅色,断言滚动/文字亮度/深色生效/零异常(需 `PORTAL_COOKIE`) |
|
||||
| `deploy/verify-workspace.js` | 工作空间深色与"无法内嵌"弹窗验收(触发 blocked 态并读 computed style) |
|
||||
| `deploy/verify-scroll.js` | 多视口纵向滚动复现/回归检查(含矮窗口) |
|
||||
| `deploy/zd-host.sh` | **合并后**的部署脚本(host 模式,接受数秒中断,失败自动回滚) |
|
||||
| `portal/`(index.html、404.html、500.html、portal.css、favicon) | 门户静态页与自定义错误页,进镜像的 `/app/portal` |
|
||||
| `admin-server/auth.go` | 容器内置统一登录(HMAC 会话 cookie、恒定时间比较、跳转白名单) |
|
||||
@ -795,6 +798,7 @@ MIT
|
||||
文档中的 `10.0.0.10`/`*.example.com` 示例地址。发布前自检:
|
||||
|
||||
```bash
|
||||
git grep -nE 'NAVI_(SYNC|ADMIN)_TOKEN=.|PORTAL_PASSWORD=.+' -- . || echo '无凭据'
|
||||
# 只匹配"看起来是真值"的赋值:排除 <占位>、${VAR:-}、change-me 之类
|
||||
git grep -nE '(NAVI_(SYNC|ADMIN)_TOKEN|PORTAL_PASSWORD|PORTAL_SESSION_SECRET)=[^<${]' -- . || echo '无凭据'
|
||||
git ls-files | grep -E '(^|/)conf\.yml$|\.env$|portal-auth\.json$|(fullchain|privkey)\.pem$' || echo '无敏感文件'
|
||||
```
|
||||
|
||||
53
deploy/verify-matrix.js
Normal file
53
deploy/verify-matrix.js
Normal file
@ -0,0 +1,53 @@
|
||||
const { chromium }=require('playwright-core');
|
||||
function rgb(s){const m=s.match(/[\d.]+/g);return m?m.slice(0,4).map(Number):[]}
|
||||
function L(s){const [r=0,g=0,b=0]=rgb(s);return Math.round(.2126*r+.7152*g+.0722*b)}
|
||||
const C=process.env.PORTAL_COOKIE;
|
||||
(async()=>{
|
||||
const b=await chromium.connectOverCDP('http://127.0.0.1:9222');
|
||||
const views=[['/','home'],['/minimal','minimal'],['/workspace','workspace']];
|
||||
const sizes=[[1280,720],[1024,600],[900,520],[390,780]];
|
||||
const rows=[]; let bad=0;
|
||||
for(const [w,h] of sizes){
|
||||
for(const [path,name] of views){
|
||||
const ctx=await b.newContext({ignoreHTTPSErrors:true,viewport:{width:w,height:h}});
|
||||
await ctx.addCookies([{name:'portal_session',value:C,domain:'.example.com',path:'/',httpOnly:true,secure:true,sameSite:'Lax'}]);
|
||||
const p=await ctx.newPage(); const errs=[]; p.on('pageerror',e=>errs.push(String(e)));
|
||||
await p.emulateMedia({colorScheme:'dark'}); await p.addInitScript(()=>localStorage.removeItem('navi-theme'));
|
||||
await p.goto('https://dashy.example.com'+path,{waitUntil:'networkidle',timeout:90000}); await p.waitForTimeout(3200);
|
||||
const r=await p.evaluate(()=>{
|
||||
const g=s=>{const e=document.querySelector(s);if(!e)return null;const c=getComputedStyle(e);return {c:c.color,bg:c.backgroundColor}};
|
||||
const de=document.documentElement;
|
||||
return {vScroll:de.scrollHeight>innerHeight+2,overflow:de.scrollHeight-innerHeight,
|
||||
hScroll:de.scrollWidth>innerWidth+2,
|
||||
title:g('header h1')||g('.minimal-home .title-and-search h1'),
|
||||
sec:g('div.collapsable .section-header h3')||g('.minimal-headings-row .headings'),
|
||||
item:g('a.item .text'),desc:g('a.item .description'),
|
||||
side:g('nav.side-bar .side-bar-item'),content:g('.work-space .web-content'),
|
||||
footer:g('.navi-footer')};
|
||||
});
|
||||
const t=r.title?L(r.title.c):0, s=r.sec?L(r.sec.c):0, it=r.item?L(r.item.c):0, d=r.desc?L(r.desc.c):0;
|
||||
const side=r.side?L(r.side.bg):null, content=r.content?L(r.content.bg):null;
|
||||
const foot=r.footer?L(r.footer.bg):null;
|
||||
const row={size:`${w}x${h}`,view:name,scroll:r.vScroll?'SCROLL':'-',overflow:r.overflow,hScroll:r.hScroll?'HSCROLL':'-',title:t,sec:s,item:it,desc:d,sideBg:side,contentBg:content,footBg:foot,err:errs.length};
|
||||
const fail=[];
|
||||
if(r.vScroll && name!=='home')fail.push('滚动+'+r.overflow); // 首页内容长,允许滚动
|
||||
if(r.hScroll)fail.push('横向滚动');
|
||||
if(!t)fail.push('未取到标题');
|
||||
if(t&&t<190)fail.push('深色未生效/标题亮度'+t);
|
||||
if(it&&it<190)fail.push('卡片字'+it);
|
||||
if(d&&d<130)fail.push('说明字'+d);
|
||||
if(side!==null&&side>120)fail.push('侧栏白底'+side);
|
||||
if(content!==null&&content>120)fail.push('内嵌白底'+content);
|
||||
if(errs.length)fail.push('异常'+errs.length);
|
||||
row.result=fail.length?('✗ '+fail.join(' / ')):'✓';
|
||||
if(fail.length) bad++;
|
||||
rows.push(row);
|
||||
if(name!=='home'){ await p.screenshot({path:`/tmp/m-${name}-${w}x${h}.png`}); }
|
||||
await ctx.close();
|
||||
}
|
||||
}
|
||||
for(const r of rows) console.log(`${r.size.padEnd(9)} ${r.view.padEnd(10)} 滚动=${r.scroll.padEnd(7)} 溢=${String(r.overflow).padStart(4)} 标题=${String(r.title).padStart(3)} 卡片=${String(r.item).padStart(3)} 说明=${String(r.desc).padStart(3)} 侧栏底=${String(r.sideBg).padStart(3)} 内嵌底=${String(r.contentBg).padStart(3)} 备案底=${String(r.footBg).padStart(3)} ${r.result}`);
|
||||
console.log(bad? `FAILED_ROWS=${bad}` : 'ALL_OK');
|
||||
await b.close();
|
||||
if(bad) process.exitCode=2;
|
||||
})().catch(e=>{console.error(e);process.exit(1)});
|
||||
32
deploy/verify-scroll.js
Normal file
32
deploy/verify-scroll.js
Normal file
@ -0,0 +1,32 @@
|
||||
const { chromium }=require('playwright-core');
|
||||
const C=process.env.PORTAL_COOKIE;
|
||||
(async()=>{
|
||||
const b=await chromium.connectOverCDP('http://127.0.0.1:9222');
|
||||
for(const [w,h] of [[1280,720],[1280,600],[1024,600],[900,520],[768,600],[390,780]]){
|
||||
const ctx=await b.newContext({ignoreHTTPSErrors:true,viewport:{width:w,height:h}});
|
||||
await ctx.addCookies([{name:'portal_session',value:C,domain:'.example.com',path:'/',httpOnly:true,secure:true,sameSite:'Lax'}]);
|
||||
const p=await ctx.newPage();
|
||||
await p.emulateMedia({colorScheme:'dark'}); await p.addInitScript(()=>localStorage.removeItem('navi-theme'));
|
||||
await p.goto('https://dashy.example.com/minimal',{waitUntil:'networkidle',timeout:90000}); await p.waitForTimeout(1800);
|
||||
const r=await p.evaluate(()=>{
|
||||
const de=document.documentElement;
|
||||
const foot=document.querySelector('.navi-footer');
|
||||
const fr=foot?foot.getBoundingClientRect():null;
|
||||
// 谁在撑高页面
|
||||
let worst=null;
|
||||
for(const e of document.querySelectorAll('body *')){
|
||||
const b=e.getBoundingClientRect();
|
||||
if(b.height<4) continue;
|
||||
if(de.scrollHeight>innerHeight+2 && b.bottom>innerHeight+2){
|
||||
if(!worst||b.bottom>worst.bottom) worst={cls:e.className.toString().slice(0,60),bottom:Math.round(b.bottom),h:Math.round(b.height)};
|
||||
}
|
||||
}
|
||||
return {scrollH:de.scrollHeight,viewH:innerHeight,overflow:de.scrollHeight-innerHeight,scrollable:de.scrollHeight>innerHeight+2,
|
||||
bodyH:document.body.scrollHeight,footerBottom:fr?Math.round(fr.bottom):null,worst};
|
||||
});
|
||||
console.log(`${String(w).padStart(4)}x${String(h).padEnd(4)} docH=${String(r.scrollH).padEnd(5)} viewH=${String(r.viewH).padEnd(5)} 溢出=${String(r.overflow).padEnd(4)} 可滚=${r.scrollable?'YES':'no '} footer底=${r.footerBottom} 元凶=${r.worst?JSON.stringify(r.worst):'无'}`);
|
||||
await p.screenshot({path:`/tmp/mini-${w}x${h}.png`});
|
||||
await ctx.close();
|
||||
}
|
||||
await b.close();
|
||||
})().catch(e=>{console.error(e);process.exit(1)});
|
||||
35
deploy/verify-workspace.js
Normal file
35
deploy/verify-workspace.js
Normal file
@ -0,0 +1,35 @@
|
||||
const { chromium }=require('playwright-core');
|
||||
function rgb(s){const m=s.match(/[\d.]+/g);return m?m.slice(0,4).map(Number):[]}
|
||||
function L(a){return Math.round(.2126*(a[0]||0)+.7152*(a[1]||0)+.0722*(a[2]||0))}
|
||||
const C=process.env.PORTAL_COOKIE;
|
||||
(async()=>{
|
||||
const b=await chromium.connectOverCDP('http://127.0.0.1:9222');
|
||||
const ctx=await b.newContext({ignoreHTTPSErrors:true,viewport:{width:1400,height:900}});
|
||||
await ctx.addCookies([{name:'portal_session',value:C,domain:'.example.com',path:'/',httpOnly:true,secure:true,sameSite:'Lax'}]);
|
||||
const p=await ctx.newPage(); const errs=[]; p.on('pageerror',e=>errs.push(String(e)));
|
||||
await p.emulateMedia({colorScheme:'dark'}); await p.addInitScript(()=>{try{localStorage.removeItem('navi-theme')}catch(e){}});
|
||||
await p.goto('https://dashy.example.com/workspace',{waitUntil:'networkidle',timeout:90000}); await p.waitForTimeout(3000);
|
||||
const n=await p.evaluate(()=>[...document.querySelectorAll('nav.side-bar a,nav.side-bar button,.side-bar-item')].length);
|
||||
let modal=null;
|
||||
for(let i=0;i<n;i++){
|
||||
await p.evaluate(i=>{const e=[...document.querySelectorAll('nav.side-bar a,nav.side-bar button,.side-bar-item')][i];e&&e.click()},i);
|
||||
await p.waitForTimeout(3500);
|
||||
modal=await p.evaluate(()=>{
|
||||
const card=document.querySelector('.navi-ws-modal-card'); if(!card) return null;
|
||||
const c=getComputedStyle(card); const t=document.querySelector('.navi-ws-modal-title');
|
||||
return {bg:c.backgroundColor, title:t?getComputedStyle(t).color:null, text:(card.textContent||'').trim().slice(0,60)};
|
||||
});
|
||||
if(modal) break;
|
||||
}
|
||||
const r=await p.evaluate(()=>{
|
||||
const g=s=>{const e=document.querySelector(s);if(!e)return null;const c=getComputedStyle(e);return c.backgroundColor};
|
||||
return {ws:(document.querySelector('.work-space')||{dataset:{}}).dataset?.naviWs||null,
|
||||
modalCard:g('.navi-ws-modal-card'),modalOk:g('.navi-ws-modal-ok'),webContent:g('.work-space .web-content')};
|
||||
});
|
||||
console.log(JSON.stringify({sidebarItems:n,modalSeen:!!modal,modal,r,errors:errs},null,1));
|
||||
await p.screenshot({path:'/tmp/ws-v50-modal.png',fullPage:true});
|
||||
await ctx.close(); await b.close();
|
||||
const ok = !modal || (L(rgb(modal.bg))<95 && L(rgb(modal.title||'rgb(255,255,255)'))>190);
|
||||
console.log(ok?'MODAL_DARK_OK':'MODAL_STILL_LIGHT');
|
||||
if(!ok) process.exitCode=2;
|
||||
})().catch(e=>{console.error(e);process.exit(1)});
|
||||
Reference in New Issue
Block a user