-
- LIVE NODE MATRIX
-
显示 {model.filteredCount} / {model.totalCount}
{model.pageCount > 1 ? ` · 第 ${model.page}/${model.pageCount} 页` : null}
@@ -969,297 +969,328 @@ export function FleetPage({
<>
- {model.rows.map((row) => (
-
-
-
-
-
-
- {(() => {
- const origin = canonicalHttpOrigin(row.instance.url);
- return origin ? (
+ {model.rows.map((row) => {
+ const phoneNumbers = row.status?.summary?.resources?.phoneNumbers ?? [];
+ const latestMessage = messageStates.get(row.id)?.latest;
+ const phoneNumbersRevealed = revealedPhoneIds.has(row.id);
+ const hasPrivatePhone = phoneNumbers.length > 0 || Boolean(latestMessage);
+ return (
+
+
+
+
+
+ {initials(row.displayName)}
+
+
+
+
+
+ {STATUS_LABELS[row.statusKind]}
+
+ {selectionMode ? (
+
+ ) : null}
+
+
+ {cardMenuId === row.id ? (
+
+ ) : null}
+
+
+
+
+
+
+ {(() => {
+ const origin = canonicalHttpOrigin(row.instance.url);
+ return origin ? (
+
+ {origin}
+
+
+ ) : (
+
+ 源地址无效
+
+ );
+ })()}
+
+ {row.instance.description ? (
+
{row.instance.description}
+ ) : null}
+ {row.anomalies.length > 0 ? (
+
+ {row.anomalies.join(', ')}
+
+ ) : null}
+ {row.tags.length > 0 ? (
+
+ {row.tags.map((item) => (
+
+
+ {item}
+
+ ))}
+
+ ) : null}
+ {row.capabilities.length > 0 ? (
+
+ {row.capabilities.map((item) => (
+
+
+ {capabilityLabel(item)}
+
+ ))}
+
+ ) : null}
- {row.instance.description ? (
-
{row.instance.description}
- ) : null}
- {row.anomalies.length > 0 ? (
+
+
+
-
+
+ 手机号
+
+ -
+
+ {phoneNumbers.length > 0
+ ? formatPhoneNumbers(phoneNumbers, phoneNumbersRevealed)
+ : '暂未获取'}
+
+ {hasPrivatePhone ? (
+
+ ) : null}
+
+
+
+
-
+
+ 最高温度
+
+ -
+ {temperature(row.status?.summary?.resources?.maxTemperatureCelsius)}
+
+
+
+
+
+
+
+ CPU
+
+ {row.status?.summary?.resources?.cpuPercent === undefined ? (
+
--
+ ) : (
+
+
+
{percent(row.status.summary.resources.cpuPercent)}
+
+ )}
+
+
+
+
+ 内存
+
+ {row.status?.summary?.resources?.memoryPercent === undefined ? (
+
--
+ ) : (
+
+
+
+ {percent(row.status.summary.resources.memoryPercent)}
+
+
+ )}
+
+
+
+ {messageStates.get(row.id)?.unavailable ? (
+ <>
+
+
+ 短信暂不可用
+
+ --
+ >
+ ) : messageStates.get(row.id)?.latest ? (
+ <>
+
+
+
+ {messageDirection(messageStates.get(row.id)!.latest!.direction)}
+
+ ·
+
+ {formatPhoneNumbers(
+ [messageStates.get(row.id)!.latest!.phoneNumber],
+ phoneNumbersRevealed,
+ )}
+
+
+
+ >
+ ) : (
+ <>
+
+
+ 暂无短信
+
+ 未收到
+ >
+ )}
+
+ {cardAction?.id === row.id ? (
- {row.anomalies.join(', ')}
+ {cardAction.error ?? cardAction.message}
) : null}
- {row.tags.length > 0 ? (
-
- {row.tags.map((item) => (
-
-
- {item}
-
- ))}
-
- ) : null}
- {row.capabilities.length > 0 ? (
-
- {row.capabilities.map((item) => (
-
-
- {capabilityLabel(item)}
-
- ))}
-
- ) : null}
-
-
-
-
-
-
- 手机号
-
- -
- {row.status?.summary?.resources?.phoneNumbers?.join('、') || '暂未获取'}
-
-
-
-
-
-
- 最高温度
-
- -
- {temperature(row.status?.summary?.resources?.maxTemperatureCelsius)}
-
-
-
-
-
-
-
- CPU
-
- {row.status?.summary?.resources?.cpuPercent === undefined ? (
-
--
- ) : (
-
-
-
{percent(row.status.summary.resources.cpuPercent)}
-
- )}
-
-
-
-
- 内存
-
- {row.status?.summary?.resources?.memoryPercent === undefined ? (
-
--
- ) : (
-
-
-
{percent(row.status.summary.resources.memoryPercent)}
-
- )}
-
-
-
- {messageStates.get(row.id)?.unavailable ? (
- <>
-
-
- 短信暂不可用
-
- --
- >
- ) : messageStates.get(row.id)?.latest ? (
- <>
-
-
-
- {messageDirection(messageStates.get(row.id)!.latest!.direction)}
-
- ·
- {messageStates.get(row.id)!.latest!.phoneNumber}
-
-
- >
- ) : (
- <>
-
-
- 暂无短信
-
- 未收到
- >
- )}
-
- {cardAction?.id === row.id ? (
-
- {cardAction.error ?? cardAction.message}
-
- ) : null}
-
- ))}
+
+ );
+ })}
添加实例
diff --git a/apps/web/src/styles.css b/apps/web/src/styles.css
index 7b012fb..ecc873a 100644
--- a/apps/web/src/styles.css
+++ b/apps/web/src/styles.css
@@ -253,11 +253,6 @@ code {
color: #fff;
font-size: 1rem;
}
-.product-copy small {
- color: #91a3b9;
- font-size: 0.68rem;
- letter-spacing: 0.03em;
-}
.topbar-actions {
display: flex;
align-items: center;
@@ -384,16 +379,13 @@ main > section > header,
margin-bottom: 1rem;
border-bottom: 1px solid var(--line);
}
-.fleet-heading p,
main > section > header p,
.page-heading p {
margin: 0.2rem 0 0;
color: var(--muted);
}
.page-kicker,
-.eyebrow,
-.fleet-sidebar-eyebrow,
-.fleet-results-eyebrow {
+.eyebrow {
margin: 0 0 0.18rem !important;
color: var(--primary) !important;
font-size: 0.66rem;
@@ -625,17 +617,6 @@ main > section > header p,
color: #fff;
background: var(--primary);
}
-.fleet-sidebar-result-count {
- margin: 0;
- padding: 0.45rem 0.6rem;
- color: var(--muted);
- border-radius: 8px;
- background: #f4f6f8;
- font-size: 0.76rem;
-}
-.fleet-sidebar-result-count strong {
- color: var(--ink);
-}
.fleet-active-filters {
display: flex;
gap: 0.35rem;
@@ -1722,10 +1703,6 @@ main ul[aria-label='已配置实例'] > li {
.auth-card input {
width: 100%;
}
-.auth-card .eyebrow {
- color: #8eabff !important;
-}
-
@media (max-width: 70rem) {
.fleet-workspace,
.fleet-workspace.is-sidebar-collapsed {
@@ -1775,7 +1752,6 @@ main ul[aria-label='已配置实例'] > li {
gap: 0.55rem;
padding: 0.55rem 0.75rem;
}
- .product-copy small,
.connection-status {
display: none;
}
@@ -1901,11 +1877,6 @@ body,
letter-spacing: 0;
}
-.product-copy small {
- color: var(--workbench-muted);
- font-size: 0.68rem;
-}
-
.global-navigation {
align-self: stretch;
gap: 0.15rem;
@@ -2079,10 +2050,6 @@ main {
border-bottom: 1px solid var(--workbench-border);
}
-.fleet-results-eyebrow {
- display: none;
-}
-
.fleet-card-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
@@ -2409,12 +2376,6 @@ main {
margin-bottom: 0.75rem;
font-size: 0.82rem;
}
-.drawer-eyebrow {
- color: var(--workbench-mint);
- font-size: 0.68rem;
- font-weight: 800;
- text-transform: uppercase;
-}
.icon-button {
width: 2.75rem;
height: 2.75rem;
@@ -2579,9 +2540,6 @@ main {
flex-wrap: wrap;
padding: 0.45rem 0.65rem;
}
- .product-copy small {
- display: none;
- }
.topbar-actions {
margin-left: auto;
}
@@ -2838,10 +2796,6 @@ h1 {
font-size: 1.04rem;
}
-.product-copy small {
- color: #75664f;
-}
-
.global-navigation {
gap: 0.3rem;
padding: 0.32rem;
@@ -2961,9 +2915,7 @@ main > section > header h1,
}
.page-kicker,
-.eyebrow,
-.fleet-sidebar-eyebrow,
-.fleet-results-eyebrow {
+.eyebrow {
color: #138f85 !important;
}
@@ -3115,10 +3067,6 @@ main > section > header h1,
border-bottom: 2px dashed #cabfa9;
}
-.fleet-results-eyebrow {
- color: #725d42 !important;
-}
-
.fleet-card-grid {
grid-template-columns: repeat(auto-fill, minmax(min(100%, 20rem), 1fr));
gap: 1rem;
@@ -3764,10 +3712,6 @@ main ul[aria-label='已配置实例'] > li > div[class*='animal-'] {
color: #725d42;
}
-.auth-card .eyebrow {
- color: #138f85 !important;
-}
-
.app-footer {
width: 100%;
margin-top: 1rem;
@@ -3894,7 +3838,6 @@ svg {
}
.fleet-sidebar-header strong,
.fleet-status-summary span,
-.fleet-results-eyebrow,
.fleet-card-kicker,
.fleet-card-metrics dt,
.fleet-card-sms h3,
@@ -3906,7 +3849,6 @@ svg {
}
.fleet-sidebar-header strong svg,
.fleet-status-summary span svg,
-.fleet-results-eyebrow svg,
.fleet-card-kicker svg,
.fleet-card-metrics dt svg,
.fleet-card-sms h3 svg,
@@ -4901,3 +4843,98 @@ svg {
overflow-x: auto;
overflow-y: hidden;
}
+
+/* Phone privacy controls share one stable row across all fleet card widths. */
+.fleet-card-hardware {
+ grid-template-columns: minmax(0, 1.55fr) minmax(5.25rem, 0.65fr);
+ gap: 0.35rem;
+ padding: 0.72rem 0.45rem;
+}
+
+.fleet-card-hardware dt,
+.fleet-card-hardware dd {
+ white-space: nowrap;
+}
+
+.fleet-card-hardware .fleet-card-phone {
+ gap: 0.15rem;
+}
+
+.fleet-card-hardware .fleet-card-phone dd {
+ display: flex;
+ min-height: 2.5rem;
+ align-items: center;
+ gap: 0.25rem;
+}
+
+.fleet-card-phone-value {
+ min-width: 0;
+ flex: 1 1 auto;
+ overflow: hidden;
+ font-size: 0.7rem;
+ font-variant-numeric: tabular-nums;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+}
+
+.fleet-phone-privacy-toggle,
+.fleet-phone-privacy-toggle:hover:not(:disabled),
+.fleet-phone-privacy-toggle:active:not(:disabled) {
+ position: relative;
+ display: grid;
+ width: 2.5rem;
+ height: 2.5rem;
+ min-height: 2.5rem;
+ flex: 0 0 2.5rem;
+ place-items: center;
+ padding: 0;
+ border: 1px solid var(--workbench-border);
+ border-radius: 8px;
+ color: var(--workbench-muted);
+ background: #fffef9;
+ box-shadow: none;
+ transform: none;
+}
+
+.fleet-phone-privacy-toggle::before {
+ position: absolute;
+ inset: -0.125rem;
+ content: '';
+}
+
+.fleet-phone-privacy-toggle:hover:not(:disabled) {
+ color: var(--workbench-text);
+ border-color: var(--workbench-border-strong);
+ background: #fff;
+}
+
+.fleet-phone-privacy-toggle[aria-pressed='true'] {
+ color: var(--workbench-mint);
+ border-color: rgba(17, 119, 110, 0.36);
+ background: #f1faf6;
+}
+
+.fleet-phone-privacy-toggle:focus-visible {
+ outline: 2px solid var(--workbench-mint);
+ outline-offset: 2px;
+}
+
+.fleet-phone-privacy-toggle svg {
+ width: 1rem;
+ height: 1rem;
+}
+
+.fleet-card-footer-primary > span:last-child {
+ font-variant-numeric: tabular-nums;
+}
+
+@media (min-width: 80rem) {
+ .fleet-card-footer {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr);
+ }
+
+ .fleet-card-footer-time {
+ justify-self: end;
+ }
+}
diff --git a/apps/web/src/ui/icon.tsx b/apps/web/src/ui/icon.tsx
index c61a781..92069a3 100644
--- a/apps/web/src/ui/icon.tsx
+++ b/apps/web/src/ui/icon.tsx
@@ -13,6 +13,8 @@ export type IconName =
| 'globe'
| 'grid'
| 'history'
+ | 'eye'
+ | 'eye-off'
| 'jobs'
| 'memory'
| 'message'
@@ -42,6 +44,16 @@ const paths: Readonly> = {
'chevron-right': ['m9 18 6-6-6-6'],
cpu: ['M9 9h6v6H9z', 'M4 9h2M4 15h2M18 9h2M18 15h2M9 4v2M15 4v2M9 18v2M15 18v2', 'M6 6h12v12H6z'],
filter: ['M4 5h16l-6 7v5l-4 2v-7Z'],
+ eye: [
+ 'M2.5 12s3.5-6 9.5-6 9.5 6 9.5 6-3.5 6-9.5 6S2.5 12 2.5 12Z',
+ 'M12 9a3 3 0 1 1 0 6 3 3 0 0 1 0-6Z',
+ ],
+ 'eye-off': [
+ 'M3 3l18 18',
+ 'M10.6 6.2A10.7 10.7 0 0 1 12 6c6 0 9.5 6 9.5 6a15 15 0 0 1-2.1 2.8',
+ 'M6.1 6.1C3.7 7.8 2.5 12 2.5 12s3.5 6 9.5 6a10.7 10.7 0 0 0 3-.4',
+ 'M9.9 9.9a3 3 0 0 0 4.2 4.2',
+ ],
globe: [
'M12 21a9 9 0 1 0 0-18 9 9 0 0 0 0 18Z',
'M3 12h18',
diff --git a/scripts/real-browser-e2e.mjs b/scripts/real-browser-e2e.mjs
index 7680e6d..94ef861 100644
--- a/scripts/real-browser-e2e.mjs
+++ b/scripts/real-browser-e2e.mjs
@@ -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,