diff --git a/ee/tabby-ui/app/(dashboard)/cluster/components/cluster.tsx b/ee/tabby-ui/app/(dashboard)/cluster/components/cluster.tsx
deleted file mode 100644
index d43994a4f809..000000000000
--- a/ee/tabby-ui/app/(dashboard)/cluster/components/cluster.tsx
+++ /dev/null
@@ -1,117 +0,0 @@
-'use client'
-
-import { noop } from 'lodash-es'
-import { useQuery } from 'urql'
-
-import { graphql } from '@/lib/gql/generates'
-import { WorkerKind } from '@/lib/gql/generates/graphql'
-import { useHealth } from '@/lib/hooks/use-health'
-import { useWorkers } from '@/lib/hooks/use-workers'
-import { useMutation } from '@/lib/tabby/gql'
-import { Button } from '@/components/ui/button'
-import { IconRotate } from '@/components/ui/icons'
-import { Input } from '@/components/ui/input'
-import { Separator } from '@/components/ui/separator'
-import { CopyButton } from '@/components/copy-button'
-
-import WorkerCard from './worker-card'
-
-const getRegistrationTokenDocument = graphql(/* GraphQL */ `
- query GetRegistrationToken {
- registrationToken
- }
-`)
-
-const resetRegistrationTokenDocument = graphql(/* GraphQL */ `
- mutation ResetRegistrationToken {
- resetRegistrationToken
- }
-`)
-
-function toBadgeString(str: string) {
- return encodeURIComponent(str.replaceAll('-', '--'))
-}
-
-export default function Workers() {
- const { data: healthInfo } = useHealth()
- const workers = useWorkers()
- const [{ data: registrationTokenRes }, reexecuteQuery] = useQuery({
- query: getRegistrationTokenDocument
- })
-
- const resetRegistrationToken = useMutation(resetRegistrationTokenDocument, {
- onCompleted() {
- reexecuteQuery()
- }
- })
-
- if (!healthInfo) return
-
- return (
-
-
- Congratulations, your tabby instance
- is up!
-
-
-
-
-
-
-
- {!!registrationTokenRes?.registrationToken && (
-
- Registration token:
-
-
-
-
- )}
-
-
- {!!workers?.[WorkerKind.Completion] && (
- <>
- {workers[WorkerKind.Completion].map((worker, i) => {
- return
- })}
- >
- )}
- {!!workers?.[WorkerKind.Chat] && (
- <>
- {workers[WorkerKind.Chat].map((worker, i) => {
- return
- })}
- >
- )}
-
-
-
- )
-}
diff --git a/ee/tabby-ui/app/(dashboard)/components/sidebar.tsx b/ee/tabby-ui/app/(dashboard)/components/sidebar.tsx
index cd9cd3d66d30..2d5ba85f256c 100644
--- a/ee/tabby-ui/app/(dashboard)/components/sidebar.tsx
+++ b/ee/tabby-ui/app/(dashboard)/components/sidebar.tsx
@@ -65,7 +65,7 @@ export default function Sidebar({ children, className }: SidebarProps) {
>
}
>
- Cluster
+ System
Jobs
Reports
Activities
diff --git a/ee/tabby-ui/app/(dashboard)/system/components/cluster.tsx b/ee/tabby-ui/app/(dashboard)/system/components/cluster.tsx
new file mode 100644
index 000000000000..9c77ea8d3121
--- /dev/null
+++ b/ee/tabby-ui/app/(dashboard)/system/components/cluster.tsx
@@ -0,0 +1,303 @@
+'use client'
+
+import bytes from 'bytes'
+import { noop, sum } from 'lodash-es'
+import { useTheme } from 'next-themes'
+import { Cell, Label, Pie, PieChart, ResponsiveContainer } from 'recharts'
+import { useQuery } from 'urql'
+
+import { graphql } from '@/lib/gql/generates'
+import {
+ DiskUsage,
+ DiskUsageStats,
+ WorkerKind
+} from '@/lib/gql/generates/graphql'
+import { useHealth } from '@/lib/hooks/use-health'
+import { useWorkers } from '@/lib/hooks/use-workers'
+import { useMutation } from '@/lib/tabby/gql'
+import { Button } from '@/components/ui/button'
+import { IconRotate } from '@/components/ui/icons'
+import { Input } from '@/components/ui/input'
+import { Separator } from '@/components/ui/separator'
+import { CopyButton } from '@/components/copy-button'
+import LoadingWrapper from '@/components/loading-wrapper'
+
+import WorkerCard from './worker-card'
+
+const getRegistrationTokenDocument = graphql(/* GraphQL */ `
+ query GetRegistrationToken {
+ registrationToken
+ }
+`)
+
+const resetRegistrationTokenDocument = graphql(/* GraphQL */ `
+ mutation ResetRegistrationToken {
+ resetRegistrationToken
+ }
+`)
+
+function toBadgeString(str: string) {
+ return encodeURIComponent(str.replaceAll('-', '--'))
+}
+
+export default function Workers() {
+ const { data: healthInfo } = useHealth()
+ const workers = useWorkers()
+ const [{ data: registrationTokenRes }, reexecuteQuery] = useQuery({
+ query: getRegistrationTokenDocument
+ })
+
+ const resetRegistrationToken = useMutation(resetRegistrationTokenDocument, {
+ onCompleted() {
+ reexecuteQuery()
+ }
+ })
+
+ if (!healthInfo) return
+
+ return (
+
+
+ Congratulations, your tabby instance
+ is up!
+
+
+
+
+
+
+
+ {!!registrationTokenRes?.registrationToken && (
+
+ Registration token:
+
+
+
+
+ )}
+
+
+ {!!workers?.[WorkerKind.Completion] && (
+ <>
+ {workers[WorkerKind.Completion].map((worker, i) => {
+ return
+ })}
+ >
+ )}
+ {!!workers?.[WorkerKind.Chat] && (
+ <>
+ {workers[WorkerKind.Chat].map((worker, i) => {
+ return
+ })}
+ >
+ )}
+
+
+
+
+
+
+ )
+}
+
+export const getDiskUsageStats = graphql(/* GraphQL */ `
+ query GetDiskUsageStats {
+ diskUsageStats {
+ events {
+ filePaths
+ size
+ }
+ indexedRepositories {
+ filePaths
+ size
+ }
+ database {
+ filePaths
+ size
+ }
+ models {
+ filePaths
+ size
+ }
+ }
+ }
+`)
+
+type UsageItem = {
+ label: string
+ key: keyof DiskUsageStats
+ color: string
+}
+
+type UsageItemWithSize = UsageItem & { size: number }
+
+const usageList: UsageItem[] = [
+ {
+ label: 'Model',
+ key: 'models',
+ color: '#0088FE'
+ },
+ {
+ label: 'Indexing',
+ key: 'indexedRepositories',
+ color: '#00C49F'
+ },
+ {
+ label: 'Event Logs',
+ key: 'events',
+ color: '#FF8042'
+ },
+ {
+ label: 'Other',
+ key: 'database',
+ color: '#FFBB28'
+ }
+]
+
+function Usage() {
+ const [{ data, fetching }] = useQuery({
+ query: getDiskUsageStats
+ })
+
+ let usageData: UsageItemWithSize[] = []
+ let totalUsage: number = 0
+ if (data) {
+ usageData = usageList
+ .map(usage => {
+ if (!data.diskUsageStats[usage.key]) return null
+ const diskUsage = data.diskUsageStats[usage.key] as DiskUsage
+ return {
+ ...usage,
+ size: diskUsage.size
+ }
+ })
+ .filter(usage => usage) as UsageItemWithSize[]
+ totalUsage = sum(usageData.map(data => data.size))
+ }
+
+ return (
+ >}>
+ <>
+
+
Disk Usage
+
+ Storage utilization by Type
+
+
+
+
+
+
+ {usageData.map(entry => (
+ |
+ ))}
+ }
+ position="center"
+ />
+
+
+
+
+
+ {usageData.map(usage => (
+
+
+
{toBytes(usage!.size)}
+
+ ))}
+
+
+ >
+
+ )
+}
+
+function CustomLabel({
+ viewBox,
+ totalUsage
+}: {
+ viewBox?: {
+ cx: number
+ cy: number
+ }
+ totalUsage: number
+}) {
+ const { theme } = useTheme()
+ if (!viewBox) return
+ const { cx, cy } = viewBox
+ return (
+
+
+ Total Usage
+
+
+ {toBytes(totalUsage)}
+
+
+ )
+}
+
+function toBytes(value: number) {
+ return bytes(value * 1024, { unitSeparator: ' ' })
+}
diff --git a/ee/tabby-ui/app/(dashboard)/cluster/components/worker-card.tsx b/ee/tabby-ui/app/(dashboard)/system/components/worker-card.tsx
similarity index 100%
rename from ee/tabby-ui/app/(dashboard)/cluster/components/worker-card.tsx
rename to ee/tabby-ui/app/(dashboard)/system/components/worker-card.tsx
diff --git a/ee/tabby-ui/app/(dashboard)/cluster/page.tsx b/ee/tabby-ui/app/(dashboard)/system/page.tsx
similarity index 90%
rename from ee/tabby-ui/app/(dashboard)/cluster/page.tsx
rename to ee/tabby-ui/app/(dashboard)/system/page.tsx
index 29c36a94a5ab..951e5e8c37ca 100644
--- a/ee/tabby-ui/app/(dashboard)/cluster/page.tsx
+++ b/ee/tabby-ui/app/(dashboard)/system/page.tsx
@@ -3,7 +3,7 @@ import { Metadata } from 'next'
import ClusterInfo from './components/cluster'
export const metadata: Metadata = {
- title: 'Cluster'
+ title: 'System'
}
export default function IndexPage() {
diff --git a/ee/tabby-ui/package.json b/ee/tabby-ui/package.json
index c26ac86d2f69..c89ae345e304 100644
--- a/ee/tabby-ui/package.json
+++ b/ee/tabby-ui/package.json
@@ -53,6 +53,7 @@
"@vercel/kv": "^0.2.1",
"@vercel/og": "^0.5.7",
"ai": "^2.1.6",
+ "bytes": "^3.1.2",
"class-variance-authority": "^0.4.0",
"clsx": "^1.2.1",
"cmdk": "^1.0.0",
@@ -107,6 +108,7 @@
"@ianvs/prettier-plugin-sort-imports": "^4.1.1",
"@parcel/watcher": "^2.3.0",
"@tailwindcss/typography": "^0.5.9",
+ "@types/bytes": "^3.1.4",
"@types/humanize-duration": "^3.27.4",
"@types/lodash-es": "^4.17.10",
"@types/node": "^17.0.12",
diff --git a/ee/tabby-ui/yarn.lock b/ee/tabby-ui/yarn.lock
index 93421fa8bb54..a98fab2a499e 100644
--- a/ee/tabby-ui/yarn.lock
+++ b/ee/tabby-ui/yarn.lock
@@ -2497,6 +2497,11 @@
lodash.merge "^4.6.2"
postcss-selector-parser "6.0.10"
+"@types/bytes@^3.1.4":
+ version "3.1.4"
+ resolved "https://registry.yarnpkg.com/@types/bytes/-/bytes-3.1.4.tgz#8563f38ea6096df3f409c6500e8ac171790a7c1f"
+ integrity sha512-A0uYgOj3zNc4hNjHc5lYUfJQ/HVyBXiUMKdXd7ysclaE6k9oJdavQzODHuwjpUu2/boCP8afjQYi8z/GtvNCWA==
+
"@types/chroma-js@^2.4.3":
version "2.4.4"
resolved "https://registry.yarnpkg.com/@types/chroma-js/-/chroma-js-2.4.4.tgz#254dddff54568ff8e5d0dcdb071871a458fdfd31"
@@ -3403,6 +3408,11 @@ busboy@1.6.0, busboy@^1.6.0:
dependencies:
streamsearch "^1.1.0"
+bytes@^3.1.2:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5"
+ integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==
+
call-bind@^1.0.0, call-bind@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c"