mirror of
https://github.com/fankes/beszel.git
synced 2025-10-19 09:49:28 +08:00
site updates
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<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" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Home</title>
|
<title>Home</title>
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
<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) {
|
function copyDockerCompose(port: string) {
|
||||||
copyToClipboard(`services:
|
copyToClipboard(`services:
|
||||||
agent:
|
agent:
|
||||||
image: 'henrygd/monitor-agent'
|
image: 'henrygd/ubik-agent'
|
||||||
container_name: 'monitor-agent'
|
container_name: 'ubik-agent'
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- '${port}:45876'
|
- '${port}:45876'
|
||||||
|
@@ -4,19 +4,21 @@ import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from 'recharts'
|
|||||||
import {
|
import {
|
||||||
ChartConfig,
|
ChartConfig,
|
||||||
ChartContainer,
|
ChartContainer,
|
||||||
ChartLegend,
|
|
||||||
ChartLegendContent,
|
|
||||||
ChartTooltip,
|
ChartTooltip,
|
||||||
ChartTooltipContent,
|
ChartTooltipContent,
|
||||||
} from '@/components/ui/chart'
|
} from '@/components/ui/chart'
|
||||||
import { useMemo, useState } from 'react'
|
import { useMemo } from 'react'
|
||||||
import { formatShortDate, formatShortTime } from '@/lib/utils'
|
import { formatShortDate, formatShortTime } from '@/lib/utils'
|
||||||
|
import Spinner from '../spinner'
|
||||||
|
|
||||||
export default function ({ chartData }: { chartData: Record<string, number | string>[] }) {
|
export default function ({
|
||||||
const [containerNames, setContainerNames] = useState([] as string[])
|
chartData,
|
||||||
|
max,
|
||||||
|
}: {
|
||||||
|
chartData: Record<string, number | string>[]
|
||||||
|
max: number
|
||||||
|
}) {
|
||||||
const chartConfig = useMemo(() => {
|
const chartConfig = useMemo(() => {
|
||||||
console.log('chartData', chartData)
|
|
||||||
let config = {} as Record<
|
let config = {} as Record<
|
||||||
string,
|
string,
|
||||||
{
|
{
|
||||||
@@ -24,13 +26,21 @@ export default function ({ chartData }: { chartData: Record<string, number | str
|
|||||||
color: string
|
color: string
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
const lastRecord = chartData.at(-1)
|
const totalUsage = {} as Record<string, number>
|
||||||
// @ts-ignore
|
for (let stats of chartData) {
|
||||||
let allKeys = new Set(Object.keys(lastRecord))
|
for (let key in stats) {
|
||||||
allKeys.delete('time')
|
if (key === 'time') {
|
||||||
const keys = Array.from(allKeys)
|
continue
|
||||||
keys.sort((a, b) => (lastRecord![b] as number) - (lastRecord![a] as number))
|
}
|
||||||
setContainerNames(keys)
|
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
|
const length = keys.length
|
||||||
for (let i = 0; i < length; i++) {
|
for (let i = 0; i < length; i++) {
|
||||||
const key = keys[i]
|
const key = keys[i]
|
||||||
@@ -40,12 +50,11 @@ export default function ({ chartData }: { chartData: Record<string, number | str
|
|||||||
color: `hsl(${hue}, 60%, 60%)`,
|
color: `hsl(${hue}, 60%, 60%)`,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log('config', config)
|
|
||||||
return config satisfies ChartConfig
|
return config satisfies ChartConfig
|
||||||
}, [chartData])
|
}, [chartData])
|
||||||
|
|
||||||
if (!containerNames.length) {
|
if (!chartData.length) {
|
||||||
return null
|
return <Spinner />
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -53,20 +62,23 @@ export default function ({ chartData }: { chartData: Record<string, number | str
|
|||||||
<AreaChart
|
<AreaChart
|
||||||
accessibilityLayer
|
accessibilityLayer
|
||||||
data={chartData}
|
data={chartData}
|
||||||
margin={{
|
|
||||||
left: 12,
|
|
||||||
right: 12,
|
|
||||||
top: 12,
|
|
||||||
}}
|
|
||||||
// reverseStackOrder={true}
|
// reverseStackOrder={true}
|
||||||
>
|
>
|
||||||
<CartesianGrid vertical={false} />
|
<CartesianGrid vertical={false} />
|
||||||
{/* <YAxis domain={[0, 250]} tickCount={5} tickLine={false} axisLine={false} tickMargin={8} /> */}
|
<YAxis
|
||||||
<XAxis
|
domain={[0, max]}
|
||||||
dataKey="time"
|
tickCount={5}
|
||||||
tickLine={false}
|
tickLine={false}
|
||||||
axisLine={false}
|
axisLine={false}
|
||||||
|
tickFormatter={(v) => `${v}%`}
|
||||||
|
/>
|
||||||
|
<XAxis
|
||||||
|
dataKey="time"
|
||||||
|
tickLine={true}
|
||||||
|
axisLine={false}
|
||||||
tickMargin={8}
|
tickMargin={8}
|
||||||
|
minTickGap={30}
|
||||||
tickFormatter={formatShortTime}
|
tickFormatter={formatShortTime}
|
||||||
/>
|
/>
|
||||||
<ChartTooltip
|
<ChartTooltip
|
||||||
@@ -76,19 +88,13 @@ export default function ({ chartData }: { chartData: Record<string, number | str
|
|||||||
// console.log('itemSorter', item)
|
// console.log('itemSorter', item)
|
||||||
// return -item.value
|
// return -item.value
|
||||||
// }}
|
// }}
|
||||||
content={
|
content={<ChartTooltipContent indicator="line" />}
|
||||||
<ChartTooltipContent
|
|
||||||
// itemSorter={(item) => {
|
|
||||||
// console.log('itemSorter', item)
|
|
||||||
// return -item.value
|
|
||||||
// }}
|
|
||||||
indicator="line"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
{containerNames.map((key) => (
|
{Object.keys(chartConfig).map((key) => (
|
||||||
<Area
|
<Area
|
||||||
key={key}
|
key={key}
|
||||||
|
// isAnimationActive={false}
|
||||||
|
animateNewValues={false}
|
||||||
dataKey={key}
|
dataKey={key}
|
||||||
type="natural"
|
type="natural"
|
||||||
fill={chartConfig[key].color}
|
fill={chartConfig[key].color}
|
||||||
@@ -97,15 +103,6 @@ export default function ({ chartData }: { chartData: Record<string, number | str
|
|||||||
stackId="a"
|
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>
|
</AreaChart>
|
||||||
</ChartContainer>
|
</ChartContainer>
|
||||||
)
|
)
|
||||||
|
@@ -7,6 +7,8 @@ import {
|
|||||||
ChartTooltipContent,
|
ChartTooltipContent,
|
||||||
} from '@/components/ui/chart'
|
} from '@/components/ui/chart'
|
||||||
import { formatShortDate, formatShortTime } from '@/lib/utils'
|
import { formatShortDate, formatShortTime } from '@/lib/utils'
|
||||||
|
import { useEffect } from 'react'
|
||||||
|
import Spinner from '../spinner'
|
||||||
// for (const data of chartData) {
|
// for (const data of chartData) {
|
||||||
// data.month = formatDateShort(data.month)
|
// data.month = formatDateShort(data.month)
|
||||||
// }
|
// }
|
||||||
@@ -18,26 +20,26 @@ const chartConfig = {
|
|||||||
},
|
},
|
||||||
} satisfies 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 (
|
return (
|
||||||
<ChartContainer config={chartConfig} className="h-full w-full absolute aspect-auto">
|
<ChartContainer config={chartConfig} className="h-full w-full absolute aspect-auto">
|
||||||
<AreaChart
|
<AreaChart accessibilityLayer data={chartData}>
|
||||||
accessibilityLayer
|
|
||||||
data={chartData}
|
|
||||||
margin={{
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
top: 7,
|
|
||||||
bottom: 7,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<CartesianGrid vertical={false} />
|
<CartesianGrid vertical={false} />
|
||||||
<YAxis
|
<YAxis
|
||||||
domain={[0, 100]}
|
domain={[0, max]}
|
||||||
tickCount={5}
|
tickCount={5}
|
||||||
tickLine={false}
|
tickLine={false}
|
||||||
axisLine={false}
|
axisLine={false}
|
||||||
tickMargin={8}
|
|
||||||
tickFormatter={(v) => `${v}%`}
|
tickFormatter={(v) => `${v}%`}
|
||||||
/>
|
/>
|
||||||
{/* todo: short time if first date is same day, otherwise short date */}
|
{/* todo: short time if first date is same day, otherwise short date */}
|
||||||
|
@@ -8,6 +8,7 @@ import {
|
|||||||
} from '@/components/ui/chart'
|
} from '@/components/ui/chart'
|
||||||
import { formatShortDate, formatShortTime } from '@/lib/utils'
|
import { formatShortDate, formatShortTime } from '@/lib/utils'
|
||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
|
import Spinner from '../spinner'
|
||||||
// for (const data of chartData) {
|
// for (const data of chartData) {
|
||||||
// data.month = formatDateShort(data.month)
|
// data.month = formatDateShort(data.month)
|
||||||
// }
|
// }
|
||||||
@@ -37,6 +38,10 @@ export default function ({
|
|||||||
// return ticks
|
// return ticks
|
||||||
// }, [diskSize])
|
// }, [diskSize])
|
||||||
|
|
||||||
|
if (!chartData.length) {
|
||||||
|
return <Spinner />
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ChartContainer config={chartConfig} className="h-full w-full absolute aspect-auto">
|
<ChartContainer config={chartConfig} className="h-full w-full absolute aspect-auto">
|
||||||
<AreaChart
|
<AreaChart
|
||||||
@@ -45,14 +50,13 @@ export default function ({
|
|||||||
margin={{
|
margin={{
|
||||||
left: 0,
|
left: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
top: 7,
|
top: 0,
|
||||||
bottom: 7,
|
bottom: 0,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CartesianGrid vertical={false} />
|
<CartesianGrid vertical={false} />
|
||||||
<YAxis
|
<YAxis
|
||||||
domain={[0, diskSize]}
|
domain={[0, diskSize]}
|
||||||
tickCount={10}
|
|
||||||
// ticks={ticks}
|
// ticks={ticks}
|
||||||
tickLine={false}
|
tickLine={false}
|
||||||
axisLine={false}
|
axisLine={false}
|
||||||
|
@@ -8,6 +8,7 @@ import {
|
|||||||
} from '@/components/ui/chart'
|
} from '@/components/ui/chart'
|
||||||
import { formatShortDate, formatShortTime } from '@/lib/utils'
|
import { formatShortDate, formatShortTime } from '@/lib/utils'
|
||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
|
import Spinner from '../spinner'
|
||||||
|
|
||||||
const chartConfig = {
|
const chartConfig = {
|
||||||
memUsed: {
|
memUsed: {
|
||||||
@@ -25,6 +26,10 @@ export default function ({
|
|||||||
return Math.ceil(chartData[0]?.mem)
|
return Math.ceil(chartData[0]?.mem)
|
||||||
}, [chartData])
|
}, [chartData])
|
||||||
|
|
||||||
|
if (!chartData.length) {
|
||||||
|
return <Spinner />
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ChartContainer config={chartConfig} className="h-full w-full absolute aspect-auto">
|
<ChartContainer config={chartConfig} className="h-full w-full absolute aspect-auto">
|
||||||
<AreaChart
|
<AreaChart
|
||||||
@@ -33,8 +38,8 @@ export default function ({
|
|||||||
margin={{
|
margin={{
|
||||||
left: 0,
|
left: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
top: 7,
|
top: 0,
|
||||||
bottom: 7,
|
bottom: 0,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CartesianGrid vertical={false} />
|
<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 { Button } from '@/components/ui/button'
|
||||||
import {
|
import {
|
||||||
@@ -17,7 +17,7 @@ export function ModeToggle() {
|
|||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button variant={'ghost'} size="icon">
|
<Button variant={'ghost'} size="icon">
|
||||||
<Sun className="h-[1.2rem] w-[1.2rem] dark:opacity-0" />
|
<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>
|
<span className="sr-only">Toggle theme</span>
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { $servers, pb } from '@/lib/stores'
|
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 { Suspense, lazy, useEffect, useState } from 'react'
|
||||||
import { Card, CardHeader, CardTitle, CardDescription, CardContent } from '../ui/card'
|
import { Card, CardHeader, CardTitle, CardDescription, CardContent } from '../ui/card'
|
||||||
import { useStore } from '@nanostores/react'
|
import { useStore } from '@nanostores/react'
|
||||||
@@ -8,6 +8,7 @@ import CpuChart from '../charts/cpu-chart'
|
|||||||
import MemChart from '../charts/mem-chart'
|
import MemChart from '../charts/mem-chart'
|
||||||
import DiskChart from '../charts/disk-chart'
|
import DiskChart from '../charts/disk-chart'
|
||||||
import ContainerCpuChart from '../charts/container-cpu-chart'
|
import ContainerCpuChart from '../charts/container-cpu-chart'
|
||||||
|
import { CpuIcon } from 'lucide-react'
|
||||||
|
|
||||||
// const CpuChart = lazy(() => import('../cpu-chart'))
|
// const CpuChart = lazy(() => import('../cpu-chart'))
|
||||||
|
|
||||||
@@ -29,7 +30,9 @@ export default function ServerDetail({ name }: { name: string }) {
|
|||||||
const [containers, setContainers] = useState([] as ContainerStatsRecord[])
|
const [containers, setContainers] = useState([] as ContainerStatsRecord[])
|
||||||
|
|
||||||
const [serverStats, setServerStats] = useState([] as SystemStatsRecord[])
|
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(
|
const [memChartData, setMemChartData] = useState(
|
||||||
{} as { time: string; mem: number; memUsed: number }[]
|
{} as { time: string; mem: number; memUsed: number }[]
|
||||||
)
|
)
|
||||||
@@ -40,6 +43,16 @@ export default function ServerDetail({ name }: { name: string }) {
|
|||||||
[] as Record<string, number | 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
|
// get stats
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!('name' in server)) {
|
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 })
|
memData.push({ time: created, mem: stats.mem, memUsed: stats.memUsed })
|
||||||
diskData.push({ time: created, disk: stats.disk, diskUsed: stats.diskUsed })
|
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())
|
setMemChartData(memData.reverse())
|
||||||
setDiskChartData(diskData.reverse())
|
setDiskChartData(diskData.reverse())
|
||||||
}, [serverStats])
|
}, [serverStats])
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
document.title = name
|
|
||||||
}, [name])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if ($servers.get().length === 0) {
|
if ($servers.get().length === 0) {
|
||||||
console.log('skipping')
|
console.log('skipping')
|
||||||
@@ -117,28 +129,44 @@ export default function ServerDetail({ name }: { name: string }) {
|
|||||||
for (let { created, stats } of containers) {
|
for (let { created, stats } of containers) {
|
||||||
let obj = { time: created } as Record<string, number | string>
|
let obj = { time: created } as Record<string, number | string>
|
||||||
for (let { name, cpu } of stats) {
|
for (let { name, cpu } of stats) {
|
||||||
obj[name] = cpu * 10
|
obj[name] = cpu
|
||||||
}
|
}
|
||||||
containerCpuData.push(obj)
|
containerCpuData.push(obj)
|
||||||
}
|
}
|
||||||
setContainerCpuChartData(containerCpuData.reverse())
|
setContainerCpuChartData(containerCpuData.reverse())
|
||||||
console.log('containerCpuData', containerCpuData)
|
|
||||||
}, [containers])
|
}, [containers])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="grid grid-cols-2 gap-6 mb-10">
|
<div className="grid gap-6 mb-10">
|
||||||
<Card className="pb-2 col-span-2">
|
<Card className="pb-2">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>CPU Usage</CardTitle>
|
<CardTitle className="flex gap-2 justify-between">
|
||||||
|
<span>CPU Usage</span>
|
||||||
|
<CpuIcon className="opacity-70" />
|
||||||
|
</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
Average usage of the one minute preceding the recorded time
|
Average usage of the one minute preceding the recorded time
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className={'pl-1 w-[calc(100%-2em)] h-52 relative'}>
|
<CardContent className={'pl-1 w-[calc(100%-2em)] h-52 relative'}>
|
||||||
{/* <Suspense fallback={<Spinner />}> */}
|
<Suspense fallback={<Spinner />}>
|
||||||
<CpuChart chartData={cpuChartData} />
|
<CpuChart chartData={cpuChartData.data} max={cpuChartData.max} />
|
||||||
{/* </Suspense> */}
|
</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>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
<Card className="pb-2">
|
<Card className="pb-2">
|
||||||
@@ -163,21 +191,6 @@ export default function ServerDetail({ name }: { name: string }) {
|
|||||||
{/* </Suspense> */}
|
{/* </Suspense> */}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</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>
|
</div>
|
||||||
|
|
||||||
<Card>
|
<Card>
|
||||||
|
@@ -106,7 +106,7 @@ export default function () {
|
|||||||
// size: 70,
|
// size: 70,
|
||||||
accessorKey: 'name',
|
accessorKey: 'name',
|
||||||
cell: (info) => (
|
cell: (info) => (
|
||||||
<span className="flex gap-1 items-center text-base">
|
<span className="flex gap-0.5 items-center text-base">
|
||||||
<span
|
<span
|
||||||
className={cn(
|
className={cn(
|
||||||
'w-2.5 h-2.5 block left-0 rounded-full',
|
'w-2.5 h-2.5 block left-0 rounded-full',
|
||||||
@@ -116,7 +116,7 @@ export default function () {
|
|||||||
></span>
|
></span>
|
||||||
<Button
|
<Button
|
||||||
variant={'ghost'}
|
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)}
|
onClick={() => copyToClipboard(info.getValue() as string)}
|
||||||
>
|
>
|
||||||
{info.getValue() as string}
|
{info.getValue() as string}
|
||||||
|
@@ -42,7 +42,7 @@
|
|||||||
--accent-foreground: 0 0% 98.04%;
|
--accent-foreground: 0 0% 98.04%;
|
||||||
--destructive: 0 56.48% 42.35%;
|
--destructive: 0 56.48% 42.35%;
|
||||||
--destructive-foreground: 0 0% 98.04%;
|
--destructive-foreground: 0 0% 98.04%;
|
||||||
--border: 240 2.86% 14%;
|
--border: 240 2.86% 12%;
|
||||||
--input: 240 3.7% 15.88%;
|
--input: 240 3.7% 15.88%;
|
||||||
--ring: 240 4.88% 86%;
|
--ring: 240 4.88% 86%;
|
||||||
--radius: 0.8rem;
|
--radius: 0.8rem;
|
||||||
|
@@ -3,13 +3,20 @@ import React, { Suspense, lazy, useEffect } from 'react'
|
|||||||
import ReactDOM from 'react-dom/client'
|
import ReactDOM from 'react-dom/client'
|
||||||
import Home from './components/routes/home.tsx'
|
import Home from './components/routes/home.tsx'
|
||||||
import { ThemeProvider } from './components/theme-provider.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 { ModeToggle } from './components/mode-toggle.tsx'
|
||||||
import { cn, updateServerList } from './lib/utils.ts'
|
import { cn, updateServerList } from './lib/utils.ts'
|
||||||
import { buttonVariants } from './components/ui/button.tsx'
|
import { buttonVariants } from './components/ui/button.tsx'
|
||||||
import { Github } from 'lucide-react'
|
import { Github } from 'lucide-react'
|
||||||
import { useStore } from '@nanostores/react'
|
import { useStore } from '@nanostores/react'
|
||||||
import { Toaster } from './components/ui/toaster.tsx'
|
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 ServerDetail = lazy(() => import('./components/routes/server.tsx'))
|
||||||
const CommandPalette = lazy(() => import('./components/command-palette.tsx'))
|
const CommandPalette = lazy(() => import('./components/command-palette.tsx'))
|
||||||
@@ -42,31 +49,56 @@ const Layout = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container mt-7 mb-14 relative">
|
<>
|
||||||
<div className="flex mb-4">
|
<div className="container">
|
||||||
{/* <a
|
<div className="flex items-center py-3.5 bg-card px-6 border bt-0 rounded-md my-5">
|
||||||
className={cn('', buttonVariants({ variant: 'ghost', size: 'icon' }))}
|
<TooltipProvider delayDuration={300}>
|
||||||
href="/"
|
<Tooltip>
|
||||||
title={'All servers'}
|
<TooltipTrigger asChild>
|
||||||
>
|
<a
|
||||||
<HomeIcon className="h-[1.2rem] w-[1.2rem]" />
|
href="/"
|
||||||
</a> */}
|
aria-label="Home"
|
||||||
<div className={'flex gap-1 ml-auto'}>
|
onClick={(e) => {
|
||||||
<a
|
e.preventDefault()
|
||||||
title={'Github'}
|
navigate('/')
|
||||||
href={'https://github.com/henrygd'}
|
}}
|
||||||
className={cn('', buttonVariants({ variant: 'ghost', size: 'icon' }))}
|
>
|
||||||
>
|
<Logo className="h-5 fill-foreground" />
|
||||||
<Github className="h-[1.2rem] w-[1.2rem]" />
|
</a>
|
||||||
</a>
|
</TooltipTrigger>
|
||||||
<ModeToggle />
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="container mb-14 relative">
|
||||||
<App />
|
<App />
|
||||||
<CommandPalette />
|
<CommandPalette />
|
||||||
<Toaster />
|
<Toaster />
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user