mirror of
https://github.com/fankes/beszel.git
synced 2025-10-18 17:29:28 +08:00
site updates
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Home</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
|
1
site/public/favicon.svg
Normal file
1
site/public/favicon.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 54.7 70" fill="#2D954F"><path d="M0 54.7V0h10.2v54.7a4.8 4.8 0 0 0 1.2 3.2 6.4 6.4 0 0 0 .3.4 5.6 5.6 0 0 0 1.5 1 4.7 4.7 0 0 0 2.1.5h29.3V0h10.1v59.8H44.6V70H15.3a16.7 16.7 0 0 1-4.8-.7 15.3 15.3 0 0 1-1.3-.5q-2.9-1.1-4.9-3.1a14 14 0 0 1-2.8-4 16.5 16.5 0 0 1-.4-.9 16 16 0 0 1-1-5.4 18.6 18.6 0 0 1-.1-.7Z"/></svg>
|
After Width: | Height: | Size: 369 B |
1
site/public/ubik-reiswar.svg
Normal file
1
site/public/ubik-reiswar.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 421.6 140.2"><path fill-rule="evenodd" stroke="#000" stroke-linecap="round" stroke-width=".9" d="M0 109.4V0h20.4v109.4a9.6 9.6 0 0 0 2.4 6.4 12.8 12.8 0 0 0 .7.7q3.1 3.1 7.1 3.1h58.6V0h20.2v119.6H89.2V140H30.6a33.4 33.4 0 0 1-9.6-1.3 30.7 30.7 0 0 1-2.7-1 30 30 0 0 1-7-4 26.8 26.8 0 0 1-2.7-2.3 28.1 28.1 0 0 1-5.5-8 33 33 0 0 1-.8-1.7 32.2 32.2 0 0 1-2.3-11 37.1 37.1 0 0 1 0-1.3ZM307 0h20.4v73.8l58.4-30.6q2 0 3-.8t1.5-2q.5-1.2.6-2.5l.1-2.3V17.8l20.4-10.2v28a29.5 29.5 0 0 1-3.8 14.4 34.6 34.6 0 0 1-.1.2 26 26 0 0 1-11 10.5 31.6 31.6 0 0 1-.5.3l-17.8 10.2 43.4 68.8h-23l-38.2-58.6-28 12.8q-2 2-3.5 3.8a7 7 0 0 0-1 1.5 5.3 5.3 0 0 0-.5 2.3V140H307V0ZM127.6 109.4v-89h20.2V0H214q5.8 0 11.3 2.3 5.5 2.3 9.8 6.3a30 30 0 0 1 6.5 9 34.4 34.4 0 0 1 .4.7 29.3 29.3 0 0 1 2.6 12.2 33.4 33.4 0 0 1 0 .1v12.6a23.3 23.3 0 0 1-1.2 7.4 26.1 26.1 0 0 1-.1.2q-1.3 3.8-3.4 7.1-2.1 3.3-4.9 6.1a48.8 48.8 0 0 1-3.3 3 39.5 39.5 0 0 1-2.3 1.8l2.4 2.4q7.6 4 12.7 11a28 28 0 0 1 3.7 6.7 22.5 22.5 0 0 1 1.4 7.9v12.6a29.4 29.4 0 0 1-2.4 11.8 28.6 28.6 0 0 1-.2.5 30.7 30.7 0 0 1-5.3 8 28.7 28.7 0 0 1-1.6 1.7q-4.3 4-9.7 6.3-5.4 2.3-11.2 2.3h-63.6q-5.8 0-10.9-2.3-5.1-2.3-8.9-6.3a28 28 0 0 1-5-7.3 33.7 33.7 0 0 1-1-2.4 33.3 33.3 0 0 1-2.2-10.6 38.6 38.6 0 0 1 0-1.7Zm20.2-89V112q0 6 5 7.3a11.8 11.8 0 0 0 2.8.3h63.6a9.2 9.2 0 0 0 5.3-1.7 13 13 0 0 0 1.7-1.4 11.2 11.2 0 0 0 2.1-2.7 9 9 0 0 0 1.1-4.4V96.8q0-2-1.6-3.9a11 11 0 0 0 0 0 57.9 57.9 0 0 0-2.2-2.4 70.3 70.3 0 0 0-1.4-1.5l-17.8-7.6-43.2 23v-23l56-30.4q1.6 0 3-2.3a12.4 12.4 0 0 0 .4-.6 18.3 18.3 0 0 0 .8-1.6q.7-1.8.8-3.1a5.2 5.2 0 0 0 0-.2V30.6a9.6 9.6 0 0 0-2.4-6.4 12.8 12.8 0 0 0-.7-.7q-3.1-3.1-7.1-3.1h-66.2ZM266.4 0H287v140.2h-20.6V0Z" font-size="12" vector-effect="non-scaling-stroke"/></svg>
|
After Width: | Height: | Size: 1.7 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
Before Width: | Height: | Size: 1.5 KiB |
@@ -26,8 +26,8 @@ export function AddServerButton() {
|
||||
function copyDockerCompose(port: string) {
|
||||
copyToClipboard(`services:
|
||||
agent:
|
||||
image: 'henrygd/monitor-agent'
|
||||
container_name: 'monitor-agent'
|
||||
image: 'henrygd/ubik-agent'
|
||||
container_name: 'ubik-agent'
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- '${port}:45876'
|
||||
|
@@ -4,19 +4,21 @@ import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from 'recharts'
|
||||
import {
|
||||
ChartConfig,
|
||||
ChartContainer,
|
||||
ChartLegend,
|
||||
ChartLegendContent,
|
||||
ChartTooltip,
|
||||
ChartTooltipContent,
|
||||
} from '@/components/ui/chart'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { useMemo } from 'react'
|
||||
import { formatShortDate, formatShortTime } from '@/lib/utils'
|
||||
import Spinner from '../spinner'
|
||||
|
||||
export default function ({ chartData }: { chartData: Record<string, number | string>[] }) {
|
||||
const [containerNames, setContainerNames] = useState([] as string[])
|
||||
|
||||
export default function ({
|
||||
chartData,
|
||||
max,
|
||||
}: {
|
||||
chartData: Record<string, number | string>[]
|
||||
max: number
|
||||
}) {
|
||||
const chartConfig = useMemo(() => {
|
||||
console.log('chartData', chartData)
|
||||
let config = {} as Record<
|
||||
string,
|
||||
{
|
||||
@@ -24,13 +26,21 @@ export default function ({ chartData }: { chartData: Record<string, number | str
|
||||
color: string
|
||||
}
|
||||
>
|
||||
const lastRecord = chartData.at(-1)
|
||||
// @ts-ignore
|
||||
let allKeys = new Set(Object.keys(lastRecord))
|
||||
allKeys.delete('time')
|
||||
const keys = Array.from(allKeys)
|
||||
keys.sort((a, b) => (lastRecord![b] as number) - (lastRecord![a] as number))
|
||||
setContainerNames(keys)
|
||||
const totalUsage = {} as Record<string, number>
|
||||
for (let stats of chartData) {
|
||||
for (let key in stats) {
|
||||
if (key === 'time') {
|
||||
continue
|
||||
}
|
||||
if (!(key in totalUsage)) {
|
||||
totalUsage[key] = 0
|
||||
}
|
||||
// @ts-ignore
|
||||
totalUsage[key] += stats[key]
|
||||
}
|
||||
}
|
||||
let keys = Object.keys(totalUsage)
|
||||
keys.sort((a, b) => (totalUsage[a] > totalUsage[b] ? -1 : 1))
|
||||
const length = keys.length
|
||||
for (let i = 0; i < length; i++) {
|
||||
const key = keys[i]
|
||||
@@ -40,12 +50,11 @@ export default function ({ chartData }: { chartData: Record<string, number | str
|
||||
color: `hsl(${hue}, 60%, 60%)`,
|
||||
}
|
||||
}
|
||||
console.log('config', config)
|
||||
return config satisfies ChartConfig
|
||||
}, [chartData])
|
||||
|
||||
if (!containerNames.length) {
|
||||
return null
|
||||
if (!chartData.length) {
|
||||
return <Spinner />
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -53,20 +62,23 @@ export default function ({ chartData }: { chartData: Record<string, number | str
|
||||
<AreaChart
|
||||
accessibilityLayer
|
||||
data={chartData}
|
||||
margin={{
|
||||
left: 12,
|
||||
right: 12,
|
||||
top: 12,
|
||||
}}
|
||||
|
||||
// reverseStackOrder={true}
|
||||
>
|
||||
<CartesianGrid vertical={false} />
|
||||
{/* <YAxis domain={[0, 250]} tickCount={5} tickLine={false} axisLine={false} tickMargin={8} /> */}
|
||||
<XAxis
|
||||
dataKey="time"
|
||||
<YAxis
|
||||
domain={[0, max]}
|
||||
tickCount={5}
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
tickFormatter={(v) => `${v}%`}
|
||||
/>
|
||||
<XAxis
|
||||
dataKey="time"
|
||||
tickLine={true}
|
||||
axisLine={false}
|
||||
tickMargin={8}
|
||||
minTickGap={30}
|
||||
tickFormatter={formatShortTime}
|
||||
/>
|
||||
<ChartTooltip
|
||||
@@ -76,19 +88,13 @@ export default function ({ chartData }: { chartData: Record<string, number | str
|
||||
// console.log('itemSorter', item)
|
||||
// return -item.value
|
||||
// }}
|
||||
content={
|
||||
<ChartTooltipContent
|
||||
// itemSorter={(item) => {
|
||||
// console.log('itemSorter', item)
|
||||
// return -item.value
|
||||
// }}
|
||||
indicator="line"
|
||||
/>
|
||||
}
|
||||
content={<ChartTooltipContent indicator="line" />}
|
||||
/>
|
||||
{containerNames.map((key) => (
|
||||
{Object.keys(chartConfig).map((key) => (
|
||||
<Area
|
||||
key={key}
|
||||
// isAnimationActive={false}
|
||||
animateNewValues={false}
|
||||
dataKey={key}
|
||||
type="natural"
|
||||
fill={chartConfig[key].color}
|
||||
@@ -97,15 +103,6 @@ export default function ({ chartData }: { chartData: Record<string, number | str
|
||||
stackId="a"
|
||||
/>
|
||||
))}
|
||||
{/* <Area
|
||||
dataKey="other"
|
||||
type="natural"
|
||||
fill="var(--color-other)"
|
||||
fillOpacity={0.4}
|
||||
stroke="var(--color-other)"
|
||||
stackId="a"
|
||||
/> */}
|
||||
{/* <ChartLegend content={<ChartLegendContent />} className="flex-wrap gap-y-2 mb-2" /> */}
|
||||
</AreaChart>
|
||||
</ChartContainer>
|
||||
)
|
||||
|
@@ -7,6 +7,8 @@ import {
|
||||
ChartTooltipContent,
|
||||
} from '@/components/ui/chart'
|
||||
import { formatShortDate, formatShortTime } from '@/lib/utils'
|
||||
import { useEffect } from 'react'
|
||||
import Spinner from '../spinner'
|
||||
// for (const data of chartData) {
|
||||
// data.month = formatDateShort(data.month)
|
||||
// }
|
||||
@@ -18,26 +20,26 @@ const chartConfig = {
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function ({ chartData }: { chartData: { time: string; cpu: number }[] }) {
|
||||
export default function ({
|
||||
chartData,
|
||||
max,
|
||||
}: {
|
||||
chartData: { time: string; cpu: number }[]
|
||||
max: number
|
||||
}) {
|
||||
if (!chartData?.length) {
|
||||
return <Spinner />
|
||||
}
|
||||
|
||||
return (
|
||||
<ChartContainer config={chartConfig} className="h-full w-full absolute aspect-auto">
|
||||
<AreaChart
|
||||
accessibilityLayer
|
||||
data={chartData}
|
||||
margin={{
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 7,
|
||||
bottom: 7,
|
||||
}}
|
||||
>
|
||||
<AreaChart accessibilityLayer data={chartData}>
|
||||
<CartesianGrid vertical={false} />
|
||||
<YAxis
|
||||
domain={[0, 100]}
|
||||
domain={[0, max]}
|
||||
tickCount={5}
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
tickMargin={8}
|
||||
tickFormatter={(v) => `${v}%`}
|
||||
/>
|
||||
{/* todo: short time if first date is same day, otherwise short date */}
|
||||
|
@@ -8,6 +8,7 @@ import {
|
||||
} from '@/components/ui/chart'
|
||||
import { formatShortDate, formatShortTime } from '@/lib/utils'
|
||||
import { useMemo } from 'react'
|
||||
import Spinner from '../spinner'
|
||||
// for (const data of chartData) {
|
||||
// data.month = formatDateShort(data.month)
|
||||
// }
|
||||
@@ -37,6 +38,10 @@ export default function ({
|
||||
// return ticks
|
||||
// }, [diskSize])
|
||||
|
||||
if (!chartData.length) {
|
||||
return <Spinner />
|
||||
}
|
||||
|
||||
return (
|
||||
<ChartContainer config={chartConfig} className="h-full w-full absolute aspect-auto">
|
||||
<AreaChart
|
||||
@@ -45,14 +50,13 @@ export default function ({
|
||||
margin={{
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 7,
|
||||
bottom: 7,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
}}
|
||||
>
|
||||
<CartesianGrid vertical={false} />
|
||||
<YAxis
|
||||
domain={[0, diskSize]}
|
||||
tickCount={10}
|
||||
// ticks={ticks}
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
|
@@ -8,6 +8,7 @@ import {
|
||||
} from '@/components/ui/chart'
|
||||
import { formatShortDate, formatShortTime } from '@/lib/utils'
|
||||
import { useMemo } from 'react'
|
||||
import Spinner from '../spinner'
|
||||
|
||||
const chartConfig = {
|
||||
memUsed: {
|
||||
@@ -25,6 +26,10 @@ export default function ({
|
||||
return Math.ceil(chartData[0]?.mem)
|
||||
}, [chartData])
|
||||
|
||||
if (!chartData.length) {
|
||||
return <Spinner />
|
||||
}
|
||||
|
||||
return (
|
||||
<ChartContainer config={chartConfig} className="h-full w-full absolute aspect-auto">
|
||||
<AreaChart
|
||||
@@ -33,8 +38,8 @@ export default function ({
|
||||
margin={{
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 7,
|
||||
bottom: 7,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
}}
|
||||
>
|
||||
<CartesianGrid vertical={false} />
|
||||
|
18
site/src/components/logo.tsx
Normal file
18
site/src/components/logo.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
export function Logo({ className }: { className?: string }) {
|
||||
return (
|
||||
<svg viewBox="0 0 421.6 140.2" className={className}>
|
||||
<path d="M0 109.4V0h20.4v109.4a9.6 9.6 0 0 0 2.4 6.4 12.8 12.8 0 0 0 .7.7q3.1 3.1 7.1 3.1h58.6V0h20.2v119.6H89.2V140H30.6a33.4 33.4 0 0 1-9.6-1.3 30.7 30.7 0 0 1-2.7-1 30 30 0 0 1-7-4 26.8 26.8 0 0 1-2.7-2.3 28.1 28.1 0 0 1-5.5-8 33 33 0 0 1-.8-1.7 32.2 32.2 0 0 1-2.3-11 37.1 37.1 0 0 1 0-1.3ZM307 0h20.4v73.8l58.4-30.6q2 0 3-.8t1.5-2q.5-1.2.6-2.5l.1-2.3V17.8l20.4-10.2v28a29.5 29.5 0 0 1-3.8 14.4 34.6 34.6 0 0 1-.1.2 26 26 0 0 1-11 10.5 31.6 31.6 0 0 1-.5.3l-17.8 10.2 43.4 68.8h-23l-38.2-58.6-28 12.8q-2 2-3.5 3.8a7 7 0 0 0-1 1.5 5.3 5.3 0 0 0-.5 2.3V140H307V0ZM127.6 109.4v-89h20.2V0H214q5.8 0 11.3 2.3 5.5 2.3 9.8 6.3a30 30 0 0 1 6.5 9 34.4 34.4 0 0 1 .4.7 29.3 29.3 0 0 1 2.6 12.2 33.4 33.4 0 0 1 0 .1v12.6a23.3 23.3 0 0 1-1.2 7.4 26.1 26.1 0 0 1-.1.2q-1.3 3.8-3.4 7.1-2.1 3.3-4.9 6.1a48.8 48.8 0 0 1-3.3 3 39.5 39.5 0 0 1-2.3 1.8l2.4 2.4q7.6 4 12.7 11a28 28 0 0 1 3.7 6.7 22.5 22.5 0 0 1 1.4 7.9v12.6a29.4 29.4 0 0 1-2.4 11.8 28.6 28.6 0 0 1-.2.5 30.7 30.7 0 0 1-5.3 8 28.7 28.7 0 0 1-1.6 1.7q-4.3 4-9.7 6.3-5.4 2.3-11.2 2.3h-63.6q-5.8 0-10.9-2.3-5.1-2.3-8.9-6.3a28 28 0 0 1-5-7.3 33.7 33.7 0 0 1-1-2.4 33.3 33.3 0 0 1-2.2-10.6 38.6 38.6 0 0 1 0-1.7Zm20.2-89V112q0 6 5 7.3a11.8 11.8 0 0 0 2.8.3h63.6a9.2 9.2 0 0 0 5.3-1.7 13 13 0 0 0 1.7-1.4 11.2 11.2 0 0 0 2.1-2.7 9 9 0 0 0 1.1-4.4V96.8q0-2-1.6-3.9a11 11 0 0 0 0 0 57.9 57.9 0 0 0-2.2-2.4 70.3 70.3 0 0 0-1.4-1.5l-17.8-7.6-43.2 23v-23l56-30.4q1.6 0 3-2.3a12.4 12.4 0 0 0 .4-.6 18.3 18.3 0 0 0 .8-1.6q.7-1.8.8-3.1a5.2 5.2 0 0 0 0-.2V30.6a9.6 9.6 0 0 0-2.4-6.4 12.8 12.8 0 0 0-.7-.7q-3.1-3.1-7.1-3.1h-66.2ZM266.4 0H287v140.2h-20.6V0Z" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function Can({ className }: { className?: string }) {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" className={className}>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M9.5 4a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1M11 2.5a.5.5 0 1 1 1 0 .5.5 0 0 1-1 0m0 2a.5.5 0 1 1 1 0 .5.5 0 0 1-1 0m2-3a.5.5 0 1 1 1 0 .5.5 0 0 1-1 0m0 2a.5.5 0 1 1 1 0 .5.5 0 0 1-1 0m0 2a.5.5 0 1 1 1 0 .5.5 0 0 1-1 0m-3.02.87v.07l.01.02.01.04v6.75A1.75 1.75 0 0 1 8.25 15h-3.5A1.75 1.75 0 0 1 3 13.25V6.46l.01-.02a.5.5 0 0 1 .14-.3L5 4.3V2.5a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 .5.5v1.8l1.85 1.85a.5.5 0 0 1 .12.22M7 3H6v1h1zm.3 2H5.7l-1 1h3.6zm.95 9a.75.75 0 0 0 .75-.75V7H4v6.25c0 .41.34.75.75.75z"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
import { Moon, Sun } from 'lucide-react'
|
||||
import { MoonStarIcon, Sun } from 'lucide-react'
|
||||
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
@@ -17,7 +17,7 @@ export function ModeToggle() {
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant={'ghost'} size="icon">
|
||||
<Sun className="h-[1.2rem] w-[1.2rem] dark:opacity-0" />
|
||||
<Moon className="absolute h-[1.2rem] w-[1.2rem] opacity-0 dark:opacity-100" />
|
||||
<MoonStarIcon className="absolute h-[1.2rem] w-[1.2rem] opacity-0 dark:opacity-100" />
|
||||
<span className="sr-only">Toggle theme</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { $servers, pb } from '@/lib/stores'
|
||||
import { ContainerStatsRecord, SystemRecord, SystemStats, SystemStatsRecord } from '@/types'
|
||||
import { ContainerStatsRecord, SystemRecord, SystemStatsRecord } from '@/types'
|
||||
import { Suspense, lazy, useEffect, useState } from 'react'
|
||||
import { Card, CardHeader, CardTitle, CardDescription, CardContent } from '../ui/card'
|
||||
import { useStore } from '@nanostores/react'
|
||||
@@ -8,6 +8,7 @@ import CpuChart from '../charts/cpu-chart'
|
||||
import MemChart from '../charts/mem-chart'
|
||||
import DiskChart from '../charts/disk-chart'
|
||||
import ContainerCpuChart from '../charts/container-cpu-chart'
|
||||
import { CpuIcon } from 'lucide-react'
|
||||
|
||||
// const CpuChart = lazy(() => import('../cpu-chart'))
|
||||
|
||||
@@ -29,7 +30,9 @@ export default function ServerDetail({ name }: { name: string }) {
|
||||
const [containers, setContainers] = useState([] as ContainerStatsRecord[])
|
||||
|
||||
const [serverStats, setServerStats] = useState([] as SystemStatsRecord[])
|
||||
const [cpuChartData, setCpuChartData] = useState({} as { time: string; cpu: number }[])
|
||||
const [cpuChartData, setCpuChartData] = useState(
|
||||
{} as { max: number; data: { time: string; cpu: number }[] }
|
||||
)
|
||||
const [memChartData, setMemChartData] = useState(
|
||||
{} as { time: string; mem: number; memUsed: number }[]
|
||||
)
|
||||
@@ -40,6 +43,16 @@ export default function ServerDetail({ name }: { name: string }) {
|
||||
[] as Record<string, number | string>[]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
document.title = name
|
||||
return () => {
|
||||
setContainerCpuChartData([])
|
||||
setCpuChartData({} as { max: number; data: { time: string; cpu: number }[] })
|
||||
setMemChartData([] as { time: string; mem: number; memUsed: number }[])
|
||||
setDiskChartData([] as { time: string; disk: number; diskUsed: number }[])
|
||||
}
|
||||
}, [name])
|
||||
|
||||
// get stats
|
||||
useEffect(() => {
|
||||
if (!('name' in server)) {
|
||||
@@ -71,15 +84,14 @@ export default function ServerDetail({ name }: { name: string }) {
|
||||
memData.push({ time: created, mem: stats.mem, memUsed: stats.memUsed })
|
||||
diskData.push({ time: created, disk: stats.disk, diskUsed: stats.diskUsed })
|
||||
}
|
||||
setCpuChartData(cpuData.reverse())
|
||||
setCpuChartData({
|
||||
max: Math.ceil(Math.max(...cpuData.map((d) => d.cpu))),
|
||||
data: cpuData.reverse(),
|
||||
})
|
||||
setMemChartData(memData.reverse())
|
||||
setDiskChartData(diskData.reverse())
|
||||
}, [serverStats])
|
||||
|
||||
useEffect(() => {
|
||||
document.title = name
|
||||
}, [name])
|
||||
|
||||
useEffect(() => {
|
||||
if ($servers.get().length === 0) {
|
||||
console.log('skipping')
|
||||
@@ -117,28 +129,44 @@ export default function ServerDetail({ name }: { name: string }) {
|
||||
for (let { created, stats } of containers) {
|
||||
let obj = { time: created } as Record<string, number | string>
|
||||
for (let { name, cpu } of stats) {
|
||||
obj[name] = cpu * 10
|
||||
obj[name] = cpu
|
||||
}
|
||||
containerCpuData.push(obj)
|
||||
}
|
||||
setContainerCpuChartData(containerCpuData.reverse())
|
||||
console.log('containerCpuData', containerCpuData)
|
||||
}, [containers])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="grid grid-cols-2 gap-6 mb-10">
|
||||
<Card className="pb-2 col-span-2">
|
||||
<div className="grid gap-6 mb-10">
|
||||
<Card className="pb-2">
|
||||
<CardHeader>
|
||||
<CardTitle>CPU Usage</CardTitle>
|
||||
<CardTitle className="flex gap-2 justify-between">
|
||||
<span>CPU Usage</span>
|
||||
<CpuIcon className="opacity-70" />
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Average usage of the one minute preceding the recorded time
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className={'pl-1 w-[calc(100%-2em)] h-52 relative'}>
|
||||
{/* <Suspense fallback={<Spinner />}> */}
|
||||
<CpuChart chartData={cpuChartData} />
|
||||
{/* </Suspense> */}
|
||||
<Suspense fallback={<Spinner />}>
|
||||
<CpuChart chartData={cpuChartData.data} max={cpuChartData.max} />
|
||||
</Suspense>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card className="pb-2">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex gap-2 justify-between">
|
||||
<span>Docker CPU Usage</span>
|
||||
<CpuIcon className="opacity-70" />
|
||||
</CardTitle>{' '}
|
||||
<CardDescription>CPU usage of docker containers</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className={'pl-1 w-[calc(100%-2em)] h-52 relative'}>
|
||||
<Suspense fallback={<Spinner />}>
|
||||
<ContainerCpuChart chartData={containerCpuChartData} max={cpuChartData.max} />
|
||||
</Suspense>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card className="pb-2">
|
||||
@@ -163,21 +191,6 @@ export default function ServerDetail({ name }: { name: string }) {
|
||||
{/* </Suspense> */}
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card className="pb-2 col-span-2">
|
||||
<CardHeader>
|
||||
<CardTitle>Container CPU Usage</CardTitle>
|
||||
<CardDescription>
|
||||
Average usage of the one minute preceding the recorded time
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className={'pl-1 w-[calc(100%-2em)] h-64 relative'}>
|
||||
{/* <Suspense fallback={<Spinner />}> */}
|
||||
{containerCpuChartData.length > 0 && (
|
||||
<ContainerCpuChart chartData={containerCpuChartData} />
|
||||
)}
|
||||
{/* </Suspense> */}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
|
@@ -106,7 +106,7 @@ export default function () {
|
||||
// size: 70,
|
||||
accessorKey: 'name',
|
||||
cell: (info) => (
|
||||
<span className="flex gap-1 items-center text-base">
|
||||
<span className="flex gap-0.5 items-center text-base">
|
||||
<span
|
||||
className={cn(
|
||||
'w-2.5 h-2.5 block left-0 rounded-full',
|
||||
@@ -116,7 +116,7 @@ export default function () {
|
||||
></span>
|
||||
<Button
|
||||
variant={'ghost'}
|
||||
className="text-foreground/80 h-7 px-2 gap-1.5"
|
||||
className="text-foreground/80 h-7 px-1.5 gap-1.5"
|
||||
onClick={() => copyToClipboard(info.getValue() as string)}
|
||||
>
|
||||
{info.getValue() as string}
|
||||
|
@@ -42,7 +42,7 @@
|
||||
--accent-foreground: 0 0% 98.04%;
|
||||
--destructive: 0 56.48% 42.35%;
|
||||
--destructive-foreground: 0 0% 98.04%;
|
||||
--border: 240 2.86% 14%;
|
||||
--border: 240 2.86% 12%;
|
||||
--input: 240 3.7% 15.88%;
|
||||
--ring: 240 4.88% 86%;
|
||||
--radius: 0.8rem;
|
||||
|
@@ -3,13 +3,20 @@ import React, { Suspense, lazy, useEffect } from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import Home from './components/routes/home.tsx'
|
||||
import { ThemeProvider } from './components/theme-provider.tsx'
|
||||
import { $authenticated, $router } from './lib/stores.ts'
|
||||
import { $authenticated, $router, navigate } from './lib/stores.ts'
|
||||
import { ModeToggle } from './components/mode-toggle.tsx'
|
||||
import { cn, updateServerList } from './lib/utils.ts'
|
||||
import { buttonVariants } from './components/ui/button.tsx'
|
||||
import { Github } from 'lucide-react'
|
||||
import { useStore } from '@nanostores/react'
|
||||
import { Toaster } from './components/ui/toaster.tsx'
|
||||
import { Can, Logo } from './components/logo.tsx'
|
||||
import {
|
||||
TooltipProvider,
|
||||
Tooltip,
|
||||
TooltipTrigger,
|
||||
TooltipContent,
|
||||
} from '@/components/ui/tooltip.tsx'
|
||||
|
||||
const ServerDetail = lazy(() => import('./components/routes/server.tsx'))
|
||||
const CommandPalette = lazy(() => import('./components/command-palette.tsx'))
|
||||
@@ -42,31 +49,56 @@ const Layout = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container mt-7 mb-14 relative">
|
||||
<div className="flex mb-4">
|
||||
{/* <a
|
||||
className={cn('', buttonVariants({ variant: 'ghost', size: 'icon' }))}
|
||||
href="/"
|
||||
title={'All servers'}
|
||||
>
|
||||
<HomeIcon className="h-[1.2rem] w-[1.2rem]" />
|
||||
</a> */}
|
||||
<div className={'flex gap-1 ml-auto'}>
|
||||
<a
|
||||
title={'Github'}
|
||||
href={'https://github.com/henrygd'}
|
||||
className={cn('', buttonVariants({ variant: 'ghost', size: 'icon' }))}
|
||||
>
|
||||
<Github className="h-[1.2rem] w-[1.2rem]" />
|
||||
</a>
|
||||
<ModeToggle />
|
||||
<>
|
||||
<div className="container">
|
||||
<div className="flex items-center py-3.5 bg-card px-6 border bt-0 rounded-md my-5">
|
||||
<TooltipProvider delayDuration={300}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<a
|
||||
href="/"
|
||||
aria-label="Home"
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
navigate('/')
|
||||
}}
|
||||
>
|
||||
<Logo className="h-5 fill-foreground" />
|
||||
</a>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Home</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
<div className={'flex gap-1 ml-auto'}>
|
||||
<TooltipProvider delayDuration={300}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<a
|
||||
title={'Github'}
|
||||
aria-label="Github repo"
|
||||
href={'https://github.com/henrygd'}
|
||||
className={cn('', buttonVariants({ variant: 'ghost', size: 'icon' }))}
|
||||
>
|
||||
<Github className="h-[1.2rem] w-[1.2rem]" />
|
||||
</a>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Github Repository</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
<ModeToggle />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<App />
|
||||
<CommandPalette />
|
||||
<Toaster />
|
||||
</div>
|
||||
<div className="container mb-14 relative">
|
||||
<App />
|
||||
<CommandPalette />
|
||||
<Toaster />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user