feat(web): protect phone data and polish release UI
This commit is contained in:
+110
-15
@@ -102,8 +102,20 @@ async function builtAssetHandler(request, response) {
|
||||
phoneNumbers: [`+852 5550 10${Math.max(0, fixtureIndex)}`],
|
||||
};
|
||||
}
|
||||
if (FLEET_FIXTURE && /^\/api\/v1\/instances\/[^/]+\/messages$/u.test(pathname))
|
||||
body = { messages: [] };
|
||||
if (FLEET_FIXTURE && /^\/api\/v1\/instances\/[^/]+\/messages$/u.test(pathname)) {
|
||||
const fixtureIndex = FLEET_FIXTURE_ITEMS.findIndex((item) => pathname.includes(item.id));
|
||||
body = {
|
||||
messages: [
|
||||
{
|
||||
id: `sms-${Math.max(0, fixtureIndex)}`,
|
||||
direction: 'incoming',
|
||||
phoneNumber: `1390013900${Math.max(0, fixtureIndex)}`,
|
||||
content: 'status',
|
||||
timestamp: '2026-07-30T08:30:00.000Z',
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
if (
|
||||
pathname === '/api/v1/jobs' ||
|
||||
pathname === '/api/v1/audit' ||
|
||||
@@ -134,7 +146,10 @@ async function builtAssetHandler(request, response) {
|
||||
response.end(JSON.stringify({ error: 'Not Found', statusCode: 404 }));
|
||||
return;
|
||||
}
|
||||
const relative = pathname.startsWith('/assets/') ? pathname.slice(1) : 'index.html';
|
||||
const relative =
|
||||
pathname.startsWith('/assets/') || pathname === '/favicon.svg'
|
||||
? pathname.slice(1)
|
||||
: 'index.html';
|
||||
try {
|
||||
const asset = await readFile(join(DIST, relative));
|
||||
response.statusCode = 200;
|
||||
@@ -144,6 +159,7 @@ async function builtAssetHandler(request, response) {
|
||||
'.html': 'text/html; charset=utf-8',
|
||||
'.js': 'text/javascript; charset=utf-8',
|
||||
'.css': 'text/css; charset=utf-8',
|
||||
'.svg': 'image/svg+xml; charset=utf-8',
|
||||
}[extname(relative)] ?? 'application/octet-stream',
|
||||
);
|
||||
response.end(asset);
|
||||
@@ -396,9 +412,15 @@ async function fleetLayout(cdp) {
|
||||
const firstResource = document.querySelector('.fleet-resource-row');
|
||||
const telemetry = document.querySelector('.fleet-card-telemetry');
|
||||
const footer = document.querySelector('.fleet-card-footer');
|
||||
const footerNumber = document.querySelector('.fleet-card-footer-primary > span:last-child');
|
||||
const menuTrigger = document.querySelector('.fleet-card-menu-trigger');
|
||||
const menuPanel = document.querySelector('.fleet-card-menu-panel');
|
||||
const checkbox = document.querySelector('.fleet-card-select');
|
||||
const hardware = document.querySelector('.fleet-card-hardware');
|
||||
const phoneField = document.querySelector('.fleet-card-phone');
|
||||
const phoneValue = document.querySelector('.fleet-card-phone-value');
|
||||
const phoneToggle = document.querySelector('.fleet-phone-privacy-toggle');
|
||||
const temperatureField = document.querySelector('.fleet-card-hardware > div:not(.fleet-card-phone)');
|
||||
const bounds = (element) => {
|
||||
if (!element) return null;
|
||||
const rect = element.getBoundingClientRect();
|
||||
@@ -406,12 +428,14 @@ async function fleetLayout(cdp) {
|
||||
};
|
||||
const hardwareEntries = [...document.querySelectorAll('.fleet-card-hardware > div')].map((item) => {
|
||||
const label = item.querySelector('dt');
|
||||
const value = item.querySelector('dd');
|
||||
const value = item.querySelector('.fleet-card-phone-value') ?? item.querySelector('dd');
|
||||
return {
|
||||
labelHeight: label?.getBoundingClientRect().height ?? 0,
|
||||
labelFits: label ? label.scrollWidth <= label.clientWidth : false,
|
||||
valueHeight: value?.getBoundingClientRect().height ?? 0,
|
||||
valueFits: value ? value.scrollWidth <= value.clientWidth : false,
|
||||
valueClientWidth: value?.clientWidth ?? 0,
|
||||
valueScrollWidth: value?.scrollWidth ?? 0,
|
||||
};
|
||||
});
|
||||
const navigation = [...document.querySelectorAll('nav[aria-label="全局导航"] a')].map((item) => {
|
||||
@@ -438,9 +462,16 @@ async function fleetLayout(cdp) {
|
||||
firstResource: bounds(firstResource),
|
||||
telemetry: bounds(telemetry),
|
||||
footer: bounds(footer),
|
||||
footerNumber: bounds(footerNumber),
|
||||
footerNumberFits: footerNumber ? footerNumber.scrollWidth <= footerNumber.clientWidth : false,
|
||||
menuTrigger: bounds(menuTrigger),
|
||||
menuPanel: bounds(menuPanel),
|
||||
checkbox: bounds(checkbox),
|
||||
hardware: bounds(hardware),
|
||||
phoneField: bounds(phoneField),
|
||||
phoneValue: bounds(phoneValue),
|
||||
phoneToggle: bounds(phoneToggle),
|
||||
temperatureField: bounds(temperatureField),
|
||||
hardwareEntries,
|
||||
navigation,
|
||||
};
|
||||
@@ -575,6 +606,31 @@ try {
|
||||
`document.querySelector('.fleet-card-version')?.textContent.trim() === 'SimAdmin 2.4.0'`,
|
||||
`Fleet SimAdmin version did not synchronize at ${viewport.width}px`,
|
||||
);
|
||||
await eventually(
|
||||
cdp,
|
||||
`document.querySelector('.fleet-card-footer-primary')?.textContent.includes('139 •••• 9000')`,
|
||||
`Fleet SMS phone did not load privately at ${viewport.width}px`,
|
||||
);
|
||||
if (viewport.width === 390) {
|
||||
const branding = await evaluate(
|
||||
cdp,
|
||||
`(() => ({ title: document.title, favicon: new URL(document.querySelector('link[rel="icon"]').href).pathname, theme: document.querySelector('meta[name="theme-color"]').content }))()`,
|
||||
);
|
||||
assert.deepEqual(branding, {
|
||||
title: 'SimAdmin Control · 多节点控制台',
|
||||
favicon: '/favicon.svg',
|
||||
theme: '#f3f0e7',
|
||||
});
|
||||
}
|
||||
const privacyText = await evaluate(
|
||||
cdp,
|
||||
`(() => ({ text: document.body.textContent, toggleCount: document.querySelectorAll('.fleet-phone-privacy-toggle').length }))()`,
|
||||
);
|
||||
assert.equal(privacyText.toggleCount, FLEET_FIXTURE_ITEMS.length);
|
||||
assert.match(privacyText.text, /\+852 •••• 0100/u);
|
||||
assert.match(privacyText.text, /139 •••• 9000/u);
|
||||
assert.doesNotMatch(privacyText.text, /\+852 5550 100/u);
|
||||
assert.doesNotMatch(privacyText.text, /13900139000/u);
|
||||
const layout = await fleetLayout(cdp);
|
||||
assert.equal(
|
||||
layout.scrollWidth <= layout.viewport,
|
||||
@@ -585,7 +641,11 @@ try {
|
||||
assert.equal(layout.groupDisplay, 'flex', `${viewport.width}px tag groups must use flex`);
|
||||
assert.equal(layout.resourceDisplay, 'grid', `${viewport.width}px resource rows must use grid`);
|
||||
assert.equal(layout.telemetryDisplay, 'grid', `${viewport.width}px telemetry must use grid`);
|
||||
assert.equal(layout.footerDisplay, 'flex', `${viewport.width}px SMS footer must use flex`);
|
||||
assert.equal(
|
||||
layout.footerDisplay,
|
||||
viewport.width >= 1280 ? 'grid' : 'flex',
|
||||
`${viewport.width}px SMS footer layout`,
|
||||
);
|
||||
assert.equal(
|
||||
layout.workspaceDisplay,
|
||||
'grid',
|
||||
@@ -604,7 +664,13 @@ try {
|
||||
layout.firstResource &&
|
||||
layout.telemetry &&
|
||||
layout.footer &&
|
||||
layout.menuTrigger,
|
||||
layout.footerNumber &&
|
||||
layout.menuTrigger &&
|
||||
layout.hardware &&
|
||||
layout.phoneField &&
|
||||
layout.phoneValue &&
|
||||
layout.phoneToggle &&
|
||||
layout.temperatureField,
|
||||
`${viewport.width}px Fleet information hierarchy must render`,
|
||||
);
|
||||
for (const [name, bounds] of [
|
||||
@@ -651,16 +717,30 @@ try {
|
||||
`${viewport.width}px card action trigger is too small: ${JSON.stringify(layout.menuTrigger)}`,
|
||||
);
|
||||
assert.equal(
|
||||
layout.hardwareEntries.length === FLEET_FIXTURE_ITEMS.length * 2 &&
|
||||
layout.hardwareEntries.every(
|
||||
(entry) =>
|
||||
entry.labelFits &&
|
||||
entry.valueFits &&
|
||||
entry.labelHeight <= 20 &&
|
||||
entry.valueHeight <= 20,
|
||||
),
|
||||
layout.footerNumberFits,
|
||||
true,
|
||||
`${viewport.width}px hardware facts wrap or clip: ${JSON.stringify(layout.hardwareEntries)}`,
|
||||
`${viewport.width}px latest SMS phone must remain fully visible`,
|
||||
);
|
||||
assert.equal(
|
||||
Math.abs(layout.phoneToggle.width - 40) <= 0.5 &&
|
||||
Math.abs(layout.phoneToggle.height - 40) <= 0.5,
|
||||
true,
|
||||
`${viewport.width}px privacy toggle must remain 40px: ${JSON.stringify(layout.phoneToggle)}`,
|
||||
);
|
||||
assert.equal(
|
||||
layout.phoneField.left >= layout.hardware.left - 0.5 &&
|
||||
layout.temperatureField.right <= layout.hardware.right + 0.5 &&
|
||||
layout.phoneValue.right <= layout.phoneToggle.left + 0.5 &&
|
||||
layout.phoneToggle.right <= layout.phoneField.right + 0.5 &&
|
||||
layout.phoneField.right < layout.temperatureField.left,
|
||||
true,
|
||||
`${viewport.width}px phone and temperature geometry overlaps: ${JSON.stringify(layout)}`,
|
||||
);
|
||||
assert.equal(
|
||||
layout.hardwareEntries.length === FLEET_FIXTURE_ITEMS.length * 2 &&
|
||||
layout.hardwareEntries.every((entry) => entry.labelFits && entry.valueFits),
|
||||
true,
|
||||
`${viewport.width}px hardware facts overflow: ${JSON.stringify(layout.hardwareEntries)}`,
|
||||
);
|
||||
if (viewport.sidebarMode === 'left') {
|
||||
assert.equal(
|
||||
@@ -705,6 +785,21 @@ try {
|
||||
await captureScreenshot(cdp, 'warm-fleet-390-cards.png');
|
||||
}
|
||||
if (viewport.width === 1440) {
|
||||
await evaluate(cdp, `document.querySelector('.fleet-phone-privacy-toggle').click()`);
|
||||
await eventually(
|
||||
cdp,
|
||||
`document.querySelector('.fleet-card')?.textContent.includes('+852 5550 100') && document.querySelector('.fleet-card')?.textContent.includes('13900139000')`,
|
||||
'Fleet phone disclosure did not reveal both phone locations',
|
||||
);
|
||||
const disclosedText = await evaluate(
|
||||
cdp,
|
||||
`[...document.querySelectorAll('.fleet-card')].map((card) => card.textContent)`,
|
||||
);
|
||||
assert.match(disclosedText[0], /\+852 5550 100/u);
|
||||
assert.match(disclosedText[0], /13900139000/u);
|
||||
assert.doesNotMatch(disclosedText[1], /\+852 5550/u);
|
||||
assert.doesNotMatch(disclosedText[1], /1390013900/u);
|
||||
await evaluate(cdp, `document.querySelector('.fleet-phone-privacy-toggle').click()`);
|
||||
await evaluate(cdp, `document.querySelector('.fleet-card-menu-trigger').click()`);
|
||||
await eventually(
|
||||
cdp,
|
||||
|
||||
Reference in New Issue
Block a user