feat: improve mobile layout responsiveness
This commit is contained in:
parent
9d1b9a78c4
commit
4ba5dcc785
7 changed files with 178 additions and 54 deletions
|
|
@ -21,6 +21,7 @@ import Settings from './components/Settings/Settings';
|
|||
import { Alerts } from './pages/Alerts';
|
||||
import { DockerHosts } from './components/Docker/DockerHosts';
|
||||
import { ToastContainer } from './components/Toast/Toast';
|
||||
import NotificationContainer from './components/NotificationContainer';
|
||||
import { ErrorBoundary } from './components/ErrorBoundary';
|
||||
import { SecurityWarning } from './components/SecurityWarning';
|
||||
import { Login } from './components/Login';
|
||||
|
|
@ -584,6 +585,7 @@ function App() {
|
|||
</AppLayout>
|
||||
</div>
|
||||
<ToastContainer />
|
||||
<NotificationContainer />
|
||||
<TokenRevealDialog />
|
||||
<TooltipRoot />
|
||||
</DarkModeContext.Provider>
|
||||
|
|
@ -784,12 +786,34 @@ function AppLayout(props: {
|
|||
navigate(tab.route);
|
||||
};
|
||||
|
||||
type PlatformTab = ReturnType<typeof platformTabs>[number];
|
||||
type UtilityTab = ReturnType<typeof utilityTabs>[number];
|
||||
|
||||
const mobileTabs = createMemo<
|
||||
Array<(PlatformTab & { type: 'platform' }) | (UtilityTab & { type: 'utility' })>
|
||||
>(() => {
|
||||
const platformEntries = platformTabs().map((tab) => ({ ...tab, type: 'platform' as const }));
|
||||
const utilityEntries = utilityTabs().map((tab) => ({ ...tab, type: 'utility' as const }));
|
||||
return [...platformEntries, ...utilityEntries];
|
||||
});
|
||||
|
||||
const handleMobileTabChange = (tabId: PlatformTab['id'] | UtilityTab['id']) => {
|
||||
const platform = platformTabs().find((tab) => tab.id === tabId);
|
||||
if (platform) {
|
||||
handlePlatformClick(platform);
|
||||
return;
|
||||
}
|
||||
const utility = utilityTabs().find((tab) => tab.id === tabId);
|
||||
if (utility) {
|
||||
handleUtilityClick(utility);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div class="pulse-shell">
|
||||
{/* Header */}
|
||||
<div class="header flex flex-row justify-between items-center mb-2">
|
||||
<div class="hidden md:block md:flex-1"></div>
|
||||
<div class="flex items-center gap-1 flex-none">
|
||||
<div class="header mb-3 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div class="flex items-center justify-between gap-2 sm:gap-3">
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
|
|
@ -824,8 +848,8 @@ function AppLayout(props: {
|
|||
</span>
|
||||
</Show>
|
||||
</div>
|
||||
<div class="header-controls flex justify-end items-center gap-4 md:flex-1">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="header-controls flex w-full flex-wrap items-center gap-3 justify-start sm:w-auto sm:flex-1 sm:justify-end">
|
||||
<div class="flex w-full flex-wrap items-center gap-2 justify-start sm:w-auto sm:justify-end">
|
||||
<div
|
||||
class={`group status text-xs rounded-full flex items-center justify-center transition-all duration-500 ease-in-out px-1.5 ${
|
||||
props.connected()
|
||||
|
|
@ -904,9 +928,41 @@ function AppLayout(props: {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mobile Tab Switcher */}
|
||||
<div class="sm:hidden mb-3">
|
||||
<label for="mobile-tab-switcher" class="sr-only">
|
||||
Navigate between tabs
|
||||
</label>
|
||||
<div class="relative">
|
||||
<select
|
||||
id="mobile-tab-switcher"
|
||||
value={getActiveTab()}
|
||||
onChange={(event) => handleMobileTabChange(event.currentTarget.value as PlatformTab['id'] | UtilityTab['id'])}
|
||||
class="w-full appearance-none rounded-lg border border-gray-300 bg-white py-2 pl-3 pr-9 text-sm font-medium text-gray-700 shadow-sm focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-200 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-200 dark:focus:border-blue-400 dark:focus:ring-blue-500/40"
|
||||
>
|
||||
<For each={mobileTabs()}>
|
||||
{(tab) => (
|
||||
<option value={tab.id}>
|
||||
{tab.type === 'platform' && !tab.enabled ? `${tab.label} (Configure)` : tab.label}
|
||||
</option>
|
||||
)}
|
||||
</For>
|
||||
</select>
|
||||
<div class="pointer-events-none absolute inset-y-0 right-3 flex items-center text-gray-400 dark:text-gray-500">
|
||||
<svg class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M5.23 7.21a.75.75 0 0 1 1.06.02L10 10.94l3.71-3.71a.75.75 0 1 1 1.06 1.06l-4.24 4.24a.75.75 0 0 1-1.06 0L5.21 8.29a.75.75 0 0 1 .02-1.08z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Tabs */}
|
||||
<div
|
||||
class="tabs flex items-end gap-2 mb-2 border-b border-gray-300 dark:border-gray-700 overflow-x-auto overflow-y-hidden whitespace-nowrap scrollbar-hide"
|
||||
class="tabs mb-2 flex items-end gap-2 overflow-x-auto overflow-y-hidden whitespace-nowrap border-b border-gray-300 dark:border-gray-700 scrollbar-hide"
|
||||
role="tablist"
|
||||
aria-label="Primary navigation"
|
||||
>
|
||||
|
|
@ -1008,7 +1064,10 @@ function AppLayout(props: {
|
|||
})()}
|
||||
</span>
|
||||
<Show when={tab.badge === 'update'}>
|
||||
<span class="ml-1 w-2 h-2 bg-red-500 rounded-full animate-pulse"></span>
|
||||
<span class="ml-1 flex items-center">
|
||||
<span class="sr-only">Update available</span>
|
||||
<span aria-hidden="true" class="block h-2 w-2 rounded-full bg-red-500 animate-pulse"></span>
|
||||
</span>
|
||||
</Show>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1353,7 +1353,7 @@ const UnifiedBackups: Component = () => {
|
|||
{/* Available Backups chart - keep visible while backups exist or a date filter is active */}
|
||||
<Show when={hasAnyBackups() || selectedDateRange() !== null}>
|
||||
<Card padding="md">
|
||||
<div class="mb-3 flex items-start justify-between gap-3">
|
||||
<div class="mb-3 flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
|
||||
<SectionHeader
|
||||
title={
|
||||
<div class="flex items-center gap-1.5">
|
||||
|
|
@ -1416,7 +1416,7 @@ const UnifiedBackups: Component = () => {
|
|||
</div>
|
||||
)}
|
||||
</Show>
|
||||
<div class="flex flex-wrap items-center justify-end gap-2">
|
||||
<div class="flex flex-wrap items-center justify-start gap-2 sm:justify-end">
|
||||
<div class="flex items-center gap-1">
|
||||
<button
|
||||
type="button"
|
||||
|
|
@ -1463,7 +1463,7 @@ const UnifiedBackups: Component = () => {
|
|||
1y
|
||||
</button>
|
||||
</div>
|
||||
<div class="h-4 w-px bg-gray-300 dark:bg-gray-600"></div>
|
||||
<div class="hidden h-4 w-px bg-gray-300 dark:bg-gray-600 sm:block"></div>
|
||||
<span class="text-gray-500 dark:text-gray-400">Last {chartTimeRange()} days</span>
|
||||
<Show when={selectedDateRange()}>
|
||||
<button
|
||||
|
|
@ -1480,7 +1480,7 @@ const UnifiedBackups: Component = () => {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="h-32 relative bg-gray-100 dark:bg-gray-800 rounded overflow-hidden">
|
||||
<div class="relative min-h-[14rem] sm:min-h-[12rem] bg-gray-100 dark:bg-gray-800 rounded overflow-hidden">
|
||||
<Show
|
||||
when={chartData().data.length > 0}
|
||||
fallback={
|
||||
|
|
@ -1511,7 +1511,9 @@ const UnifiedBackups: Component = () => {
|
|||
>
|
||||
<svg
|
||||
class="available-backups-svg w-full h-full"
|
||||
style="cursor: pointer"
|
||||
role="img"
|
||||
aria-label="Available backups chart"
|
||||
style="cursor: pointer; touch-action: pan-y;"
|
||||
ref={(el) => {
|
||||
// Use createEffect to reactively update the chart
|
||||
createEffect(() => {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { Card } from '@/components/shared/Card';
|
|||
import { MetricBar } from '@/components/Dashboard/MetricBar';
|
||||
import { renderDockerStatusBadge } from './DockerStatusBadge';
|
||||
import { formatUptime } from '@/utils/format';
|
||||
import { ScrollableTable } from '@/components/shared/ScrollableTable';
|
||||
|
||||
export interface DockerHostSummary {
|
||||
host: DockerHost;
|
||||
|
|
@ -114,15 +115,15 @@ export const DockerHostSummaryTable: Component<DockerHostSummaryTableProps> = (p
|
|||
|
||||
return (
|
||||
<Card padding="none" class="mb-4 overflow-hidden">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full min-w-[720px] border-collapse">
|
||||
<ScrollableTable minWidth="720px">
|
||||
<table class="w-full border-collapse">
|
||||
<thead>
|
||||
<tr class="bg-gray-50 dark:bg-gray-700/50 text-gray-600 dark:text-gray-300 border-b border-gray-200 dark:border-gray-600">
|
||||
<th
|
||||
class="pl-3 pr-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider w-1/4 cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-blue-500"
|
||||
onClick={() => handleSort('name')}
|
||||
onKeyDown={(e) => e.key === 'Enter' && handleSort('name')}
|
||||
tabindex="0"
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
aria-label={`Sort by host ${sortKey() === 'name' ? (sortDirection() === 'asc' ? 'ascending' : 'descending') : ''}`}
|
||||
>
|
||||
|
|
@ -150,19 +151,19 @@ export const DockerHostSummaryTable: Component<DockerHostSummaryTableProps> = (p
|
|||
Containers {renderSortIndicator('running')}
|
||||
</th>
|
||||
<th
|
||||
class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider min-w-24 cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
|
||||
class="hidden px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider min-w-24 cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600 sm:table-cell"
|
||||
onClick={() => handleSort('uptime')}
|
||||
>
|
||||
Uptime {renderSortIndicator('uptime')}
|
||||
</th>
|
||||
<th
|
||||
class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider min-w-32 cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
|
||||
class="hidden px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider min-w-32 cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600 sm:table-cell"
|
||||
onClick={() => handleSort('lastSeen')}
|
||||
>
|
||||
Last Update {renderSortIndicator('lastSeen')}
|
||||
</th>
|
||||
<th
|
||||
class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider min-w-24 cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
|
||||
class="hidden px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider min-w-24 cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600 sm:table-cell"
|
||||
onClick={() => handleSort('agent')}
|
||||
>
|
||||
Agent {renderSortIndicator('agent')}
|
||||
|
|
@ -174,6 +175,8 @@ export const DockerHostSummaryTable: Component<DockerHostSummaryTableProps> = (p
|
|||
{(summary) => {
|
||||
const selected = props.selectedHostId() === summary.host.id;
|
||||
const online = isHostOnline(summary.host);
|
||||
const uptimeLabel = summary.uptimeSeconds ? formatUptime(summary.uptimeSeconds) : '—';
|
||||
const agentLabel = summary.host.agentVersion ? `v${summary.host.agentVersion}` : '—';
|
||||
|
||||
const rowStyle = () => {
|
||||
const styles: Record<string, string> = {};
|
||||
|
|
@ -206,14 +209,16 @@ export const DockerHostSummaryTable: Component<DockerHostSummaryTableProps> = (p
|
|||
return className;
|
||||
};
|
||||
|
||||
const agentOutdated = isAgentOutdated(summary.host.agentVersion);
|
||||
|
||||
return (
|
||||
<tr
|
||||
class={rowClass()}
|
||||
style={rowStyle()}
|
||||
onClick={() => props.onSelect(summary.host.id)}
|
||||
>
|
||||
<td class="pr-2 py-0.5 pl-3 whitespace-nowrap">
|
||||
<div class="flex items-center gap-1">
|
||||
<td class="pr-2 py-1 pl-3 align-top">
|
||||
<div class="flex flex-wrap items-center gap-1">
|
||||
<span class="font-medium text-[11px] text-gray-900 dark:text-gray-100">
|
||||
{summary.host.displayName}
|
||||
</span>
|
||||
|
|
@ -228,11 +233,51 @@ export const DockerHostSummaryTable: Component<DockerHostSummaryTableProps> = (p
|
|||
</span>
|
||||
</Show>
|
||||
</div>
|
||||
<div class="mt-2 grid grid-cols-1 gap-1 text-[10px] text-gray-500 dark:text-gray-400 sm:hidden">
|
||||
<div class="flex items-center gap-1">
|
||||
<span class="font-semibold text-gray-600 dark:text-gray-300">Uptime:</span>
|
||||
<span>{uptimeLabel}</span>
|
||||
</div>
|
||||
<div class="flex items-start gap-1">
|
||||
<span class="font-semibold text-gray-600 dark:text-gray-300">Last:</span>
|
||||
<span class="flex-1">
|
||||
<span>{summary.lastSeenRelative}</span>
|
||||
<Show when={summary.lastSeenAbsolute}>
|
||||
<span class="block text-[9px] text-gray-400 dark:text-gray-500">
|
||||
{summary.lastSeenAbsolute}
|
||||
</span>
|
||||
</Show>
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center gap-1">
|
||||
<span class="font-semibold text-gray-600 dark:text-gray-300">Agent:</span>
|
||||
<span
|
||||
class={
|
||||
agentOutdated
|
||||
? 'rounded px-1 py-0.5 bg-yellow-100 dark:bg-yellow-900/20 text-yellow-700 dark:text-yellow-400 font-medium'
|
||||
: 'rounded px-1 py-0.5 bg-green-100 dark:bg-green-900/20 text-green-700 dark:text-green-400 font-medium'
|
||||
}
|
||||
>
|
||||
{agentLabel}
|
||||
</span>
|
||||
<Show when={agentOutdated}>
|
||||
<span class="text-[9px] font-medium text-yellow-600 dark:text-yellow-500">
|
||||
Update recommended
|
||||
</span>
|
||||
</Show>
|
||||
</div>
|
||||
<Show when={summary.host.intervalSeconds}>
|
||||
<div class="flex items-center gap-1">
|
||||
<span class="font-semibold text-gray-600 dark:text-gray-300">Interval:</span>
|
||||
<span>{summary.host.intervalSeconds}s</span>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-2 py-0.5">
|
||||
<td class="px-2 py-1 align-top">
|
||||
{renderDockerStatusBadge(summary.host.status)}
|
||||
</td>
|
||||
<td class="px-2 py-0.5">
|
||||
<td class="px-2 py-1 align-top">
|
||||
<Show when={online} fallback={<span class="text-xs text-gray-400 dark:text-gray-500">—</span>}>
|
||||
<MetricBar
|
||||
value={summary.cpuPercent}
|
||||
|
|
@ -241,7 +286,7 @@ export const DockerHostSummaryTable: Component<DockerHostSummaryTableProps> = (p
|
|||
/>
|
||||
</Show>
|
||||
</td>
|
||||
<td class="px-2 py-0.5">
|
||||
<td class="px-2 py-1 align-top">
|
||||
<Show when={online} fallback={<span class="text-xs text-gray-400 dark:text-gray-500">—</span>}>
|
||||
<MetricBar
|
||||
value={summary.memoryPercent}
|
||||
|
|
@ -251,8 +296,8 @@ export const DockerHostSummaryTable: Component<DockerHostSummaryTableProps> = (p
|
|||
/>
|
||||
</Show>
|
||||
</td>
|
||||
<td class="px-2 py-0.5">
|
||||
<div class="flex justify-center">
|
||||
<td class="px-2 py-1 align-top">
|
||||
<div class="flex justify-start sm:justify-center">
|
||||
<MetricBar
|
||||
value={summary.runningPercent}
|
||||
label={`${summary.runningCount}/${summary.totalCount}`}
|
||||
|
|
@ -260,38 +305,40 @@ export const DockerHostSummaryTable: Component<DockerHostSummaryTableProps> = (p
|
|||
/>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-2 py-0.5 whitespace-nowrap">
|
||||
<td class="hidden px-2 py-1 align-top whitespace-nowrap sm:table-cell">
|
||||
<span class="text-xs text-gray-600 dark:text-gray-400">
|
||||
{summary.uptimeSeconds ? formatUptime(summary.uptimeSeconds) : '—'}
|
||||
{uptimeLabel}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-2 py-0.5">
|
||||
<td class="hidden px-2 py-1 align-top sm:table-cell">
|
||||
<div class="text-sm text-gray-900 dark:text-gray-100">
|
||||
{summary.lastSeenRelative}
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400">
|
||||
{summary.lastSeenAbsolute}
|
||||
</div>
|
||||
<Show when={summary.lastSeenAbsolute}>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400">
|
||||
{summary.lastSeenAbsolute}
|
||||
</div>
|
||||
</Show>
|
||||
</td>
|
||||
<td class="px-2 py-0.5">
|
||||
<td class="hidden px-2 py-1 align-top sm:table-cell">
|
||||
<div class="flex flex-col gap-0.5">
|
||||
<Show when={summary.host.agentVersion}>
|
||||
<div class="flex flex-col gap-0.5">
|
||||
<span
|
||||
class={
|
||||
isAgentOutdated(summary.host.agentVersion)
|
||||
agentOutdated
|
||||
? 'text-[10px] px-1.5 py-0.5 rounded bg-yellow-100 dark:bg-yellow-900/30 text-yellow-700 dark:text-yellow-400 font-medium inline-block w-fit'
|
||||
: 'text-[10px] px-1.5 py-0.5 rounded bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-400 font-medium inline-block w-fit'
|
||||
}
|
||||
title={
|
||||
isAgentOutdated(summary.host.agentVersion)
|
||||
agentOutdated
|
||||
? 'Outdated - Update recommended'
|
||||
: 'Up to date - Auto-update enabled'
|
||||
}
|
||||
>
|
||||
v{summary.host.agentVersion}
|
||||
</span>
|
||||
<Show when={isAgentOutdated(summary.host.agentVersion)}>
|
||||
<Show when={agentOutdated}>
|
||||
<span class="text-[9px] text-yellow-600 dark:text-yellow-500 font-medium">
|
||||
⚠ Update available
|
||||
</span>
|
||||
|
|
@ -311,7 +358,7 @@ export const DockerHostSummaryTable: Component<DockerHostSummaryTableProps> = (p
|
|||
</For>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</ScrollableTable>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Component, JSX, createSignal, createEffect, onMount } from 'solid-js';
|
||||
import { Component, JSX, createSignal, createEffect, onMount, onCleanup } from 'solid-js';
|
||||
import { Show } from 'solid-js';
|
||||
|
||||
interface ScrollableTableProps {
|
||||
|
|
@ -23,6 +23,9 @@ export const ScrollableTable: Component<ScrollableTableProps> = (props) => {
|
|||
onMount(() => {
|
||||
checkScroll();
|
||||
window.addEventListener('resize', checkScroll);
|
||||
onCleanup(() => {
|
||||
window.removeEventListener('resize', checkScroll);
|
||||
});
|
||||
});
|
||||
|
||||
createEffect(() => {
|
||||
|
|
@ -43,7 +46,7 @@ export const ScrollableTable: Component<ScrollableTableProps> = (props) => {
|
|||
<div
|
||||
ref={scrollContainer}
|
||||
class="overflow-x-auto"
|
||||
style="scrollbar-width: none; -ms-overflow-style: none;"
|
||||
style="scrollbar-width: none; -ms-overflow-style: none; -webkit-overflow-scrolling: touch; overscroll-behavior-x: contain;"
|
||||
>
|
||||
<style>{`
|
||||
.overflow-x-auto::-webkit-scrollbar { display: none; }
|
||||
|
|
|
|||
|
|
@ -31,11 +31,11 @@ export function SectionHeader(props: SectionHeaderProps) {
|
|||
const sizeClass = () => {
|
||||
switch (local.size) {
|
||||
case 'sm':
|
||||
return 'text-base';
|
||||
return 'text-base sm:text-lg';
|
||||
case 'lg':
|
||||
return 'text-2xl';
|
||||
return 'text-xl sm:text-2xl lg:text-3xl';
|
||||
default:
|
||||
return 'text-lg';
|
||||
return 'text-lg sm:text-xl';
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -47,13 +47,13 @@ export function SectionHeader(props: SectionHeaderProps) {
|
|||
</span>
|
||||
</Show>
|
||||
<h2
|
||||
class={`${sizeClass()} font-semibold text-gray-900 dark:text-gray-100 ${local.titleClass ?? ''}`.trim()}
|
||||
class={`${sizeClass()} font-semibold leading-snug text-gray-900 dark:text-gray-100 ${local.titleClass ?? ''}`.trim()}
|
||||
>
|
||||
{local.title}
|
||||
</h2>
|
||||
<Show when={local.description}>
|
||||
<p
|
||||
class={`text-sm text-gray-600 dark:text-gray-400 ${local.descriptionClass ?? ''}`.trim()}
|
||||
class={`text-sm sm:text-base text-gray-600 dark:text-gray-400 ${local.descriptionClass ?? ''}`.trim()}
|
||||
>
|
||||
{local.description}
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -119,15 +119,27 @@
|
|||
|
||||
@layer components {
|
||||
.pulse-shell {
|
||||
width: min(95.5vw, 95rem);
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
max-width: 95rem;
|
||||
margin-inline: auto;
|
||||
padding-inline: clamp(1.25rem, 3vw, 3.25rem);
|
||||
padding-inline: clamp(1rem, 3vw, 3.25rem);
|
||||
transition: padding-inline 0.3s ease;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.pulse-shell {
|
||||
padding-inline: clamp(0.75rem, 6vw, 1.25rem);
|
||||
}
|
||||
}
|
||||
|
||||
.pulse-panel {
|
||||
padding: clamp(0.75rem, 1.6vw, 1.5rem);
|
||||
padding: clamp(0.75rem, 1.8vw, 1.5rem);
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.pulse-panel {
|
||||
padding: clamp(0.75rem, 5vw, 1.25rem);
|
||||
}
|
||||
}
|
||||
|
||||
/* Card styles */
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { SectionHeader } from '@/components/shared/SectionHeader';
|
|||
import { SettingsPanel } from '@/components/shared/SettingsPanel';
|
||||
import { Toggle } from '@/components/shared/Toggle';
|
||||
import { formField, formControl, formHelpText, labelClass, controlClass } from '@/components/shared/Form';
|
||||
import { ScrollableTable } from '@/components/shared/ScrollableTable';
|
||||
import { useWebSocket } from '@/App';
|
||||
import { showSuccess, showError } from '@/utils/toast';
|
||||
import { showTooltip, hideTooltip } from '@/components/shared/Tooltip';
|
||||
|
|
@ -1795,7 +1796,7 @@ function OverviewTab(props: {
|
|||
return (
|
||||
<div class="space-y-6">
|
||||
{/* Stats Cards */}
|
||||
<div class="grid grid-cols-2 gap-3 sm:gap-4 lg:grid-cols-4">
|
||||
<div class="grid grid-cols-1 gap-3 sm:grid-cols-2 sm:gap-4 lg:grid-cols-4">
|
||||
<Card padding="sm" class="sm:p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
|
|
@ -3073,7 +3074,7 @@ function ScheduleTab(props: ScheduleTabProps) {
|
|||
<span class={`${labelClass('text-xs uppercase tracking-[0.08em]')} mb-2 block`}>
|
||||
Grouping strategy
|
||||
</span>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<div class="grid grid-cols-1 gap-2 sm:grid-cols-2">
|
||||
<label
|
||||
class={`relative flex items-center gap-2 rounded-lg border-2 p-3 transition-all ${
|
||||
grouping().byNode
|
||||
|
|
@ -4221,7 +4222,7 @@ function HistoryTab() {
|
|||
<select
|
||||
value={timeFilter()}
|
||||
onChange={(e) => setTimeFilter(e.currentTarget.value as '24h' | '7d' | '30d' | 'all')}
|
||||
class="px-3 py-2 text-sm border rounded-lg dark:bg-gray-700 dark:border-gray-600"
|
||||
class="w-full sm:w-auto px-3 py-2 text-sm border rounded-lg dark:bg-gray-700 dark:border-gray-600"
|
||||
>
|
||||
<option value="24h">Last 24h</option>
|
||||
<option value="7d">Last 7d</option>
|
||||
|
|
@ -4232,14 +4233,14 @@ function HistoryTab() {
|
|||
<select
|
||||
value={severityFilter()}
|
||||
onChange={(e) => setSeverityFilter(e.currentTarget.value as 'warning' | 'critical' | 'all')}
|
||||
class="px-3 py-2 text-sm border rounded-lg dark:bg-gray-700 dark:border-gray-600"
|
||||
class="w-full sm:w-auto px-3 py-2 text-sm border rounded-lg dark:bg-gray-700 dark:border-gray-600"
|
||||
>
|
||||
<option value="all">All Levels</option>
|
||||
<option value="critical">Critical Only</option>
|
||||
<option value="warning">Warning Only</option>
|
||||
</select>
|
||||
|
||||
<div class="flex-1 max-w-xs">
|
||||
<div class="w-full sm:flex-1 sm:max-w-xs">
|
||||
<input
|
||||
ref={searchInputRef}
|
||||
type="text"
|
||||
|
|
@ -4273,7 +4274,7 @@ function HistoryTab() {
|
|||
>
|
||||
{/* Table */}
|
||||
<div class="mb-2 border border-gray-200 dark:border-gray-700 rounded overflow-hidden">
|
||||
<div class="overflow-x-auto">
|
||||
<ScrollableTable minWidth="900px">
|
||||
<table class="w-full min-w-[900px] text-xs sm:text-sm">
|
||||
<thead>
|
||||
<tr class="bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300 border-b border-gray-300 dark:border-gray-600">
|
||||
|
|
@ -4418,7 +4419,7 @@ function HistoryTab() {
|
|||
</For>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</ScrollableTable>
|
||||
</div>
|
||||
</Show>
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue