Skip to content

Commit f2550ac

Browse files
feat(ui): add Kubernetes UI dashboard package
Co-Authored-By: Dan Lynch <[email protected]>
1 parent 5c56cb8 commit f2550ac

36 files changed

+2979
-0
lines changed

packages/ui/next.config.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/** @type {import('next').NextConfig} */
2+
const nextConfig = {
3+
reactStrictMode: true,
4+
swcMinify: true,
5+
}
6+
7+
module.exports = nextConfig

packages/ui/package.json

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
{
2+
"name": "@kubernetesjs/ui",
3+
"version": "0.1.0",
4+
"author": "Dan Lynch <[email protected]>",
5+
"description": "KubernetesJS UI Dashboard",
6+
"keywords": [
7+
"kubernetes",
8+
"k8s",
9+
"ui",
10+
"dashboard",
11+
"typescript",
12+
"react",
13+
"nextjs",
14+
"tailwindcss",
15+
"deployment",
16+
"container",
17+
"orchestration",
18+
"devops",
19+
"cloud-native"
20+
],
21+
"main": "index.js",
22+
"homepage": "https://github.com/hyperweb-io/kubernetesjs",
23+
"license": "SEE LICENSE IN LICENSE",
24+
"publishConfig": {
25+
"access": "public",
26+
"directory": "dist"
27+
},
28+
"repository": {
29+
"type": "git",
30+
"url": "https://github.com/hyperweb-io/kubernetesjs"
31+
},
32+
"bugs": {
33+
"url": "https://github.com/hyperweb-io/kubernetesjs/issues"
34+
},
35+
"scripts": {
36+
"dev": "next dev",
37+
"build": "next build",
38+
"start": "next start",
39+
"lint": "next lint"
40+
},
41+
"dependencies": {
42+
"kubernetesjs": "^0.6.0",
43+
"next": "^14.1.0",
44+
"next-themes": "^0.2.1",
45+
"react": "^18.2.0",
46+
"react-dom": "^18.2.0",
47+
"monaco-editor": "^0.45.0",
48+
"zustand": "^4.4.7",
49+
"class-variance-authority": "^0.7.0",
50+
"clsx": "^2.0.0",
51+
"lucide-react": "^0.294.0",
52+
"tailwind-merge": "^2.1.0",
53+
"tailwindcss-animate": "^1.0.7"
54+
},
55+
"devDependencies": {
56+
"@types/node": "^20.10.4",
57+
"@types/react": "^18.2.45",
58+
"@types/react-dom": "^18.2.17",
59+
"autoprefixer": "^10.4.16",
60+
"eslint": "^8.55.0",
61+
"eslint-config-next": "^14.0.4",
62+
"postcss": "^8.4.32",
63+
"tailwindcss": "^3.3.6",
64+
"typescript": "^5.3.3"
65+
}
66+
}

packages/ui/postcss.config.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module.exports = {
2+
plugins: {
3+
tailwindcss: {},
4+
autoprefixer: {},
5+
},
6+
}

packages/ui/src/app/globals.css

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
@tailwind base;
2+
@tailwind components;
3+
@tailwind utilities;
4+
5+
@layer base {
6+
:root {
7+
--background: 0 0% 100%;
8+
--foreground: 222.2 84% 4.9%;
9+
--card: 0 0% 100%;
10+
--card-foreground: 222.2 84% 4.9%;
11+
--popover: 0 0% 100%;
12+
--popover-foreground: 222.2 84% 4.9%;
13+
--primary: 221.2 83.2% 53.3%;
14+
--primary-foreground: 210 40% 98%;
15+
--secondary: 210 40% 96.1%;
16+
--secondary-foreground: 222.2 47.4% 11.2%;
17+
--muted: 210 40% 96.1%;
18+
--muted-foreground: 215.4 16.3% 46.9%;
19+
--accent: 210 40% 96.1%;
20+
--accent-foreground: 222.2 47.4% 11.2%;
21+
--destructive: 0 84.2% 60.2%;
22+
--destructive-foreground: 210 40% 98%;
23+
--border: 214.3 31.8% 91.4%;
24+
--input: 214.3 31.8% 91.4%;
25+
--ring: 221.2 83.2% 53.3%;
26+
--radius: 0.5rem;
27+
}
28+
29+
.dark {
30+
--background: 222.2 84% 4.9%;
31+
--foreground: 210 40% 98%;
32+
--card: 222.2 84% 4.9%;
33+
--card-foreground: 210 40% 98%;
34+
--popover: 222.2 84% 4.9%;
35+
--popover-foreground: 210 40% 98%;
36+
--primary: 217.2 91.2% 59.8%;
37+
--primary-foreground: 222.2 47.4% 11.2%;
38+
--secondary: 217.2 32.6% 17.5%;
39+
--secondary-foreground: 210 40% 98%;
40+
--muted: 217.2 32.6% 17.5%;
41+
--muted-foreground: 215 20.2% 65.1%;
42+
--accent: 217.2 32.6% 17.5%;
43+
--accent-foreground: 210 40% 98%;
44+
--destructive: 0 62.8% 30.6%;
45+
--destructive-foreground: 210 40% 98%;
46+
--border: 217.2 32.6% 17.5%;
47+
--input: 217.2 32.6% 17.5%;
48+
--ring: 224.3 76.3% 48%;
49+
}
50+
}
51+
52+
@layer base {
53+
* {
54+
@apply border-border;
55+
}
56+
body {
57+
@apply bg-background text-foreground;
58+
}
59+
}

packages/ui/src/app/layout.tsx

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import type { Metadata } from 'next';
2+
import { Inter } from 'next/font/google';
3+
import './globals.css';
4+
import { ThemeProvider } from '@/components/theme-provider';
5+
import { Sidebar } from '@/components/layout/sidebar';
6+
7+
const inter = Inter({ subsets: ['latin'] });
8+
9+
export const metadata: Metadata = {
10+
title: 'KubernetesJS Dashboard',
11+
description: 'Modern UI for managing Kubernetes clusters',
12+
};
13+
14+
export default function RootLayout({
15+
children,
16+
}: {
17+
children: React.ReactNode;
18+
}) {
19+
return (
20+
<html lang="en" suppressHydrationWarning>
21+
<body className={inter.className}>
22+
<ThemeProvider
23+
attribute="class"
24+
defaultTheme="system"
25+
enableSystem
26+
disableTransitionOnChange
27+
>
28+
<div className="flex h-screen">
29+
<Sidebar />
30+
<main className="flex-1 overflow-auto p-6">
31+
{children}
32+
</main>
33+
</div>
34+
</ThemeProvider>
35+
</body>
36+
</html>
37+
);
38+
}

packages/ui/src/app/page.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { DashboardHeader } from '@/components/dashboard/dashboard-header';
2+
import { ClusterOverview } from '@/components/dashboard/cluster-overview';
3+
4+
export default function Home() {
5+
return (
6+
<div className="space-y-6">
7+
<DashboardHeader />
8+
<ClusterOverview />
9+
</div>
10+
);
11+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
"use client";
2+
3+
import { useState, useEffect } from "react";
4+
import { ResourceHeader } from "@/components/resources/resource-header";
5+
import { DeploymentList } from "@/components/resources/deployments/deployment-list";
6+
import { useKubernetes } from "@/hooks/use-kubernetes";
7+
8+
export default function DeploymentsPage() {
9+
const { client, namespace } = useKubernetes();
10+
const [deployments, setDeployments] = useState([]);
11+
const [isLoading, setIsLoading] = useState(true);
12+
const [error, setError] = useState<string | null>(null);
13+
14+
useEffect(() => {
15+
async function fetchDeployments() {
16+
setIsLoading(true);
17+
setError(null);
18+
19+
try {
20+
const response = await client.listAppsV1NamespacedDeployment({
21+
path: { namespace }
22+
});
23+
24+
setDeployments(response.items || []);
25+
} catch (err) {
26+
setError(err instanceof Error ? err.message : "Failed to fetch deployments");
27+
} finally {
28+
setIsLoading(false);
29+
}
30+
}
31+
32+
fetchDeployments();
33+
}, [client, namespace]);
34+
35+
return (
36+
<div className="space-y-6">
37+
<ResourceHeader
38+
title="Deployments"
39+
description={`Manage deployments in the ${namespace} namespace`}
40+
onRefresh={() => {
41+
setIsLoading(true);
42+
const timer = setTimeout(() => {
43+
setIsLoading(false);
44+
}, 100);
45+
return () => clearTimeout(timer);
46+
}}
47+
/>
48+
49+
<DeploymentList
50+
deployments={deployments}
51+
isLoading={isLoading}
52+
error={error}
53+
/>
54+
</div>
55+
);
56+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
"use client";
2+
3+
import { useState, useEffect } from "react";
4+
import { ResourceHeader } from "@/components/resources/resource-header";
5+
import { PodList } from "@/components/resources/pods/pod-list";
6+
import { useKubernetes } from "@/hooks/use-kubernetes";
7+
8+
export default function PodsPage() {
9+
const { client, namespace } = useKubernetes();
10+
const [pods, setPods] = useState([]);
11+
const [isLoading, setIsLoading] = useState(true);
12+
const [error, setError] = useState<string | null>(null);
13+
14+
useEffect(() => {
15+
async function fetchPods() {
16+
setIsLoading(true);
17+
setError(null);
18+
19+
try {
20+
const response = await client.listCoreV1NamespacedPod({
21+
path: { namespace }
22+
});
23+
24+
setPods(response.items || []);
25+
} catch (err) {
26+
setError(err instanceof Error ? err.message : "Failed to fetch pods");
27+
} finally {
28+
setIsLoading(false);
29+
}
30+
}
31+
32+
fetchPods();
33+
}, [client, namespace]);
34+
35+
return (
36+
<div className="space-y-6">
37+
<ResourceHeader
38+
title="Pods"
39+
description={`Manage pods in the ${namespace} namespace`}
40+
onRefresh={() => {
41+
setIsLoading(true);
42+
const timer = setTimeout(() => {
43+
setIsLoading(false);
44+
}, 100);
45+
return () => clearTimeout(timer);
46+
}}
47+
/>
48+
49+
<PodList
50+
pods={pods}
51+
isLoading={isLoading}
52+
error={error}
53+
/>
54+
</div>
55+
);
56+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
"use client";
2+
3+
import { useState, useEffect } from "react";
4+
import { ResourceHeader } from "@/components/resources/resource-header";
5+
import { ServiceList } from "@/components/resources/services/service-list";
6+
import { useKubernetes } from "@/hooks/use-kubernetes";
7+
8+
export default function ServicesPage() {
9+
const { client, namespace } = useKubernetes();
10+
const [services, setServices] = useState([]);
11+
const [isLoading, setIsLoading] = useState(true);
12+
const [error, setError] = useState<string | null>(null);
13+
14+
useEffect(() => {
15+
async function fetchServices() {
16+
setIsLoading(true);
17+
setError(null);
18+
19+
try {
20+
const response = await client.listCoreV1NamespacedService({
21+
path: { namespace }
22+
});
23+
24+
setServices(response.items || []);
25+
} catch (err) {
26+
setError(err instanceof Error ? err.message : "Failed to fetch services");
27+
} finally {
28+
setIsLoading(false);
29+
}
30+
}
31+
32+
fetchServices();
33+
}, [client, namespace]);
34+
35+
return (
36+
<div className="space-y-6">
37+
<ResourceHeader
38+
title="Services"
39+
description={`Manage services in the ${namespace} namespace`}
40+
onRefresh={() => {
41+
setIsLoading(true);
42+
const timer = setTimeout(() => {
43+
setIsLoading(false);
44+
}, 100);
45+
return () => clearTimeout(timer);
46+
}}
47+
/>
48+
49+
<ServiceList
50+
services={services}
51+
isLoading={isLoading}
52+
error={error}
53+
/>
54+
</div>
55+
);
56+
}

0 commit comments

Comments
 (0)