ui updated
This commit is contained in:
parent
e25aca59c3
commit
ebd7c84a24
84
backend/data/pending-autostart/__global__.json
Normal file
84
backend/data/pending-autostart/__global__.json
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"modelKey": "sw33t_g1rl",
|
||||||
|
"url": "https://www.myfreecams.com/#sw33t_g1rl",
|
||||||
|
"mode": "probe_retry",
|
||||||
|
"nextProbeAtMs": 1775732773528,
|
||||||
|
"currentShow": "offline",
|
||||||
|
"source": "watched"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"modelKey": "sunshinegirlx",
|
||||||
|
"url": "https://chaturbate.com/sunshinegirlx/",
|
||||||
|
"mode": "wait_public",
|
||||||
|
"currentShow": "private",
|
||||||
|
"imageUrl": "https://thumb.live.mmcdn.com/ri/sunshinegirlx.jpg",
|
||||||
|
"source": "watched"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"modelKey": "yourhanna",
|
||||||
|
"url": "https://chaturbate.com/yourhanna/",
|
||||||
|
"mode": "wait_public",
|
||||||
|
"currentShow": "hidden",
|
||||||
|
"imageUrl": "https://thumb.live.mmcdn.com/ri/yourhanna.jpg",
|
||||||
|
"source": "watched"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"modelKey": "nataliecroft",
|
||||||
|
"url": "https://chaturbate.com/nataliecroft/",
|
||||||
|
"mode": "wait_public",
|
||||||
|
"currentShow": "hidden",
|
||||||
|
"imageUrl": "https://thumb.live.mmcdn.com/ri/nataliecroft.jpg",
|
||||||
|
"source": "watched"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"modelKey": "wasianbby",
|
||||||
|
"url": "https://chaturbate.com/wasianbby/",
|
||||||
|
"mode": "wait_public",
|
||||||
|
"currentShow": "private",
|
||||||
|
"imageUrl": "https://thumb.live.mmcdn.com/ri/wasianbby.jpg",
|
||||||
|
"source": "watched"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"modelKey": "love_vibe",
|
||||||
|
"url": "https://chaturbate.com/love_vibe/",
|
||||||
|
"mode": "wait_public",
|
||||||
|
"currentShow": "private",
|
||||||
|
"imageUrl": "https://thumb.live.mmcdn.com/ri/love_vibe.jpg",
|
||||||
|
"source": "watched"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"modelKey": "georgiannahearn",
|
||||||
|
"url": "https://chaturbate.com/georgiannahearn/",
|
||||||
|
"mode": "wait_public",
|
||||||
|
"currentShow": "private",
|
||||||
|
"imageUrl": "https://thumb.live.mmcdn.com/ri/georgiannahearn.jpg",
|
||||||
|
"source": "watched"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"modelKey": "libbyshepard",
|
||||||
|
"url": "https://chaturbate.com/libbyshepard/",
|
||||||
|
"mode": "wait_public",
|
||||||
|
"currentShow": "away",
|
||||||
|
"imageUrl": "https://thumb.live.mmcdn.com/ri/libbyshepard.jpg",
|
||||||
|
"source": "watched"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"modelKey": "minions_",
|
||||||
|
"url": "https://chaturbate.com/minions_/",
|
||||||
|
"mode": "wait_public",
|
||||||
|
"currentShow": "hidden",
|
||||||
|
"imageUrl": "https://thumb.live.mmcdn.com/ri/minions_.jpg",
|
||||||
|
"source": "watched"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"modelKey": "amanda_sayhey",
|
||||||
|
"url": "https://chaturbate.com/amanda_sayhey/",
|
||||||
|
"mode": "probe_retry",
|
||||||
|
"nextProbeAtMs": 1775734021567,
|
||||||
|
"currentShow": "unknown",
|
||||||
|
"source": "watched"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
3
backend/data/pending-autostart/admin.json
Normal file
3
backend/data/pending-autostart/admin.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"items": []
|
||||||
|
}
|
||||||
@ -3580,6 +3580,12 @@ export default function App() {
|
|||||||
return <LoginPage onLoggedIn={checkAuth} />
|
return <LoginPage onLoggedIn={checkAuth} />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const headerInfoPanelClass =
|
||||||
|
'rounded-lg border border-gray-200/70 bg-white/45 px-2.5 py-1.5 shadow-sm backdrop-blur dark:border-white/10 dark:bg-white/5 sm:rounded-xl sm:px-3 sm:py-2'
|
||||||
|
|
||||||
|
const headerStatBadgeClass =
|
||||||
|
'inline-flex h-7 items-center gap-1 rounded-full border border-gray-200/70 bg-white/70 px-2 text-[10px] font-semibold text-gray-900 shadow-sm backdrop-blur dark:border-white/10 dark:bg-white/10 dark:text-gray-100 sm:h-8 sm:gap-1.5 sm:px-2.5 sm:text-[11px]'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-[100dvh] bg-gray-50 text-gray-900 dark:bg-gray-950 dark:text-gray-100">
|
<div className="min-h-[100dvh] bg-gray-50 text-gray-900 dark:bg-gray-950 dark:text-gray-100">
|
||||||
<div aria-hidden="true" className="pointer-events-none fixed inset-0 overflow-hidden">
|
<div aria-hidden="true" className="pointer-events-none fixed inset-0 overflow-hidden">
|
||||||
@ -3589,81 +3595,115 @@ export default function App() {
|
|||||||
|
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<header className="z-[70] bg-white/70 backdrop-blur dark:bg-gray-950/60 sm:sticky sm:top-0 sm:border-b sm:border-gray-200/70 sm:dark:border-white/10">
|
<header className="z-[70] bg-white/70 backdrop-blur dark:bg-gray-950/60 sm:sticky sm:top-0 sm:border-b sm:border-gray-200/70 sm:dark:border-white/10">
|
||||||
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 pt-3 sm:py-4 space-y-2 sm:space-y-3">
|
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 pt-2.5 sm:py-4 space-y-2 sm:space-y-3">
|
||||||
<div className="flex items-center sm:items-start justify-between gap-3 sm:gap-4">
|
<div className="flex items-center justify-between gap-3 sm:gap-4">
|
||||||
<div className="min-w-0">
|
<div className="min-w-0 flex-1">
|
||||||
<div className="min-w-0">
|
{/* Desktop */}
|
||||||
<div className="flex items-center gap-3 min-w-0">
|
<div className="hidden sm:flex min-h-[48px] items-center">
|
||||||
<h1 className="text-lg font-semibold tracking-tight text-gray-900 dark:text-white">
|
<div className={`min-w-0 ${headerInfoPanelClass}`}>
|
||||||
Recorder
|
<div className="flex min-w-0 flex-wrap items-center gap-2.5">
|
||||||
</h1>
|
<h1 className="shrink-0 text-lg font-semibold tracking-tight text-gray-900 dark:text-white">
|
||||||
|
Recorder
|
||||||
|
</h1>
|
||||||
|
|
||||||
{/* ✅ Mobile: Icons + Counts direkt rechts neben Recorder */}
|
<div className="flex min-w-0 flex-wrap items-center gap-1.5">
|
||||||
<div className="flex items-center gap-1.5 shrink-0">
|
<span className={headerStatBadgeClass} title="online">
|
||||||
<span
|
<SignalIcon className="size-4 opacity-80" />
|
||||||
className="inline-flex items-center gap-1 rounded-full bg-gray-100/80 px-2 py-1 text-[11px] font-semibold text-gray-900 ring-1 ring-gray-200/60 dark:bg-white/10 dark:text-gray-100 dark:ring-white/10"
|
<span className="tabular-nums">{onlineModelsCount}</span>
|
||||||
title="online"
|
</span>
|
||||||
>
|
|
||||||
<SignalIcon className="size-4 opacity-80" />
|
|
||||||
<span className="tabular-nums">{onlineModelsCount}</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span
|
<span className={headerStatBadgeClass} title="Watched online">
|
||||||
className="inline-flex items-center gap-1 rounded-full bg-gray-100/80 px-2 py-1 text-[11px] font-semibold text-gray-900 ring-1 ring-gray-200/60 dark:bg-white/10 dark:text-gray-100 dark:ring-white/10"
|
<EyeIcon className="size-4 opacity-80" />
|
||||||
title="Watched online"
|
<span className="tabular-nums">{onlineWatchedModelsCount}</span>
|
||||||
>
|
</span>
|
||||||
<EyeIcon className="size-4 opacity-80" />
|
|
||||||
<span className="tabular-nums">{onlineWatchedModelsCount}</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span
|
<span className={headerStatBadgeClass} title="Fav online">
|
||||||
className="inline-flex items-center gap-1 rounded-full bg-gray-100/80 px-2 py-1 text-[11px] font-semibold text-gray-900 ring-1 ring-gray-200/60 dark:bg-white/10 dark:text-gray-100 dark:ring-white/10"
|
<HeartIcon className="size-4 opacity-80" />
|
||||||
title="Fav online"
|
<span className="tabular-nums">{onlineFavCount}</span>
|
||||||
>
|
</span>
|
||||||
<HeartIcon className="size-4 opacity-80" />
|
|
||||||
<span className="tabular-nums">{onlineFavCount}</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span
|
<span className={headerStatBadgeClass} title="Like online">
|
||||||
className="inline-flex items-center gap-1 rounded-full bg-gray-100/80 px-2 py-1 text-[11px] font-semibold text-gray-900 ring-1 ring-gray-200/60 dark:bg-white/10 dark:text-gray-100 dark:ring-white/10"
|
<HandThumbUpIcon className="size-4 opacity-80" />
|
||||||
title="Like online"
|
<span className="tabular-nums">{onlineLikedCount}</span>
|
||||||
>
|
</span>
|
||||||
<HandThumbUpIcon className="size-4 opacity-80" />
|
</div>
|
||||||
<span className="tabular-nums">{onlineLikedCount}</span>
|
|
||||||
</span>
|
<div className="hidden lg:block h-4 w-px bg-gray-200/70 dark:bg-white/10" />
|
||||||
|
|
||||||
|
<div className="hidden lg:block whitespace-nowrap text-[11px] text-gray-500 dark:text-gray-400">
|
||||||
|
<LastUpdatedText
|
||||||
|
enabled={Boolean(recSettings.useChaturbateApi)}
|
||||||
|
fetchedAt={cbOnlineFetchedAt}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="hidden sm:block text-[11px] text-gray-500 dark:text-gray-400">
|
</div>
|
||||||
<LastUpdatedText
|
</div>
|
||||||
enabled={Boolean(recSettings.useChaturbateApi)}
|
|
||||||
fetchedAt={cbOnlineFetchedAt}
|
{/* Mobile */}
|
||||||
/>
|
<div className="sm:hidden space-y-1.5">
|
||||||
|
<div className={headerInfoPanelClass}>
|
||||||
|
<div className="flex min-w-0 flex-col gap-1.5">
|
||||||
|
<div className="flex min-w-0 items-center justify-between gap-2">
|
||||||
|
<h1 className="shrink-0 text-base font-semibold tracking-tight text-gray-900 dark:text-white">
|
||||||
|
Recorder
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<div className="min-w-0 flex max-w-[72%] flex-wrap justify-end gap-1">
|
||||||
|
<span className={headerStatBadgeClass} title="online">
|
||||||
|
<SignalIcon className="size-4 opacity-80" />
|
||||||
|
<span className="tabular-nums">{onlineModelsCount}</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span className={headerStatBadgeClass} title="Watched online">
|
||||||
|
<EyeIcon className="size-4 opacity-80" />
|
||||||
|
<span className="tabular-nums">{onlineWatchedModelsCount}</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span className={headerStatBadgeClass} title="Fav online">
|
||||||
|
<HeartIcon className="size-4 opacity-80" />
|
||||||
|
<span className="tabular-nums">{onlineFavCount}</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span className={headerStatBadgeClass} title="Like online">
|
||||||
|
<HandThumbUpIcon className="size-4 opacity-80" />
|
||||||
|
<span className="tabular-nums">{onlineLikedCount}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="text-[10px] leading-tight text-gray-500 dark:text-gray-400">
|
||||||
|
<LastUpdatedText
|
||||||
|
enabled={Boolean(recSettings.useChaturbateApi)}
|
||||||
|
fetchedAt={cbOnlineFetchedAt}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* ✅ Mobile: Status volle Breite + PerfMonitor + Cookies nebeneinander */}
|
<div className="space-y-1.5">
|
||||||
<div className="sm:hidden mt-1 w-full">
|
{showPerfMon ? (
|
||||||
<div className="text-[11px] text-gray-500 dark:text-gray-400">
|
<PerformanceMonitor
|
||||||
<LastUpdatedText
|
mode="inline"
|
||||||
enabled={Boolean(recSettings.useChaturbateApi)}
|
compact
|
||||||
fetchedAt={cbOnlineFetchedAt}
|
className="w-full"
|
||||||
/>
|
/>
|
||||||
</div>
|
) : null}
|
||||||
|
|
||||||
<div className="mt-2 flex items-stretch gap-2">
|
|
||||||
{showPerfMon ? <PerformanceMonitor mode="inline" className="flex-1" /> : <div className="flex-1" />}
|
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-1.5">
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
onClick={() => setCookieModalOpen(true)}
|
onClick={() => setCookieModalOpen(true)}
|
||||||
className="px-3 shrink-0"
|
className="h-9 w-full px-2.5 text-sm"
|
||||||
>
|
>
|
||||||
Cookies
|
Cookies
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="soft"
|
||||||
|
color='red'
|
||||||
onClick={logout}
|
onClick={logout}
|
||||||
className="px-3 shrink-0"
|
className="h-9 w-full px-2.5 text-sm"
|
||||||
>
|
>
|
||||||
Abmelden
|
Abmelden
|
||||||
</Button>
|
</Button>
|
||||||
@ -3672,12 +3712,29 @@ export default function App() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="hidden sm:flex items-center gap-2 h-full">
|
<div className="hidden sm:flex shrink-0 items-center gap-2 lg:gap-3">
|
||||||
{showPerfMon ? <PerformanceMonitor mode="inline" /> : null}
|
{showPerfMon ? (
|
||||||
<Button variant="secondary" onClick={() => setCookieModalOpen(true)} className="h-9 px-3">
|
<PerformanceMonitor
|
||||||
|
mode="inline"
|
||||||
|
compact
|
||||||
|
className="w-[300px] lg:w-[360px]"
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
onClick={() => setCookieModalOpen(true)}
|
||||||
|
className="h-9 px-3 shrink-0"
|
||||||
|
>
|
||||||
Cookies
|
Cookies
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="secondary" onClick={logout} className="h-9 px-3">
|
|
||||||
|
<Button
|
||||||
|
variant="soft"
|
||||||
|
color='red'
|
||||||
|
onClick={logout}
|
||||||
|
className="h-9 px-3 shrink-0"
|
||||||
|
>
|
||||||
Abmelden
|
Abmelden
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -3710,7 +3710,8 @@ export default function FinishedDownloads({
|
|||||||
|
|
||||||
<Button
|
<Button
|
||||||
size="md"
|
size="md"
|
||||||
variant="secondary"
|
variant={lastAction ? "soft" : "secondary"}
|
||||||
|
color="emerald"
|
||||||
disabled={!lastAction || undoing}
|
disabled={!lastAction || undoing}
|
||||||
onClick={undoLastAction}
|
onClick={undoLastAction}
|
||||||
leadingIcon={<ArrowUturnLeftIcon className="size-4 shrink-0" />}
|
leadingIcon={<ArrowUturnLeftIcon className="size-4 shrink-0" />}
|
||||||
@ -3780,7 +3781,8 @@ export default function FinishedDownloads({
|
|||||||
<div className="flex items-center justify-between gap-2">
|
<div className="flex items-center justify-between gap-2">
|
||||||
<Button
|
<Button
|
||||||
size="md"
|
size="md"
|
||||||
variant="secondary"
|
variant={lastAction ? "soft" : "secondary"}
|
||||||
|
color="emerald"
|
||||||
className="flex-1"
|
className="flex-1"
|
||||||
disabled={!lastAction || undoing}
|
disabled={!lastAction || undoing}
|
||||||
onClick={undoLastAction}
|
onClick={undoLastAction}
|
||||||
|
|||||||
@ -8,6 +8,8 @@ type Props = {
|
|||||||
className?: string
|
className?: string
|
||||||
/** Server perf poll Intervall */
|
/** Server perf poll Intervall */
|
||||||
pollMs?: number
|
pollMs?: number
|
||||||
|
/** kompakter, header-freundlicher Modus */
|
||||||
|
compact?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const clamp01 = (v: number) => Math.max(0, Math.min(1, v))
|
const clamp01 = (v: number) => Math.max(0, Math.min(1, v))
|
||||||
@ -41,6 +43,26 @@ function formatBytes(bytes: number | null) {
|
|||||||
return `${v.toFixed(digits)} ${units[i]}`
|
return `${v.toFixed(digits)} ${units[i]}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatBytesCompact(bytes: number | null) {
|
||||||
|
if (bytes == null || !Number.isFinite(bytes) || bytes <= 0) return '–'
|
||||||
|
const units = ['B', 'KB', 'MB', 'GB', 'TB']
|
||||||
|
let v = bytes
|
||||||
|
let i = 0
|
||||||
|
|
||||||
|
while (v >= 1024 && i < units.length - 1) {
|
||||||
|
v /= 1024
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
const rounded =
|
||||||
|
i === 0 ? Math.round(v) :
|
||||||
|
v >= 100 ? Math.round(v) :
|
||||||
|
v >= 10 ? Number(v.toFixed(0)) :
|
||||||
|
Number(v.toFixed(1))
|
||||||
|
|
||||||
|
return `${rounded} ${units[i]}`
|
||||||
|
}
|
||||||
|
|
||||||
function useFps(sampleMs = 1000) {
|
function useFps(sampleMs = 1000) {
|
||||||
const [fps, setFps] = React.useState<number | null>(null)
|
const [fps, setFps] = React.useState<number | null>(null)
|
||||||
|
|
||||||
@ -103,6 +125,7 @@ export default function PerformanceMonitor({
|
|||||||
mode = 'inline',
|
mode = 'inline',
|
||||||
className,
|
className,
|
||||||
pollMs = 3000,
|
pollMs = 3000,
|
||||||
|
compact = false,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const fps = useFps(1000)
|
const fps = useFps(1000)
|
||||||
|
|
||||||
@ -201,18 +224,95 @@ export default function PerformanceMonitor({
|
|||||||
const wrapperClass =
|
const wrapperClass =
|
||||||
mode === 'floating'
|
mode === 'floating'
|
||||||
? 'fixed bottom-4 right-4 z-[80]'
|
? 'fixed bottom-4 right-4 z-[80]'
|
||||||
: 'flex items-center' // inline: sichtbar, Caller entscheidet per className
|
: compact
|
||||||
|
? 'min-w-0'
|
||||||
|
: 'w-full min-w-0'
|
||||||
|
|
||||||
|
const panelClass = compact
|
||||||
|
? `
|
||||||
|
w-full min-w-0 rounded-lg border border-gray-200/70 bg-white/70 px-1.5 py-1 shadow-sm backdrop-blur
|
||||||
|
dark:border-white/10 dark:bg-white/5
|
||||||
|
sm:rounded-xl sm:px-2 sm:py-1.5
|
||||||
|
`
|
||||||
|
: `
|
||||||
|
rounded-lg border border-gray-200/70 bg-white/70 px-2.5 py-1.5 shadow-sm backdrop-blur
|
||||||
|
dark:border-white/10 dark:bg-white/5
|
||||||
|
grid grid-cols-2 gap-x-3 gap-y-2
|
||||||
|
sm:flex sm:items-center sm:gap-3
|
||||||
|
`
|
||||||
|
|
||||||
|
if (compact) {
|
||||||
|
const metrics = [
|
||||||
|
{
|
||||||
|
key: 'disk',
|
||||||
|
label: 'Disk',
|
||||||
|
value: formatBytesCompact(diskFreeBytes),
|
||||||
|
fill: usedFill,
|
||||||
|
tone: diskTone,
|
||||||
|
title: diskTitle,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'ping',
|
||||||
|
label: 'Ping',
|
||||||
|
value: formatMs(ping),
|
||||||
|
fill: pingFill,
|
||||||
|
tone: pingTone,
|
||||||
|
title: `Ping: ${formatMs(ping)}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'fps',
|
||||||
|
label: 'FPS',
|
||||||
|
value: fps != null ? String(fps) : '–',
|
||||||
|
fill: fpsFill,
|
||||||
|
tone: fpsTone,
|
||||||
|
title: `FPS: ${fps != null ? fps : '–'}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'cpu',
|
||||||
|
label: 'CPU',
|
||||||
|
value: formatPct(cpu),
|
||||||
|
fill: cpuFill,
|
||||||
|
tone: cpuTone,
|
||||||
|
title: `CPU: ${formatPct(cpu)}`,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`${wrapperClass} ${className ?? ''}`}>
|
||||||
|
<div className={panelClass}>
|
||||||
|
<div className="grid grid-cols-2 gap-1 md:grid-cols-4">
|
||||||
|
{metrics.map((item) => (
|
||||||
|
<div
|
||||||
|
key={item.key}
|
||||||
|
title={item.title}
|
||||||
|
className="min-w-0 rounded-md px-1.5 py-0.5 sm:rounded-lg sm:px-2 sm:py-1"
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-between gap-1.5 whitespace-nowrap">
|
||||||
|
<span className="min-w-0 truncate text-[9px] font-medium uppercase tracking-wide text-gray-500 dark:text-gray-400 sm:text-[10px]">
|
||||||
|
{item.label}
|
||||||
|
</span>
|
||||||
|
<span className="shrink-0 whitespace-nowrap text-[10px] font-semibold leading-none tabular-nums text-gray-900 dark:text-gray-100 sm:text-[11px]">
|
||||||
|
{item.value}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-1 h-[3px] overflow-hidden rounded-full bg-gray-200/70 dark:bg-white/10 sm:mt-1.5 sm:h-1">
|
||||||
|
<div
|
||||||
|
className={`h-full rounded-full ${barTone(item.tone)}`}
|
||||||
|
style={{ width: `${Math.round(item.fill * 100)}%` }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`${wrapperClass} ${className ?? ''}`}>
|
<div className={`${wrapperClass} ${className ?? ''}`}>
|
||||||
<div
|
<div className={panelClass}>
|
||||||
className="
|
|
||||||
rounded-lg border border-gray-200/70 bg-white/70 px-2.5 py-1.5 shadow-sm backdrop-blur
|
|
||||||
dark:border-white/10 dark:bg-white/5
|
|
||||||
grid grid-cols-2 gap-x-3 gap-y-2
|
|
||||||
sm:flex sm:items-center sm:gap-3
|
|
||||||
"
|
|
||||||
>
|
|
||||||
{/* DISK */}
|
{/* DISK */}
|
||||||
<div className="flex items-center gap-2" title={diskTitle}>
|
<div className="flex items-center gap-2" title={diskTitle}>
|
||||||
<span className="text-[11px] font-medium text-gray-600 dark:text-gray-300">Disk</span>
|
<span className="text-[11px] font-medium text-gray-600 dark:text-gray-300">Disk</span>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user