feat(usage): improve usage records layout and scrolling

- streamline the page header and status toolbar
- move the horizontal scrollbar above the table
- add page size selection and record range details
- improve column visibility controls and responsive styles
- adjust the Vite chunk size warning limit
This commit is contained in:
lzt404 committed 2026-08-26 01:56:46 +08:00
1 parent f83ba83d9d
commit cbd2ae76af
6 files changed
+404 -171

No files matched your search

+2
View File
@@ -199,6 +199,8 @@ export const jaOverrides = {
'usage.events.columnsSelected': '{total} 列中 {selected} 列を選択',
'usage.events.selectAllColumns': 'すべて選択',
'usage.events.applyColumns': '適用',
'usage.events.pageSize': '{size} 件/ページ',
'usage.events.rangeSummary': '{total} 件中 {start} - {end} 件を表示',
'usage.column.time': '日時',
'usage.column.model': 'モデル',
'usage.column.provider': 'Provider',
+2
View File
@@ -199,6 +199,8 @@ export const en: Record<MessageKey, string> = {
'usage.events.columnsSelected': '{selected} of {total} columns selected',
'usage.events.selectAllColumns': 'Select All',
'usage.events.applyColumns': 'Apply',
'usage.events.pageSize': '{size} / page',
'usage.events.rangeSummary': 'Showing {start} - {end} of {total}',
'usage.column.time': 'Time',
'usage.column.model': 'Model',
'usage.column.provider': 'Provider',
+4 -2
View File
@@ -192,11 +192,13 @@ export const zhCN = {
'usage.events.resetColumns': '重置列宽',
'usage.events.resizeHint': '拖动调整列宽,双击重置本列',
'usage.events.dragHint': '可拖动表头边线调整每列宽度',
'usage.events.columnSettings': '列设置',
'usage.events.columnSettingsDescription': '选择要在请求明细中显示的列。',
'usage.events.columnSettings': '显示列',
'usage.events.columnSettingsDescription': '选择请求明细中需要显示的列。',
'usage.events.columnsSelected': '已选择 {selected} / {total} 列',
'usage.events.selectAllColumns': '选择全部',
'usage.events.applyColumns': '应用',
'usage.events.pageSize': '{size} 条/页',
'usage.events.rangeSummary': '显示第 {start} - {end} 条,共 {total} 条',
'usage.column.time': '时间',
'usage.column.model': '模型',
'usage.column.provider': 'Provider',
+232 -92
View File
@@ -4,6 +4,8 @@ import { listen } from '@tauri-apps/api/event';
import {
Activity,
BarChart3,
ChevronLeft,
ChevronRight,
CircleDollarSign,
Clock3,
Columns3Cog,
@@ -270,6 +272,7 @@ export function UsageRecordsPage() {
const [apiKeyHash, setApiKeyHash] = useState('');
const [result, setResult] = useState('all');
const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState(50);
const [status, setStatus] = useState<CollectorStatus | null>(null);
const [overview, setOverview] = useState<UsageOverview | null>(null);
const [analysis, setAnalysis] = useState<UsageAnalysis>(emptyAnalysis);
@@ -347,7 +350,7 @@ export function UsageRecordsPage() {
statusRequest,
optionsRequest,
invoke<UsageEventPage>('get_usage_events', {
query: { ...query, page, page_size: 50 },
query: { ...query, page, page_size: pageSize },
}),
]);
if (requestId !== requestIdRef.current) return;
@@ -372,7 +375,7 @@ export function UsageRecordsPage() {
if (requestId === requestIdRef.current) setLoading(false);
}
},
[activeTab, buildQueries, page]
[activeTab, buildQueries, page, pageSize]
);
useEffect(() => {
@@ -437,24 +440,55 @@ export function UsageRecordsPage() {
return (
<section className="page management-page usage-records-page">
<header className="management-header usage-records-header">
<div className="usage-header-left">
<span className="usage-header-tag">Local Usage</span>
<h1>{t('usage.title')}</h1>
{error ? <div className="management-alert error">{error}</div> : null}
<div className="usage-topbar">
<div className="usage-tabs" role="tablist" aria-label={t('usage.pageLabel')}>
<button
type="button"
className={activeTab === 'overview' ? 'active' : ''}
onClick={() => setActiveTab('overview')}
>
<BarChart3 size={15} />
<span>{t('usage.tab.overview')}</span>
</button>
<button
type="button"
className={activeTab === 'analysis' ? 'active' : ''}
onClick={() => setActiveTab('analysis')}
>
<Activity size={15} />
<span>{t('usage.tab.analysis')}</span>
</button>
<button
type="button"
className={activeTab === 'events' ? 'active' : ''}
onClick={() => setActiveTab('events')}
>
<List size={15} />
<span>{t('usage.tab.events')}</span>
</button>
<button
type="button"
className={activeTab === 'pricing' ? 'active' : ''}
onClick={() => setActiveTab('pricing')}
>
<CircleDollarSign size={15} />
<span>{t('usage.tab.pricing')}</span>
</button>
</div>
<div className="usage-header-right">
<div className="usage-topbar-actions">
<div className={`usage-collector-state ${collectorTone}`} title={status?.message}>
<span className="status-dot" />
<div>
<strong>
{status?.state === 'collecting'
? t('usage.collector.collecting')
: status?.state === 'error'
? t('usage.collector.error')
: t('usage.collector.waiting')}
</strong>
<span>{t('usage.longTermRecords', { count: compactNumber(status?.totalRecords ?? 0) })}</span>
</div>
<strong>
{status?.state === 'collecting'
? t('usage.collector.collecting')
: status?.state === 'error'
? t('usage.collector.error')
: t('usage.collector.waiting')}
</strong>
<span>{t('usage.longTermRecords', { count: compactNumber(status?.totalRecords ?? 0) })}</span>
</div>
<button
type="button"
@@ -467,43 +501,6 @@ export function UsageRecordsPage() {
<RefreshCw size={15} className={loading ? 'spin' : ''} />
</button>
</div>
</header>
{error ? <div className="management-alert error">{error}</div> : null}
<div className="usage-tabs" role="tablist" aria-label={t('usage.pageLabel')}>
<button
type="button"
className={activeTab === 'overview' ? 'active' : ''}
onClick={() => setActiveTab('overview')}
>
<BarChart3 size={15} />
<span>{t('usage.tab.overview')}</span>
</button>
<button
type="button"
className={activeTab === 'analysis' ? 'active' : ''}
onClick={() => setActiveTab('analysis')}
>
<Activity size={15} />
<span>{t('usage.tab.analysis')}</span>
</button>
<button
type="button"
className={activeTab === 'events' ? 'active' : ''}
onClick={() => setActiveTab('events')}
>
<List size={15} />
<span>{t('usage.tab.events')}</span>
</button>
<button
type="button"
className={activeTab === 'pricing' ? 'active' : ''}
onClick={() => setActiveTab('pricing')}
>
<CircleDollarSign size={15} />
<span>{t('usage.tab.pricing')}</span>
</button>
</div>
<section className="panel usage-filter-panel">
@@ -667,7 +664,17 @@ export function UsageRecordsPage() {
{activeTab === 'overview' && overview ? <OverviewView overview={overview} /> : null}
{activeTab === 'analysis' ? <AnalysisView analysis={analysis} overview={overview} /> : null}
{activeTab === 'events' && events ? <EventsView events={events} onPage={setPage} /> : null}
{activeTab === 'events' && events ? (
<EventsView
events={events}
pageSize={pageSize}
onPage={setPage}
onPageSizeChange={(size) => {
setPageSize(size);
setPage(1);
}}
/>
) : null}
{activeTab === 'pricing' && pricing ? (
<PricingView pricing={pricing} query={buildQueries().query} onChanged={() => loadData(true)} />
) : null}
@@ -1066,6 +1073,66 @@ const getInitialColumnWidths = (): Record<EventColumnKey, number> => {
return initial;
};
function TableTopScrollbar({
tableWrapRef,
totalWidth,
}: {
tableWrapRef: React.RefObject<HTMLDivElement | null>;
totalWidth: number;
}) {
const scrollbarRef = useRef<HTMLDivElement | null>(null);
const [hasOverflow, setHasOverflow] = useState(false);
useEffect(() => {
const scrollbar = scrollbarRef.current;
const tableWrap = tableWrapRef.current;
if (!scrollbar || !tableWrap) return;
let syncing = false;
const syncTable = () => {
if (syncing) return;
syncing = true;
tableWrap.scrollLeft = scrollbar.scrollLeft;
window.requestAnimationFrame(() => {
syncing = false;
});
};
const syncScrollbar = () => {
if (syncing) return;
syncing = true;
scrollbar.scrollLeft = tableWrap.scrollLeft;
window.requestAnimationFrame(() => {
syncing = false;
});
};
const updateOverflow = () => {
setHasOverflow(tableWrap.scrollWidth > tableWrap.clientWidth + 1);
scrollbar.scrollLeft = tableWrap.scrollLeft;
};
updateOverflow();
scrollbar.addEventListener('scroll', syncTable, { passive: true });
tableWrap.addEventListener('scroll', syncScrollbar, { passive: true });
const resizeObserver = new ResizeObserver(updateOverflow);
resizeObserver.observe(tableWrap);
return () => {
scrollbar.removeEventListener('scroll', syncTable);
tableWrap.removeEventListener('scroll', syncScrollbar);
resizeObserver.disconnect();
};
}, [tableWrapRef, totalWidth]);
return (
<div
ref={scrollbarRef}
className={`usage-table-top-scrollbar ${hasOverflow ? '' : 'is-hidden'}`}
aria-hidden="true"
>
<div style={{ width: `${totalWidth}px`, height: '1px' }} />
</div>
);
}
function UsageResultCell({ record }: { record: UsageRecord }) {
const { t } = useI18n();
const state = record.canceled ? 'canceled' : record.failed ? 'failed' : 'success';
@@ -1203,14 +1270,26 @@ function UsageEventCell({
}
}
function EventsView({ events, onPage }: { events: UsageEventPage; onPage: (page: number) => void }) {
function EventsView({
events,
pageSize,
onPage,
onPageSizeChange,
}: {
events: UsageEventPage;
pageSize: number;
onPage: (page: number) => void;
onPageSizeChange: (pageSize: number) => void;
}) {
const { t } = useI18n();
const [widths, setWidths] = useState<Record<EventColumnKey, number>>(getInitialColumnWidths);
const [visibleColumnKeys, setVisibleColumnKeys] = useState<EventColumnKey[]>(getInitialVisibleColumns);
const [columnSettingsOpen, setColumnSettingsOpen] = useState(false);
const [draftVisibleColumnKeys, setDraftVisibleColumnKeys] = useState<EventColumnKey[]>(visibleColumnKeys);
const [resizingCol, setResizingCol] = useState<EventColumnKey | null>(null);
const columnDialogRef = useRef<HTMLElement | null>(null);
const tableWrapRef = useRef<HTMLDivElement | null>(null);
const visibleColumnKeySet = new Set(visibleColumnKeys);
const visibleColumns = EVENT_COLUMNS.filter((column) => visibleColumnKeySet.has(column.key));
@@ -1242,12 +1321,15 @@ function EventsView({ events, onPage }: { events: UsageEventPage; onPage: (page:
if (current.includes(key)) {
return current.length > 1 ? current.filter((columnKey) => columnKey !== key) : current;
}
return EVENT_COLUMNS.filter((column) => current.includes(column.key) || column.key === key).map((column) => column.key);
return EVENT_COLUMNS.filter(
(column) => current.includes(column.key) || column.key === key
).map((column) => column.key);
});
};
const applyColumnSettings = () => {
const next = draftVisibleColumnKeys.length > 0 ? draftVisibleColumnKeys : getAllEventColumnKeys();
const next =
draftVisibleColumnKeys.length > 0 ? draftVisibleColumnKeys : getAllEventColumnKeys();
setVisibleColumnKeys(next);
try {
localStorage.setItem(EVENT_VISIBLE_COLS_STORAGE_KEY, JSON.stringify(next));
@@ -1279,7 +1361,8 @@ function EventsView({ events, onPage }: { events: UsageEventPage; onPage: (page:
e.stopPropagation();
const startX = e.clientX;
const startWidth = widths[key] ?? EVENT_COLUMNS.find((c) => c.key === key)?.defaultWidth ?? 100;
const startWidth =
widths[key] ?? EVENT_COLUMNS.find((c) => c.key === key)?.defaultWidth ?? 100;
const colDef = EVENT_COLUMNS.find((c) => c.key === key);
const minWidth = colDef?.minWidth ?? 50;
@@ -1314,7 +1397,13 @@ function EventsView({ events, onPage }: { events: UsageEventPage; onPage: (page:
window.addEventListener('pointerup', onPointerUp);
};
const totalTableWidth = visibleColumns.reduce((sum, col) => sum + (widths[col.key] ?? col.defaultWidth), 0);
const totalTableWidth = visibleColumns.reduce(
(sum, col) => sum + (widths[col.key] ?? col.defaultWidth),
0
);
const startRecordNum = events.total > 0 ? (events.page - 1) * pageSize + 1 : 0;
const endRecordNum = Math.min(events.page * pageSize, events.total);
return (
<section className="panel usage-events-panel">
@@ -1323,10 +1412,11 @@ function EventsView({ events, onPage }: { events: UsageEventPage; onPage: (page:
<span className="usage-events-count-badge">
{t('usage.events.total', { count: compactNumber(events.total) })}
</span>
<span className="usage-col-hint" title={t('usage.events.resizeHint')}>
{t('usage.events.dragHint')}
<span className="usage-events-page-indicator">
{t('usage.events.page', { page: events.page, total: events.totalPages })}
</span>
</div>
<div className="usage-events-summary-right">
<button
type="button"
@@ -1334,25 +1424,29 @@ function EventsView({ events, onPage }: { events: UsageEventPage; onPage: (page:
onClick={openColumnSettings}
title={t('usage.events.columnSettings')}
>
<Columns3Cog size={13} />
<Columns3Cog size={14} />
<span>{t('usage.events.columnSettings')}</span>
</button>
{isCustomized ? (
<button
type="button"
className="usage-col-reset-btn"
className="usage-col-reset-btn icon-only"
onClick={resetAllWidths}
title={t('usage.events.resizeHint')}
title={t('usage.events.resetColumns')}
aria-label={t('usage.events.resetColumns')}
>
<RotateCcw size={12} />
<span>{t('usage.events.resetColumns')}</span>
<RotateCcw size={13} />
</button>
) : null}
</div>
</div>
{events.items.length > 0 ? (
<TableTopScrollbar tableWrapRef={tableWrapRef} totalWidth={totalTableWidth} />
) : null}
{events.items.length ? (
<div className="usage-table-wrap">
<div ref={tableWrapRef} className="usage-table-wrap">
<table className="usage-events-table" style={{ width: `${totalTableWidth}px`, minWidth: '100%' }}>
<colgroup>
{visibleColumns.map((col) => (
@@ -1404,31 +1498,63 @@ function EventsView({ events, onPage }: { events: UsageEventPage; onPage: (page:
)}
<div className="usage-pagination">
<button
type="button"
className="secondary-button"
disabled={events.page <= 1}
onClick={() => onPage(events.page - 1)}
>
{t('usage.previous')}
</button>
<span className="usage-pagination-info">
{events.page} / {events.totalPages}
</span>
<button
type="button"
className="secondary-button"
disabled={events.page >= events.totalPages}
onClick={() => onPage(events.page + 1)}
>
{t('usage.next')}
</button>
<div className="usage-pagination-left">
<span className="usage-pagination-summary">
{t('usage.events.rangeSummary', {
start: startRecordNum,
end: endRecordNum,
total: compactNumber(events.total),
})}
</span>
<label style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
<select
className="usage-page-size-select"
value={pageSize}
onChange={(e) => onPageSizeChange(Number(e.currentTarget.value))}
aria-label={t('usage.events.pageSize', { size: pageSize })}
>
<option value="20">{t('usage.events.pageSize', { size: 20 })}</option>
<option value="50">{t('usage.events.pageSize', { size: 50 })}</option>
<option value="100">{t('usage.events.pageSize', { size: 100 })}</option>
<option value="200">{t('usage.events.pageSize', { size: 200 })}</option>
</select>
</label>
</div>
<div className="usage-pagination-right">
<button
type="button"
className="usage-page-nav-btn"
disabled={events.page <= 1}
onClick={() => onPage(events.page - 1)}
>
<ChevronLeft size={14} />
<span>{t('usage.previous')}</span>
</button>
<span className="usage-pagination-info">
{events.page} / {events.totalPages}
</span>
<button
type="button"
className="usage-page-nav-btn"
disabled={events.page >= events.totalPages}
onClick={() => onPage(events.page + 1)}
>
<span>{t('usage.next')}</span>
<ChevronRight size={14} />
</button>
</div>
</div>
{columnSettingsOpen ? (
<div
className="config-dialog-backdrop"
onMouseDown={(event) => event.currentTarget === event.target && setColumnSettingsOpen(false)}
onMouseDown={(event) =>
event.currentTarget === event.target && setColumnSettingsOpen(false)
}
>
<section
ref={columnDialogRef}
@@ -1455,7 +1581,9 @@ function EventsView({ events, onPage }: { events: UsageEventPage; onPage: (page:
<X size={17} />
</button>
</div>
<p className="usage-column-dialog-description">{t('usage.events.columnSettingsDescription')}</p>
<p className="usage-column-dialog-description">
{t('usage.events.columnSettingsDescription')}
</p>
<div className="usage-column-options">
{EVENT_COLUMNS.map((column) => {
const checked = draftVisibleColumnKeys.includes(column.key);
@@ -1480,15 +1608,27 @@ function EventsView({ events, onPage }: { events: UsageEventPage; onPage: (page:
total: EVENT_COLUMNS.length,
})}
</span>
<button type="button" className="usage-column-select-all" onClick={resetVisibleColumns}>
<button
type="button"
className="usage-column-select-all"
onClick={resetVisibleColumns}
>
{t('usage.events.selectAllColumns')}
</button>
</div>
<div className="usage-column-dialog-actions">
<button type="button" className="secondary-button" onClick={() => setColumnSettingsOpen(false)}>
<button
type="button"
className="secondary-button"
onClick={() => setColumnSettingsOpen(false)}
>
{t('common.cancel')}
</button>
<button type="button" className="primary-button" onClick={applyColumnSettings}>
<button
type="button"
className="primary-button"
onClick={applyColumnSettings}
>
{t('usage.events.applyColumns')}
</button>
</div>
+163 -77
View File
@@ -6617,42 +6617,29 @@ textarea:focus-visible {
.usage-records-page {
display: grid;
gap: 16px;
gap: 12px;
align-content: start;
}
.usage-records-header {
.usage-topbar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
min-height: 56px;
gap: 12px;
min-width: 0;
flex-wrap: wrap;
}
.usage-header-left {
display: grid;
gap: 2px;
}
.usage-header-tag {
font-size: var(--font-size-caption);
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--theme-8a847c);
}
.usage-header-right {
.usage-topbar-actions {
display: flex;
align-items: center;
gap: 10px;
gap: 8px;
}
.usage-refresh-btn {
width: 36px;
height: 36px;
border-radius: 8px;
width: 34px;
height: 34px;
border-radius: 7px;
border: 1px solid var(--theme-e3e1db);
background: var(--theme-fffdf8);
color: var(--theme-5f5a53);
@@ -6675,19 +6662,21 @@ textarea:focus-visible {
}
.usage-collector-state {
display: flex;
display: inline-flex;
align-items: center;
gap: 10px;
min-width: 170px;
padding: 6px 12px;
gap: 7px;
min-width: 0;
height: 34px;
padding: 0 10px;
border: 1px solid var(--theme-e3e1db);
border-radius: 8px;
border-radius: 7px;
background: var(--theme-fffdf8);
white-space: nowrap;
}
.usage-collector-state .status-dot {
width: 8px;
height: 8px;
width: 7px;
height: 7px;
border-radius: 50%;
background: var(--theme-8a847c);
flex-shrink: 0;
@@ -6704,11 +6693,6 @@ textarea:focus-visible {
box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.2);
}
.usage-collector-state > div {
display: grid;
gap: 1px;
}
.usage-collector-state strong {
color: var(--theme-2d2a26);
font-size: var(--font-size-caption);
@@ -6722,6 +6706,11 @@ textarea:focus-visible {
line-height: 1.2;
}
.usage-collector-state > span:last-child {
padding-left: 7px;
border-left: 1px solid var(--theme-e3e1db);
}
/* Tab Switcher */
.usage-tabs {
display: inline-flex;
@@ -7264,11 +7253,11 @@ textarea:focus-visible {
.usage-events-summary {
display: flex;
min-height: 58px;
min-height: 54px;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 11px 16px;
padding: 10px 16px;
border-bottom: 1px solid var(--theme-e3e1db);
background: var(--theme-fffdf8);
color: var(--theme-8a847c);
@@ -7286,7 +7275,7 @@ textarea:focus-visible {
.usage-events-count-badge {
display: inline-flex;
align-items: center;
padding: 3px 8px;
padding: 3px 9px;
border: 1px solid var(--theme-e3e1db);
border-radius: 999px;
background: var(--theme-f4f1ea);
@@ -7298,7 +7287,7 @@ textarea:focus-visible {
.usage-events-summary-right {
display: flex;
align-items: center;
gap: 10px;
gap: 8px;
}
.usage-events-page-indicator {
@@ -7310,7 +7299,7 @@ textarea:focus-visible {
.usage-col-hint {
font-size: 11px;
color: var(--theme-9a958d);
opacity: 0.85;
opacity: 0.9;
}
.usage-col-reset-btn,
@@ -7319,8 +7308,8 @@ textarea:focus-visible {
align-items: center;
justify-content: center;
gap: 6px;
min-height: 32px;
padding: 6px 11px;
min-height: 30px;
padding: 5px 10px;
font-size: var(--font-size-caption);
font-weight: 600;
background: var(--theme-f4f1ea);
@@ -7334,8 +7323,8 @@ textarea:focus-visible {
.usage-col-reset-btn svg,
.usage-col-settings-btn svg {
width: 15px;
height: 15px;
width: 14px;
height: 14px;
flex: 0 0 auto;
}
@@ -7346,6 +7335,7 @@ textarea:focus-visible {
border-color: var(--theme-d5d2cb);
}
/* Column Settings Dialog */
.usage-column-dialog {
display: flex;
width: min(540px, 100%);
@@ -7468,9 +7458,25 @@ textarea:focus-visible {
min-width: 84px;
}
/* Native horizontal scrollbar, mirrored above the table. */
.usage-table-top-scrollbar {
overflow-x: auto;
overflow-y: hidden;
height: 16px;
border-bottom: 1px solid var(--theme-e3e1db);
background: var(--theme-fffdf8);
}
.usage-table-top-scrollbar.is-hidden {
display: none;
}
/* Main Table Wrap & Table */
.usage-table-wrap {
overflow: auto;
height: clamp(480px, 62vh, 700px);
position: relative;
overflow-x: hidden;
overflow-y: auto;
height: clamp(480px, 60vh, 720px);
min-height: 0;
border: 0;
border-radius: 0;
@@ -7489,8 +7495,8 @@ textarea:focus-visible {
.usage-events-table th,
.usage-events-table td {
height: 46px;
padding: 8px 12px;
height: 44px;
padding: 7px 11px;
border-bottom: 1px solid var(--theme-eeeae2);
color: var(--theme-5f5a53);
white-space: nowrap;
@@ -7498,12 +7504,13 @@ textarea:focus-visible {
box-sizing: border-box;
}
.usage-events-table tr:nth-child(even) td {
background-color: transparent;
.usage-events-table tbody tr {
cursor: default;
transition: background-color 0.12s ease;
}
.usage-events-table tr:hover td {
background-color: var(--theme-f2efe8) !important;
.usage-events-table tbody tr:hover td {
background-color: var(--theme-f4f1ea) !important;
}
.usage-events-table th {
@@ -7554,7 +7561,7 @@ textarea:focus-visible {
bottom: 0;
width: 7px;
cursor: col-resize;
z-index: 3;
z-index: 6;
touch-action: none;
display: flex;
justify-content: center;
@@ -7606,21 +7613,6 @@ body.table-col-resizing * {
font-variant-numeric: tabular-nums;
}
@media (max-width: 560px) {
.usage-column-options {
grid-template-columns: 1fr;
}
.usage-column-dialog-footer {
align-items: stretch;
flex-direction: column;
}
.usage-column-dialog-actions button {
flex: 1;
}
}
.usage-events-table td.usage-td-provider,
.usage-events-table td.usage-td-source {
overflow: hidden;
@@ -7711,17 +7703,87 @@ body.table-col-resizing * {
font-size: 11px;
}
/* Enhanced Pagination */
.usage-pagination {
display: flex;
min-height: 52px;
min-height: 50px;
align-items: center;
justify-content: flex-end;
gap: 8px;
padding: 0 16px;
justify-content: space-between;
gap: 12px;
padding: 8px 16px;
border-top: 1px solid var(--theme-e3e1db);
background: var(--theme-fffdf8);
color: var(--theme-8a847c);
font-size: var(--font-size-caption);
flex-wrap: wrap;
}
.usage-pagination-left {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
.usage-pagination-summary {
font-size: var(--font-size-caption);
color: var(--theme-7a746c);
font-variant-numeric: tabular-nums;
}
.usage-page-size-select {
padding: 4px 8px;
border: 1px solid var(--theme-e3e1db);
border-radius: 5px;
background: var(--theme-faf8f4);
color: var(--theme-2d2a26);
font-size: var(--font-size-caption);
cursor: pointer;
}
.usage-page-size-select:focus {
border-color: var(--theme-3f6f98);
outline: none;
}
.usage-pagination-right {
display: flex;
align-items: center;
gap: 6px;
}
.usage-page-nav-btn {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 30px;
height: 30px;
padding: 0 6px;
border: 1px solid var(--theme-e3e1db);
border-radius: 5px;
background: var(--theme-fffdf8);
color: var(--theme-5f5a53);
cursor: pointer;
font-size: var(--font-size-caption);
font-weight: 500;
transition: all 0.15s ease;
gap: 3px;
}
.usage-col-reset-btn.icon-only {
width: 30px;
padding: 0;
}
.usage-page-nav-btn:hover:not(:disabled) {
background: var(--theme-f4f1ea);
border-color: var(--theme-d5d2cb);
color: var(--theme-2d2a26);
}
.usage-page-nav-btn:disabled {
opacity: 0.35;
cursor: default;
}
.usage-pagination-info {
@@ -7731,12 +7793,28 @@ body.table-col-resizing * {
color: var(--theme-7a746c);
}
.usage-pagination button {
min-width: 72px;
min-height: 30px;
padding: 0 10px;
border-radius: 6px;
font-size: var(--font-size-caption);
@media (max-width: 640px) {
.usage-column-options {
grid-template-columns: 1fr;
}
.usage-column-dialog-footer {
align-items: stretch;
flex-direction: column;
}
.usage-column-dialog-actions button {
flex: 1;
}
.usage-pagination {
align-items: stretch;
flex-direction: column;
}
.usage-pagination-right {
justify-content: space-between;
}
}
.usage-events-panel > .usage-empty {
@@ -9183,6 +9261,14 @@ body.table-col-resizing * {
padding: 0 7px;
}
.usage-topbar-actions {
width: 100%;
}
.usage-collector-state {
flex: 1;
}
.usage-stat-grid {
grid-template-columns: 1fr;
}
+1
View File
@@ -11,6 +11,7 @@ export default defineConfig({
build: {
target: 'es2020',
outDir: 'dist',
chunkSizeWarningLimit: 600,
rollupOptions: {
output: {
manualChunks(id) {