refine Semi UI access workspace

This commit is contained in:
lingniu
2026-07-18 10:17:05 +08:00
parent c1c35f8962
commit 41b5c41998
4 changed files with 287 additions and 113 deletions

View File

@@ -52,9 +52,11 @@ test('removes old access rows immediately when the vehicle filter scope changes'
expect(await screen.findByText('旧接入车牌')).toBeInTheDocument();
expect(screen.getByRole('heading', { name: '真实来源与接入健康', level: 5 })).toBeInTheDocument();
expect(screen.getByLabelText('接入管理操作')).toHaveClass('v2-workspace-command-bar', 'v2-access-command-bar');
for (const className of ['v2-access-filter-card-v3', 'v2-access-kpis-card-v3', 'v2-access-table-v3']) {
expect(view.container.querySelector('.v2-access-discovery-shell')).toBeInTheDocument();
for (const className of ['v2-access-filter-card-v3', 'v2-access-table-v3']) {
expect(view.container.querySelector(`.${className}.semi-card`)).toBeInTheDocument();
}
expect(screen.getByRole('navigation', { name: '接入差异筛选' })).toHaveClass('v2-access-status-tabs');
const filterActions = view.container.querySelector<HTMLElement>('.v2-access-filter-actions');
expect(filterActions).toBeInTheDocument();
expect(within(filterActions!).getByRole('button', { name: '查询' })).toBeInTheDocument();
@@ -66,6 +68,7 @@ test('removes old access rows immediately when the vehicle filter scope changes'
expect(desktopRow).toHaveAttribute('aria-expanded', 'false');
fireEvent.keyDown(desktopRow, { key: 'Enter' });
expect(await screen.findByRole('button', { name: '关闭车辆接入详情' })).toBeInTheDocument();
expect(screen.getByRole('dialog', { name: '车辆接入详情' })).toBeInTheDocument();
expect(desktopRow).toHaveAttribute('aria-expanded', 'true');
const inspectorHeader = screen.getByRole('heading', { level: 5, name: '旧接入车牌' }).closest<HTMLElement>('.v2-workspace-panel-header');
expect(inspectorHeader).toBeInTheDocument();
@@ -143,9 +146,9 @@ test('renders mobile access vehicles as selectable Semi cards', async () => {
expect(action.closest('.semi-card')).toHaveClass('v2-access-mobile-card');
expect(view.container.querySelector('.v2-access-table-scroll-v3.is-mobile-scroll')).toHaveAttribute('tabindex', '0');
expect(view.container.querySelector('.v2-access-table-scroll-v3.is-mobile-scroll')).toHaveAttribute('aria-label', '车辆接入列表,可上下滚动');
expect(within(action).getByText('GB32960')).toBeInTheDocument();
expect(within(action).getByText('JT808')).toBeInTheDocument();
expect(within(action).getByText('YUTONG_MQTT')).toBeInTheDocument();
expect(within(action).getByText('32960')).toBeInTheDocument();
expect(within(action).getByText('808')).toBeInTheDocument();
expect(within(action).getByText('宇通')).toBeInTheDocument();
expect(action).toHaveAttribute('aria-pressed', 'false');
expect(action).toHaveAttribute('aria-expanded', 'false');
fireEvent.click(action);

View File

@@ -29,10 +29,27 @@ const connectionLabels: Record<AccessVehicleRow['connectionState'], string> = {
healthy: '已接来源正常', degraded: '部分来源异常', incomplete: '资料待维护', offline: '已接来源离线', not_connected: '尚无来源'
};
const scopeLabels: Record<string, string> = {
'': '全部主车辆',
attention: '需关注车辆',
master_data: '资料待维护',
healthy: '已接来源正常',
degraded: '部分来源异常',
offline: '已接来源离线',
not_connected: '尚无来源'
};
function statusByProtocol(row: AccessVehicleRow, protocol: string) {
return row.protocolStatuses.find((item) => item.protocol === protocol);
}
function compactProtocolLabel(protocol: string) {
if (protocol === 'GB32960') return '32960';
if (protocol === 'JT808') return '808';
if (protocol === 'YUTONG_MQTT') return '宇通';
return protocol;
}
function compactTime(value: string) {
if (!value) return '—';
const parsed = new Date(value);
@@ -193,7 +210,7 @@ export default function AccessPage() {
setOffset(0);
}, [mobileLayout]);
const rows = vehiclesQuery.data?.items ?? []; const selected = rows.find((row) => row.vin === selectedVIN);
useSideSheetA11y(mobileLayout && Boolean(selected), '.v2-access-detail-sidesheet', 'v2-access-detail', '车辆接入详情', '关闭车辆接入详情');
useSideSheetA11y(Boolean(selected), '.v2-access-detail-sidesheet', 'v2-access-detail', '车辆接入详情', '关闭车辆接入详情');
useSideSheetA11y(editable && governanceOpen, '.v2-access-governance-sidesheet', 'v2-access-governance', '接入治理配置', '关闭接入治理配置');
const syncURL = (filters: Filters) => { const next = new URLSearchParams(); Object.entries(filters).forEach(([key, value]) => { if (value) next.set(key, value); }); setSearchParams(next, { replace: true }); };
const apply = (next: Filters) => { setDraft(next); setCriteria(next); setOffset(0); setSelectedVIN(''); syncURL(next); };
@@ -202,78 +219,80 @@ export default function AccessPage() {
const refresh = () => Promise.all([summaryQuery.refetch(), vehiclesQuery.refetch(), ...(editable ? [unresolvedQuery.refetch(), thresholdQuery.refetch()] : [])]);
return <div className="v2-access-page v2-access-page-v3">
<WorkspaceCommandBar
className="v2-access-command-bar"
ariaLabel="接入管理操作"
title="真实来源与接入健康"
description="核对车辆真实存在的数据来源、在线健康和待维护资料,不对尚未接入的业务协议作推断。"
status={summary ? `${summary.totalVehicles.toLocaleString('zh-CN')} 辆主车辆` : '正在读取车辆'}
meta={<Typography.Text type="tertiary"> {summary?.asOf ? formatAccessTime(summary.asOf) : '—'}</Typography.Text>}
actions={<>{editable ? <Button theme="light" icon={<IconSetting />} aria-haspopup="dialog" aria-controls="v2-access-governance" aria-expanded={governanceOpen} onClick={() => setGovernanceOpen(true)}>{unresolvedQuery.data?.total ? ` · ${unresolvedQuery.data.total}` : ''}</Button> : null}<Button theme="light" icon={<IconRefresh />} onClick={() => void refresh()}></Button></>}
/>
<WorkspaceFilterPanel
className="v2-access-filter-card-v3"
title="接入筛选"
description="车辆、接入状态、真实协议与品牌"
mobileSummary={Object.values(criteria).filter(Boolean).length ? `已启用 ${Object.values(criteria).filter(Boolean).length}` : '全部主车辆'}
expanded={!filtersCollapsed}
status={Object.values(criteria).filter(Boolean).length ? `${Object.values(criteria).filter(Boolean).length} 项已启用` : '全部主车辆'}
statusColor={Object.values(criteria).filter(Boolean).length ? 'blue' : 'grey'}
onToggle={() => setFiltersCollapsed((value) => !value)}
>
<form className="v2-access-filter-v3" onSubmit={submit}>
<label className="is-search"><span></span><Input aria-label="车辆" prefix={<IconSearch />} value={draft.keyword} onChange={(value) => setDraft({ ...draft, keyword: value })} placeholder="车牌 / VIN" /></label>
<label><span id="access-connection-label"></span><Select aria-labelledby="access-connection-label" value={draft.connectionState} onChange={(value) => setDraft({ ...draft, connectionState: String(value) })} optionList={[{ value: '', label: '全部状态' }, { value: 'attention', label: '需关注' }, { value: 'healthy', label: '已接来源正常' }, { value: 'master_data', label: '资料待维护' }, { value: 'degraded', label: '部分来源异常' }, { value: 'offline', label: '已接来源离线' }, { value: 'not_connected', label: '尚无来源' }]} /></label>
<label><span id="access-protocol-label"></span><Select aria-labelledby="access-protocol-label" value={draft.protocol} onChange={(value) => setDraft({ ...draft, protocol: String(value) })} optionList={[{ value: '', label: '全部协议' }, ...PROTOCOLS.map((item) => ({ value: item, label: item }))]} /></label>
<label><span id="access-oem-label"></span><Select aria-labelledby="access-oem-label" value={draft.oem} onChange={(value) => setDraft({ ...draft, oem: String(value) })} optionList={[{ value: '', label: '全部品牌' }, ...(summary?.oems.map((item) => ({ value: item.name, label: item.name })) ?? [])]} /></label>
<div className="v2-access-filter-actions">
<Button className="v2-primary-button" theme="solid" htmlType="submit"></Button>
<Button className="v2-secondary-button" theme="light" htmlType="button" onClick={() => apply(EMPTY_FILTERS)}></Button>
</div>
</form></WorkspaceFilterPanel>
<div className="v2-access-discovery-shell">
<WorkspaceCommandBar
className="v2-access-command-bar"
ariaLabel="接入管理操作"
title="真实来源与接入健康"
description="真实来源、在线健康与资料差异一处核对"
status={summary ? `${summary.totalVehicles.toLocaleString('zh-CN')} 辆主车辆` : '正在读取车辆'}
meta={<Typography.Text type="tertiary"> {summary?.asOf ? formatAccessTime(summary.asOf) : ''}</Typography.Text>}
actions={<>{editable ? <Button theme="light" icon={<IconSetting />} aria-haspopup="dialog" aria-controls="v2-access-governance" aria-expanded={governanceOpen} onClick={() => setGovernanceOpen(true)}>{unresolvedQuery.data?.total ? ` · ${unresolvedQuery.data.total}` : ''}</Button> : null}<Button theme="light" icon={<IconRefresh />} onClick={() => void refresh()}></Button></>}
/>
<WorkspaceFilterPanel
className="v2-access-filter-card-v3"
title="接入筛选"
description="车辆、接入状态、真实协议与品牌"
mobileSummary={Object.values(criteria).filter(Boolean).length ? `已启用 ${Object.values(criteria).filter(Boolean).length}` : '全部主车辆'}
expanded={!filtersCollapsed}
status={Object.values(criteria).filter(Boolean).length ? `${Object.values(criteria).filter(Boolean).length} 项已启用` : '全部主车辆'}
statusColor={Object.values(criteria).filter(Boolean).length ? 'blue' : 'grey'}
onToggle={() => setFiltersCollapsed((value) => !value)}
>
<form className="v2-access-filter-v3" onSubmit={submit}>
<label className="is-search"><span></span><Input aria-label="车辆" prefix={<IconSearch />} value={draft.keyword} onChange={(value) => setDraft({ ...draft, keyword: value })} placeholder="车牌 / VIN" /></label>
<label><span id="access-connection-label"></span><Select aria-labelledby="access-connection-label" value={draft.connectionState} onChange={(value) => setDraft({ ...draft, connectionState: String(value) })} optionList={[{ value: '', label: '全部状态' }, { value: 'attention', label: '需关注' }, { value: 'healthy', label: '已接来源正常' }, { value: 'master_data', label: '资料待维护' }, { value: 'degraded', label: '部分来源异常' }, { value: 'offline', label: '已接来源离线' }, { value: 'not_connected', label: '尚无来源' }]} /></label>
<label><span id="access-protocol-label"></span><Select aria-labelledby="access-protocol-label" value={draft.protocol} onChange={(value) => setDraft({ ...draft, protocol: String(value) })} optionList={[{ value: '', label: '全部协议' }, ...PROTOCOLS.map((item) => ({ value: item, label: item }))]} /></label>
<label><span id="access-oem-label"></span><Select aria-labelledby="access-oem-label" value={draft.oem} onChange={(value) => setDraft({ ...draft, oem: String(value) })} optionList={[{ value: '', label: '全部品牌' }, ...(summary?.oems.map((item) => ({ value: item.name, label: item.name })) ?? [])]} /></label>
<div className="v2-access-filter-actions">
<Button className="v2-primary-button" theme="solid" htmlType="submit"></Button>
<Button className="v2-secondary-button" theme="light" htmlType="button" onClick={() => apply(EMPTY_FILTERS)}></Button>
</div>
</form>
</WorkspaceFilterPanel>
</div>
{summaryQuery.isError ? <InlineError message={summaryQuery.error instanceof Error ? summaryQuery.error.message : '接入汇总读取失败'} onRetry={() => summaryQuery.refetch()} /> : null}
<Card className="v2-access-kpis-card-v3" bodyStyle={{ padding: 0 }}><section className="v2-access-kpis-v3">{[
{ label: '主车辆', value: summary?.totalVehicles ?? 0, tone: 'all', connectionState: '', hint: '车辆主档' },
{ label: '需关注', value: Math.max(0, (summary?.totalVehicles ?? 0) - (summary?.healthyVehicles ?? 0)), tone: 'attention', connectionState: 'attention' },
{ label: '资料待维护', value: summary?.masterDataIncompleteVehicles ?? 0, tone: 'incomplete', connectionState: 'master_data' },
{ label: '尚无来源', value: summary?.neverReported ?? 0, tone: 'never', connectionState: 'not_connected' }
].map((item) => {
const value = Number(item.value).toLocaleString('zh-CN');
return <MetricActionButton key={item.label} label={item.label} value={value} hint={item.hint} tone={item.tone} active={criteria.connectionState === item.connectionState} ariaLabel={`筛选${item.label},共 ${value}`} onClick={() => apply({ ...criteria, connectionState: item.connectionState })} />;
})}</section></Card>
{vehiclesQuery.isError ? <InlineError message={vehiclesQuery.error instanceof Error ? vehiclesQuery.error.message : '接入车辆读取失败'} onRetry={() => vehiclesQuery.refetch()} /> : null}
<div className={`v2-access-workspace-v3 ${selected && !mobileLayout ? 'is-inspector-open' : ''}`}>
<div className="v2-access-workspace-v3">
<Card className="v2-access-table-v3" bodyStyle={{ padding: 0 }}>
<WorkspacePanelHeader
title="车辆真实接入来源"
description="缺席协议表示当前未发现”,不会被推断成应接缺失;时间为各来源最后接收时间"
title={scopeLabels[criteria.connectionState] || '车辆接入清单'}
description="缺席协议表示当前未发现;优先核对异常、离线与资料差异"
actionsClassName="v2-access-table-actions"
actions={<><ProtocolCoverage summary={summary} /><Button theme="light" icon={<IconDownload />} onClick={() => downloadRows(rows)} disabled={!rows.length}></Button></>}
/>
<nav className="v2-access-status-tabs" aria-label="接入差异筛选">{[
{ label: '主车辆', value: summary?.totalVehicles ?? 0, tone: 'all', connectionState: '', hint: '全部' },
{ label: '需关注', value: Math.max(0, (summary?.totalVehicles ?? 0) - (summary?.healthyVehicles ?? 0)), tone: 'attention', connectionState: 'attention' },
{ label: '资料待维护', value: summary?.masterDataIncompleteVehicles ?? 0, tone: 'incomplete', connectionState: 'master_data' },
{ label: '尚无来源', value: summary?.neverReported ?? 0, tone: 'never', connectionState: 'not_connected' }
].map((item) => {
const value = Number(item.value).toLocaleString('zh-CN');
return <MetricActionButton key={item.label} label={item.label} value={value} hint={item.hint} tone={item.tone} active={criteria.connectionState === item.connectionState} ariaLabel={`筛选${item.label},共 ${value}`} onClick={() => apply({ ...criteria, connectionState: item.connectionState })} />;
})}</nav>
<div
className={`v2-access-table-scroll-v3${mobileLayout ? ' is-mobile-scroll' : ''}`}
tabIndex={mobileLayout ? 0 : undefined}
aria-label={mobileLayout ? '车辆接入列表,可上下滚动' : undefined}
>
{mobileLayout
? <div className="v2-access-mobile-list">{rows.map((row) => <Card key={row.vin} className={`v2-access-mobile-card${selected?.vin === row.vin ? ' is-selected' : ''}`} bodyStyle={{ padding: 0 }}><Button theme="borderless" type="tertiary" aria-pressed={selected?.vin === row.vin} aria-expanded={selected?.vin === row.vin} aria-label={`查看 ${row.plate || row.vin} 接入详情`} className="v2-access-mobile-action" onClick={() => setSelectedVIN(row.vin)}><span className="v2-access-mobile-card-content"><header><span><strong>{row.plate || '未绑定车牌'}</strong><small>{row.vin}</small></span><ConnectionState row={row} /></header><p>{row.oem || '品牌未维护'} · {row.model || row.company || '车型未维护'}</p><span className="v2-access-mobile-protocols">{PROTOCOLS.map((protocol) => <ProtocolState key={protocol} protocolLabel={protocol} status={statusByProtocol(row, protocol)} />)}</span><footer><IconChevronRight /></footer></span></Button></Card>)}</div>
? <div className="v2-access-mobile-list">{rows.map((row) => <Card key={row.vin} className={`v2-access-mobile-card${selected?.vin === row.vin ? ' is-selected' : ''}`} bodyStyle={{ padding: 0 }}><Button theme="borderless" type="tertiary" aria-pressed={selected?.vin === row.vin} aria-expanded={selected?.vin === row.vin} aria-label={`查看 ${row.plate || row.vin} 接入详情`} className="v2-access-mobile-action" onClick={() => setSelectedVIN(row.vin)}><span className="v2-access-mobile-card-content"><header><span><strong>{row.plate || '未绑定车牌'}</strong><small>{row.vin}</small></span><ConnectionState row={row} /></header><p>{row.oem || '品牌未维护'} · {row.model || row.company || '车型未维护'}</p><span className="v2-access-mobile-protocols">{PROTOCOLS.map((protocol) => <ProtocolState key={protocol} protocolLabel={compactProtocolLabel(protocol)} status={statusByProtocol(row, protocol)} />)}</span><footer><IconChevronRight /></footer></span></Button></Card>)}</div>
: <AccessVehicleTable rows={rows} selectedVIN={selectedVIN} onSelect={setSelectedVIN} />}
{vehiclesQuery.isFetching ? <PanelLoading className="v2-access-loading" title="正在更新车辆接入状态…" description="当前列表返回后会自动替换。" compact={Boolean(rows.length)} /> : null}
{!vehiclesQuery.isFetching && !rows.length ? <PanelEmpty className="v2-access-empty" title="没有匹配车辆" description="调整车牌、协议或接入状态筛选后重试。" /> : null}
</div>
<footer><TablePagination page={page} totalPages={totalPages} info={`${(vehiclesQuery.data?.total ?? 0).toLocaleString('zh-CN')} 辆主车辆`} onPageChange={(next) => setOffset((next - 1) * limit)} pageSize={limit} pageSizeLabel="每页车辆数" onPageSizeChange={(next) => { setLimit(next); setOffset(0); }} pageSizeOptions={[{ value: 20, label: '20 辆/页' }, { value: 50, label: '50 辆/页' }, { value: 100, label: '100 辆/页' }]} /></footer>
</Card>
{!mobileLayout && selected ? <VehicleInspector row={selected} onClose={() => setSelectedVIN('')} /> : null}
</div>
<SideSheet
className="v2-access-detail-sidesheet"
visible={mobileLayout && Boolean(selected)}
visible={Boolean(selected)}
aria-label="车辆接入详情"
width="100%"
width={mobileLayout ? '100%' : 520}
title={<div className="v2-access-sheet-title"><strong></strong><span>{selected ? `${selected.plate || '未绑定车牌'} · ${selected.vin}` : '来源、在线状态与接入证据'}</span></div>}
onCancel={() => setSelectedVIN('')}
>
{mobileLayout && selected ? <VehicleInspector row={selected} onClose={() => setSelectedVIN('')} sheet /> : null}
{selected ? <VehicleInspector row={selected} onClose={() => setSelectedVIN('')} sheet /> : null}
</SideSheet>
{editable ? <SideSheet
className="v2-access-governance-sidesheet"

View File

@@ -197,9 +197,12 @@ describe('V2 production entry', () => {
expect(corePageSources.AccessPage).toContain('<Input');
expect(corePageSources.AccessPage).toContain('<Select');
expect(corePageSources.AccessPage).toContain('<Card');
for (const surface of ['v2-access-kpis-card-v3', 'v2-access-table-v3']) {
for (const surface of ['v2-access-table-v3']) {
expect(corePageSources.AccessPage).toContain(`<Card className="${surface}"`);
}
expect(corePageSources.AccessPage).toContain('className="v2-access-discovery-shell"');
expect(corePageSources.AccessPage).toContain('className="v2-access-status-tabs"');
expect(corePageSources.AccessPage).not.toContain('className="v2-access-kpis-card-v3"');
expect(corePageSources.AccessPage).toContain('<Card className={`v2-access-inspector-v3');
expect(corePageSources.AccessPage).toContain('className="v2-access-detail-sidesheet"');
expect(corePageSources.AccessPage).toContain('className="v2-access-governance-sidesheet"');

View File

@@ -8300,12 +8300,39 @@
width: 100%;
height: 100%;
min-height: 0;
gap: 8px;
gap: 10px;
overflow: hidden;
padding: 12px 14px 14px;
padding: 14px 16px 16px;
}
.v2-access-discovery-shell {
display: grid;
flex: 0 0 auto;
overflow: visible;
gap: 0;
border: 1px solid #dce4ee;
border-radius: 14px;
background: #fff;
box-shadow: 0 9px 28px rgba(31, 53, 80, .055);
}
.v2-access-discovery-shell > .v2-workspace-command-bar {
width: 100%;
min-height: 50px;
border: 0;
border-bottom: 1px solid #e6ecf3;
border-radius: 14px 14px 0 0;
box-shadow: none;
}
.v2-access-discovery-shell > .v2-access-filter-card-v3.semi-card {
width: 100%;
overflow: visible;
border: 0;
border-radius: 0 0 14px 14px;
box-shadow: none;
}
.v2-access-page-v3 > .v2-workspace-command-bar,
.v2-ops-command-bar {
max-width: none;
}
@@ -8362,20 +8389,6 @@
box-shadow: 0 7px 16px rgba(18, 104, 243, .15);
}
.v2-access-kpis-card-v3.semi-card,
.v2-access-kpis-v3 {
min-height: 60px;
}
.v2-access-kpis-v3 .v2-metric-action {
min-height: 58px;
padding: 8px 14px;
}
.v2-access-kpis-v3 strong {
font-size: 20px;
}
.v2-access-workspace-v3 {
min-height: 0;
flex: 1 1 0;
@@ -8387,6 +8400,96 @@
min-height: 0;
}
.v2-access-status-tabs {
display: grid;
min-height: 48px;
flex: 0 0 auto;
grid-template-columns: repeat(4, minmax(0, 1fr));
overflow: hidden;
border-bottom: 1px solid #e4eaf2;
background: #fbfcfe;
}
.v2-access-status-tabs .v2-metric-action.semi-button {
position: relative;
width: 100%;
min-width: 0;
height: 48px;
border: 0;
border-radius: 0;
background: transparent;
padding: 0 14px;
color: #66778d;
}
.v2-access-status-tabs .v2-metric-action + .v2-metric-action::before {
position: absolute;
inset: 10px auto 10px 0;
width: 1px;
background: #e1e7ef;
content: '';
}
.v2-access-status-tabs .v2-metric-action:hover {
background: #f4f8fe;
}
.v2-access-status-tabs .v2-metric-action.is-active {
z-index: 1;
background: #eef5ff;
color: var(--v2-blue);
box-shadow: inset 0 -3px var(--v2-blue);
}
.v2-access-status-tabs .semi-button-content,
.v2-access-status-tabs .v2-metric-action-content {
display: flex;
width: 100%;
min-width: 0;
align-items: center;
}
.v2-access-status-tabs .v2-metric-action-content {
gap: 8px;
}
.v2-access-status-tabs small {
overflow: hidden;
color: inherit;
font-size: 11px;
font-weight: 650;
text-overflow: ellipsis;
white-space: nowrap;
}
.v2-access-status-tabs strong {
margin-left: auto;
color: #304157;
font-size: 16px;
line-height: 1;
font-variant-numeric: tabular-nums;
}
.v2-access-status-tabs .is-attention strong,
.v2-access-status-tabs .is-incomplete strong {
color: #c46b0d;
}
.v2-access-status-tabs .is-never strong {
color: #67778b;
}
.v2-access-status-tabs em {
color: #8b98aa;
font-size: 9px;
font-style: normal;
}
.v2-access-table-v3 .semi-table-tbody > .semi-table-row > .semi-table-row-cell {
height: 58px;
padding-block: 6px;
}
.v2-ops-page {
width: 100%;
height: 100%;
@@ -8427,6 +8530,23 @@
padding: 7px 7px 8px;
}
.v2-access-discovery-shell {
display: contents;
}
.v2-access-discovery-shell > .v2-workspace-command-bar {
min-height: 48px;
border: 1px solid #dce6f2;
border-radius: 10px;
background: linear-gradient(105deg, #fff 0%, #f8fbff 100%);
}
.v2-access-discovery-shell > .v2-access-filter-card-v3.semi-card {
border: 1px solid #dce6f2;
border-radius: 10px;
box-shadow: var(--v2-shadow);
}
.v2-access-filter-card-v3 > .semi-card-body > .v2-workspace-filter-heading {
display: flex;
}
@@ -8457,42 +8577,6 @@
flex: 1 1 0;
}
.v2-access-kpis-card-v3.semi-card,
.v2-access-kpis-v3 {
min-height: 62px;
}
.v2-access-kpis-v3 {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.v2-access-kpis-v3 .v2-metric-action {
min-height: 62px;
padding: 7px 6px;
}
.v2-access-kpis-v3 .v2-metric-action-content {
display: grid;
align-content: center;
gap: 4px;
}
.v2-access-kpis-v3 small {
overflow: hidden;
font-size: 8px;
text-overflow: ellipsis;
white-space: nowrap;
}
.v2-access-kpis-v3 strong {
margin-left: 0;
font-size: 16px;
}
.v2-access-kpis-v3 em {
display: none;
}
.v2-access-workspace-v3 {
min-height: 0;
flex: 1 1 0;
@@ -8502,10 +8586,59 @@
.v2-access-table-v3 > .semi-card-body > .v2-workspace-panel-header {
min-height: 50px;
align-items: center;
flex-direction: row;
padding: 6px 8px;
}
.v2-access-table-v3 .v2-workspace-panel-copy > .semi-typography {
.v2-access-table-v3 .v2-workspace-panel-copy {
width: auto;
flex: 1 1 auto;
}
.v2-access-table-v3 .v2-workspace-panel-copy > .semi-typography:not(h5) {
display: none;
}
.v2-access-table-v3 .v2-workspace-panel-copy > h5.semi-typography {
display: block;
font-size: 12px;
}
.v2-access-table-v3 .v2-access-table-actions {
width: auto;
max-width: none;
flex: 0 0 auto;
flex-wrap: nowrap;
}
.v2-access-status-tabs {
min-height: 44px;
}
.v2-access-status-tabs .v2-metric-action.semi-button {
height: 44px;
padding: 0 6px;
}
.v2-access-status-tabs .v2-metric-action + .v2-metric-action::before {
inset-block: 9px;
}
.v2-access-status-tabs .v2-metric-action-content {
justify-content: center;
gap: 4px;
}
.v2-access-status-tabs small {
font-size: 9px;
}
.v2-access-status-tabs strong {
margin-left: 0;
font-size: 13px;
}
.v2-access-status-tabs em {
display: none;
}
@@ -8557,11 +8690,11 @@
}
.v2-access-mobile-card-content > header strong {
font-size: 13px;
font-size: 14px;
}
.v2-access-mobile-card-content > header small {
font-size: 8px;
font-size: 9px;
}
.v2-access-mobile-card-content > header .v2-access-connection {
@@ -8581,8 +8714,8 @@
.v2-access-mobile-card-content > p {
overflow: hidden;
font-size: 9px;
line-height: 12px;
font-size: 10px;
line-height: 13px;
text-overflow: ellipsis;
white-space: nowrap;
}
@@ -8603,7 +8736,7 @@
min-width: 0;
overflow: hidden;
color: #50627a;
font-size: 8px;
font-size: 9px;
line-height: 11px;
text-overflow: ellipsis;
white-space: nowrap;
@@ -8612,7 +8745,7 @@
.v2-access-mobile-list .v2-access-protocol-cell > span {
justify-self: end;
gap: 3px;
font-size: 8px;
font-size: 9px;
line-height: 11px;
}
@@ -8623,7 +8756,7 @@
.v2-access-mobile-list .v2-access-protocol-cell > strong {
grid-column: 1 / -1;
font-size: 8px;
font-size: 9px;
line-height: 11px;
}
@@ -8648,6 +8781,22 @@
height: 34px;
}
.v2-access-detail-sidesheet .v2-access-protocol-details.semi-card-group {
display: grid;
grid-template-columns: minmax(0, 1fr);
overflow-x: hidden;
gap: 8px;
padding: 10px;
scroll-snap-type: none;
}
.v2-access-detail-sidesheet .v2-access-protocol-detail.semi-card {
width: 100%;
min-width: 0;
flex: none;
scroll-snap-align: none;
}
.v2-ops-page {
height: auto;
min-height: 100%;