From 9d7dcbfb9e390d02eba5a36930c59d0d5c73634c Mon Sep 17 00:00:00 2001
From: incognitotgt
Date: Tue, 23 Apr 2024 07:15:33 -0400
Subject: [PATCH] change formatter file upload
---
.eslintrc.json | 3 -
.gitignore | 1 +
biome.json | 18 +-
config.json | 2 +-
drizzle.config.ts | 8 +-
next.config.mjs | 23 +-
package.json | 5 +-
pnpm-lock.yaml | 84 ++++-
postcss.config.cjs | 2 +-
src/actions/client-session.ts | 34 +-
src/app/(main)/admin/layout.tsx | 16 +-
src/app/(main)/admin/page.tsx | 2 +-
src/app/(main)/error.tsx | 10 +-
src/app/(main)/layout.tsx | 14 +-
src/app/(main)/page.tsx | 417 +++++++++++-----------
src/app/api/auth/[...nextauth]/route.ts | 8 +-
src/app/api/session/files/[slug]/route.ts | 70 ++++
src/app/api/session/preview/route.ts | 33 +-
src/app/api/vnc/[slug]/route.ts | 77 ++--
src/app/auth/error/page.tsx | 14 +-
src/app/auth/layout.tsx | 8 +-
src/app/auth/login/page.tsx | 32 +-
src/app/auth/logout/page.tsx | 30 +-
src/app/auth/verify/page.tsx | 14 +-
src/app/globals.css | 333 ++++++++---------
src/app/layout.tsx | 33 +-
src/app/not-found.tsx | 8 +-
src/app/providers.tsx | 6 +-
src/app/view/[slug]/page.tsx | 337 +++++++++++------
src/app/view/error.tsx | 8 +-
src/app/view/not-found.tsx | 2 +-
src/components/mode-toggle.tsx | 16 +-
src/components/navbar.tsx | 99 +++--
src/components/submit-button.tsx | 12 +-
src/components/ui/alert-dialog.tsx | 50 +--
src/components/ui/alert.tsx | 22 +-
src/components/ui/aspect-ratio.tsx | 7 +
src/components/ui/avatar.tsx | 22 +-
src/components/ui/button.tsx | 22 +-
src/components/ui/card.tsx | 30 +-
src/components/ui/context-menu.tsx | 68 ++--
src/components/ui/dialog.tsx | 44 +--
src/components/ui/dropdown-menu.tsx | 68 ++--
src/components/ui/input.tsx | 22 ++
src/components/ui/label.tsx | 18 +-
src/components/ui/navigation-menu.tsx | 42 +--
src/components/ui/popover.tsx | 18 +-
src/components/ui/select.tsx | 46 +--
src/components/ui/separator.tsx | 14 +-
src/components/ui/sheet.tsx | 48 +--
src/components/ui/skeleton.tsx | 6 +-
src/components/ui/slider.tsx | 14 +-
src/components/ui/sonner.tsx | 16 +-
src/components/ui/switch.tsx | 14 +-
src/components/ui/tabs.tsx | 55 +++
src/components/ui/textarea.tsx | 12 +-
src/components/ui/tooltip.tsx | 20 +-
src/components/vnc-screen.tsx | 360 ++++++++++---------
src/lib/auth.ts | 18 +-
src/lib/docker.ts | 14 +-
src/lib/drizzle/db.ts | 12 +-
src/lib/drizzle/schema.ts | 22 +-
src/lib/drizzle/seed.ts | 30 +-
src/lib/migrate.ts | 10 +-
src/lib/util/get-session.ts | 18 +-
src/lib/util/session.ts | 70 ++--
src/lib/utils.ts | 7 +-
src/middleware.ts | 6 +-
tailwind.config.ts | 9 +-
69 files changed, 1704 insertions(+), 1329 deletions(-)
delete mode 100644 .eslintrc.json
create mode 100644 src/app/api/session/files/[slug]/route.ts
create mode 100644 src/components/ui/aspect-ratio.tsx
create mode 100644 src/components/ui/input.tsx
create mode 100644 src/components/ui/tabs.tsx
diff --git a/.eslintrc.json b/.eslintrc.json
deleted file mode 100644
index 72cc705..0000000
--- a/.eslintrc.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "extends": "next/core-web-vitals"
-}
diff --git a/.gitignore b/.gitignore
index 1b924b6..93e3209 100644
--- a/.gitignore
+++ b/.gitignore
@@ -37,3 +37,4 @@ yarn-error.log*
next-env.d.ts
.tmp
docker/
+.assets
diff --git a/biome.json b/biome.json
index 20e59cb..3f3840b 100644
--- a/biome.json
+++ b/biome.json
@@ -2,22 +2,30 @@
"$schema": "https://biomejs.dev/schemas/1.7.0/schema.json",
"formatter": {
"enabled": true,
- "formatWithErrors": false,
+ "formatWithErrors": true,
"indentStyle": "tab",
"indentWidth": 2,
"lineEnding": "lf",
"lineWidth": 120,
"attributePosition": "auto",
- "ignore": ["**/pnpm-lock.yaml", "**/.next/**"]
+ "ignore": ["**/pnpm-lock.yaml", "**/.next/**", "**/node_modules/**"]
+ },
+ "organizeImports": {
+ "enabled": true
+ },
+ "linter": {
+ "enabled": true,
+ "rules": {
+ "recommended": true
+ },
+ "ignore": ["**/pnpm-lock.yaml", "**/.next/**", "**/node_modules/**"]
},
- "organizeImports": { "enabled": true },
- "linter": { "enabled": true, "rules": { "recommended": true }, "ignore": ["**/pnpm-lock.yaml", "**/.next/**"] },
"javascript": {
"formatter": {
"jsxQuoteStyle": "double",
"quoteProperties": "asNeeded",
"trailingComma": "all",
- "semicolons": "asNeeded",
+ "semicolons": "always",
"arrowParentheses": "always",
"bracketSpacing": true,
"bracketSameLine": false,
diff --git a/config.json b/config.json
index 97e55e9..3e26ded 100644
--- a/config.json
+++ b/config.json
@@ -1,5 +1,5 @@
{
"style": {
- "backgroundImage": "https://raw.githubusercontent.com/zhichaoh/catppuccin-wallpapers/main/waves/cat-waves.png"
+ "backgroundImage": "https://raw.githubusercontent.com/zhichaoh/catppuccin-wallpapers/main/landscapes/yosemite.png"
}
}
diff --git a/drizzle.config.ts b/drizzle.config.ts
index 3151e28..637c02b 100644
--- a/drizzle.config.ts
+++ b/drizzle.config.ts
@@ -1,5 +1,5 @@
-import "dotenv/config"
-import type { Config } from "drizzle-kit"
+import "dotenv/config";
+import type { Config } from "drizzle-kit";
const config: Config = {
driver: "pg",
@@ -10,5 +10,5 @@ const config: Config = {
},
verbose: true,
strict: true,
-}
-export default config
+};
+export default config;
diff --git a/next.config.mjs b/next.config.mjs
index 5e8e878..b95cdaa 100644
--- a/next.config.mjs
+++ b/next.config.mjs
@@ -1,26 +1,21 @@
// @ts-check
-import { verifyPatch } from "next-ws/server/index.js"
+import { verifyPatch } from "next-ws/server/index.js";
-verifyPatch()
+verifyPatch();
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
remotePatterns: [
{
protocol: "https",
- hostname: "http.cat",
- port: "",
- pathname: "/**",
- },
- {
- protocol: "https",
- hostname: "raw.githubusercontent.com",
+ hostname: "*",
port: "",
pathname: "/**",
},
],
},
experimental: {
+ typedRoutes: true,
serverActions: {
allowedOrigins: ["localhost:3000", "*.use.devtunnels.ms"],
},
@@ -32,15 +27,15 @@ const nextConfig = {
destination: "/auth/error",
permanent: true,
},
- ]
+ ];
},
webpack(config) {
config.module.rules.push({
test: /\.node$/,
loader: "node-loader",
- })
- return config
+ });
+ return config;
},
-}
+};
-export default nextConfig
+export default nextConfig;
diff --git a/package.json b/package.json
index d39b9b1..b5d122b 100644
--- a/package.json
+++ b/package.json
@@ -16,6 +16,7 @@
"dependencies": {
"@novnc/novnc": "^1.4.0",
"@radix-ui/react-alert-dialog": "^1.0.5",
+ "@radix-ui/react-aspect-ratio": "^1.0.3",
"@radix-ui/react-avatar": "^1.0.4",
"@radix-ui/react-context-menu": "^2.1.5",
"@radix-ui/react-dialog": "^1.0.5",
@@ -28,6 +29,7 @@
"@radix-ui/react-slider": "^1.1.2",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-switch": "^1.0.3",
+ "@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-tooltip": "^1.0.7",
"@types/dockerode": "^3.3.26",
"class-variance-authority": "^0.7.0",
@@ -48,6 +50,7 @@
"react-dom": "^18.2.0",
"server-only": "^0.0.1",
"sonner": "^1.4.41",
+ "swr": "^2.2.5",
"tailwind-merge": "^2.2.2",
"tailwindcss-animate": "^1.0.7",
"ws": "^8.16.0"
@@ -61,7 +64,7 @@
"@types/react-dom": "^18.2.22",
"@types/ws": "^8.5.10",
"autoprefixer": "^10.4.19",
- "drizzle-kit": "^0.20.16",
+ "drizzle-kit": "^0.20.17",
"postcss": "^8.4.38",
"tailwindcss": "^3.4.1",
"tailwindcss-dotted-background": "^1.1.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 11bf071..ec01a7e 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -11,6 +11,9 @@ dependencies:
'@radix-ui/react-alert-dialog':
specifier: ^1.0.5
version: 1.0.5(@types/react-dom@18.2.22)(@types/react@18.2.67)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-aspect-ratio':
+ specifier: ^1.0.3
+ version: 1.0.3(@types/react-dom@18.2.22)(@types/react@18.2.67)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-avatar':
specifier: ^1.0.4
version: 1.0.4(@types/react-dom@18.2.22)(@types/react@18.2.67)(react-dom@18.2.0)(react@18.2.0)
@@ -47,6 +50,9 @@ dependencies:
'@radix-ui/react-switch':
specifier: ^1.0.3
version: 1.0.3(@types/react-dom@18.2.22)(@types/react@18.2.67)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-tabs':
+ specifier: ^1.0.4
+ version: 1.0.4(@types/react-dom@18.2.22)(@types/react@18.2.67)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-tooltip':
specifier: ^1.0.7
version: 1.0.7(@types/react-dom@18.2.22)(@types/react@18.2.67)(react-dom@18.2.0)(react@18.2.0)
@@ -107,6 +113,9 @@ dependencies:
sonner:
specifier: ^1.4.41
version: 1.4.41(react-dom@18.2.0)(react@18.2.0)
+ swr:
+ specifier: ^2.2.5
+ version: 2.2.5(react@18.2.0)
tailwind-merge:
specifier: ^2.2.2
version: 2.2.2
@@ -143,8 +152,8 @@ devDependencies:
specifier: ^10.4.19
version: 10.4.19(postcss@8.4.38)
drizzle-kit:
- specifier: ^0.20.16
- version: 0.20.16
+ specifier: ^0.20.17
+ version: 0.20.17
postcss:
specifier: ^8.4.38
version: 8.4.38
@@ -921,6 +930,27 @@ packages:
react-dom: 18.2.0(react@18.2.0)
dev: false
+ /@radix-ui/react-aspect-ratio@1.0.3(@types/react-dom@18.2.22)(@types/react@18.2.67)(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-fXR5kbMan9oQqMuacfzlGG/SQMcmMlZ4wrvpckv8SgUulD0MMpspxJrxg/Gp/ISV3JfV1AeSWTYK9GvxA4ySwA==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ react-dom: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.24.1
+ '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.22)(@types/react@18.2.67)(react-dom@18.2.0)(react@18.2.0)
+ '@types/react': 18.2.67
+ '@types/react-dom': 18.2.22
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ dev: false
+
/@radix-ui/react-avatar@1.0.4(@types/react-dom@18.2.22)(@types/react@18.2.67)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-kVK2K7ZD3wwj3qhle0ElXhOjbezIgyl2hVvgwfIdexL3rN6zJmy5AqqIf+D31lxVppdzV8CjAfZ6PklkmInZLw==}
peerDependencies:
@@ -1561,6 +1591,34 @@ packages:
react-dom: 18.2.0(react@18.2.0)
dev: false
+ /@radix-ui/react-tabs@1.0.4(@types/react-dom@18.2.22)(@types/react@18.2.67)(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-egZfYY/+wRNCflXNHx+dePvnz9FbmssDTJBtgRfDY7e8SE5oIo3Py2eCB1ckAbh1Q7cQ/6yJZThJ++sgbxibog==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ react-dom: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.24.1
+ '@radix-ui/primitive': 1.0.1
+ '@radix-ui/react-context': 1.0.1(@types/react@18.2.67)(react@18.2.0)
+ '@radix-ui/react-direction': 1.0.1(@types/react@18.2.67)(react@18.2.0)
+ '@radix-ui/react-id': 1.0.1(@types/react@18.2.67)(react@18.2.0)
+ '@radix-ui/react-presence': 1.0.1(@types/react-dom@18.2.22)(@types/react@18.2.67)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.22)(@types/react@18.2.67)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-roving-focus': 1.0.4(@types/react-dom@18.2.22)(@types/react@18.2.67)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.67)(react@18.2.0)
+ '@types/react': 18.2.67
+ '@types/react-dom': 18.2.22
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ dev: false
+
/@radix-ui/react-tooltip@1.0.7(@types/react-dom@18.2.22)(@types/react@18.2.67)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-lPh5iKNFVQ/jav/j6ZrWq3blfDJ0OH9R6FlNUHPMqdLuQ9vwDgFsRxvl8b7Asuy5c8xmoojHUxKHQSOAvMHxyw==}
peerDependencies:
@@ -2326,8 +2384,8 @@ packages:
wordwrap: 1.0.0
dev: true
- /drizzle-kit@0.20.16:
- resolution: {integrity: sha512-WoqV0XDny8mHnVoLYcUs6H3Ae1HBxeV0zi3VXG/mTahIQYfadZmIc3eRqzXjD3yxemj+GdHI1nThddmsoabdIA==}
+ /drizzle-kit@0.20.17:
+ resolution: {integrity: sha512-mLVDS4nXmO09wFVlzGrdshWnAL+U9eQGC5zRs6hTN6Q9arwQGWU2XnZ17I8BM8Quau8CQRx3Ms6VPgRWJFVp7Q==}
hasBin: true
dependencies:
'@esbuild-kit/esm-loader': 2.6.5
@@ -3823,6 +3881,16 @@ packages:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'}
+ /swr@2.2.5(react@18.2.0):
+ resolution: {integrity: sha512-QtxqyclFeAsxEUeZIYmsaQ0UjimSq1RZ9Un7I68/0ClKK/U3LoyQunwkQfJZr2fc22DfIXLNDc2wFyTEikCUpg==}
+ peerDependencies:
+ react: ^16.11.0 || ^17.0.0 || ^18.0.0
+ dependencies:
+ client-only: 0.0.1
+ react: 18.2.0
+ use-sync-external-store: 1.2.0(react@18.2.0)
+ dev: false
+
/tailwind-merge@2.2.2:
resolution: {integrity: sha512-tWANXsnmJzgw6mQ07nE3aCDkCK4QdT3ThPMCzawoYA2Pws7vSTCvz3Vrjg61jVUGfFZPJzxEP+NimbcW+EdaDw==}
dependencies:
@@ -4031,6 +4099,14 @@ packages:
tslib: 2.6.2
dev: false
+ /use-sync-external-store@1.2.0(react@18.2.0):
+ resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0
+ dependencies:
+ react: 18.2.0
+ dev: false
+
/util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
diff --git a/postcss.config.cjs b/postcss.config.cjs
index 1b69d43..e873f1a 100644
--- a/postcss.config.cjs
+++ b/postcss.config.cjs
@@ -3,4 +3,4 @@ module.exports = {
tailwindcss: {},
autoprefixer: {},
},
-}
+};
diff --git a/src/actions/client-session.ts b/src/actions/client-session.ts
index 274d14e..4937d91 100644
--- a/src/actions/client-session.ts
+++ b/src/actions/client-session.ts
@@ -1,21 +1,21 @@
-"use server"
-import { getAuthSession } from "@/lib/auth"
-import { deleteSession as deleteSessionBase, manageSession as manageSessionBase } from "@/lib/util/session"
-import type Dockerode from "dockerode"
-import { revalidatePath } from "next/cache"
-import { redirect } from "next/navigation"
+"use server";
+import { getAuthSession } from "@/lib/auth";
+import { deleteSession as deleteSessionBase, manageSession as manageSessionBase } from "@/lib/util/session";
+import type Dockerode from "dockerode";
+import { revalidatePath } from "next/cache";
+import { redirect } from "next/navigation";
async function deleteSession(id: string) {
- const userSession = await getAuthSession()
- if (!userSession || !userSession.user) return
- await deleteSessionBase(id, userSession)
- revalidatePath("/")
- redirect("/")
+ const userSession = await getAuthSession();
+ if (!userSession || !userSession.user) return;
+ await deleteSessionBase(id, userSession);
+ revalidatePath("/");
+ redirect("/");
}
async function manageSession(id: string, action: keyof Dockerode.Container, navigate = true) {
- const userSession = await getAuthSession()
- if (!userSession || !userSession.user) return
- await manageSessionBase(id, action, userSession)
- revalidatePath("/")
- if (navigate) redirect("/")
+ const userSession = await getAuthSession();
+ if (!userSession || !userSession.user) return;
+ await manageSessionBase(id, action, userSession);
+ revalidatePath("/");
+ if (navigate) redirect("/");
}
-export { deleteSession, manageSession }
+export { deleteSession, manageSession };
diff --git a/src/app/(main)/admin/layout.tsx b/src/app/(main)/admin/layout.tsx
index c18d01a..c4115ff 100644
--- a/src/app/(main)/admin/layout.tsx
+++ b/src/app/(main)/admin/layout.tsx
@@ -1,18 +1,18 @@
-import { getAuthSession } from "@/lib/auth"
-import { db, user } from "@/lib/drizzle/db"
-import { eq } from "drizzle-orm"
-import { redirect } from "next/navigation"
+import { getAuthSession } from "@/lib/auth";
+import { db, user } from "@/lib/drizzle/db";
+import { eq } from "drizzle-orm";
+import { redirect } from "next/navigation";
export default async function AdminLayout({ children }: { children: React.ReactNode }) {
- const userSession = await getAuthSession()
+ const userSession = await getAuthSession();
const { isAdmin } = (
await db
.select({ isAdmin: user.isAdmin })
.from(user)
.where(eq(user.email, userSession?.user?.email as string))
- )[0]
+ )[0];
if (!isAdmin) {
- return redirect("/")
+ return redirect("/");
}
- return children
+ return children;
}
diff --git a/src/app/(main)/admin/page.tsx b/src/app/(main)/admin/page.tsx
index fac0528..8292683 100644
--- a/src/app/(main)/admin/page.tsx
+++ b/src/app/(main)/admin/page.tsx
@@ -1,3 +1,3 @@
export default function AdminPage() {
- return coming soon (hopefully)
+ return coming soon (hopefully)
;
}
diff --git a/src/app/(main)/error.tsx b/src/app/(main)/error.tsx
index c40aef1..87f792b 100644
--- a/src/app/(main)/error.tsx
+++ b/src/app/(main)/error.tsx
@@ -1,7 +1,7 @@
-"use client"
+"use client";
-import { Button } from "@/components/ui/button"
-import { Sparkles } from "lucide-react"
+import { Button } from "@/components/ui/button";
+import { Sparkles } from "lucide-react";
// biome-ignore lint: lint/suspicious/noShadowRestrictedNames
export default function Error({ error, reset }: { error: Error & { digest?: string }; reset: () => void }) {
return (
@@ -22,10 +22,10 @@ export default function Error({ error, reset }: { error: Error & { digest?: stri
Reset
- More details are in the {typeof window === "undefined" ? "terminal" : "browser"} console
+ More details are in the {typeof window === "undefined" ? "server logs" : "browser console"}
- )
+ );
}
diff --git a/src/app/(main)/layout.tsx b/src/app/(main)/layout.tsx
index 20f2c7d..b3460fa 100644
--- a/src/app/(main)/layout.tsx
+++ b/src/app/(main)/layout.tsx
@@ -1,19 +1,19 @@
-import Navigation from "@/components/navbar"
-import { getAuthSession } from "@/lib/auth"
-import { db, user as userSchema } from "@/lib/drizzle/db"
-import { eq } from "drizzle-orm"
+import Navigation from "@/components/navbar";
+import { getAuthSession } from "@/lib/auth";
+import { db, user as userSchema } from "@/lib/drizzle/db";
+import { eq } from "drizzle-orm";
export default async function DashboardLayout({ children }: { children: React.ReactNode }) {
- const userSession = await getAuthSession()
+ const userSession = await getAuthSession();
const dbUser = (
await db
.select()
.from(userSchema)
.where(eq(userSchema.email, userSession?.user?.email as string))
- )[0]
+ )[0];
return (
{children}
- )
+ );
}
diff --git a/src/app/(main)/page.tsx b/src/app/(main)/page.tsx
index 6a5106e..36ab769 100644
--- a/src/app/(main)/page.tsx
+++ b/src/app/(main)/page.tsx
@@ -1,4 +1,4 @@
-import { StyledSubmit } from "@/components/submit-button"
+import { StyledSubmit } from "@/components/submit-button";
import {
Dialog,
DialogClose,
@@ -8,27 +8,29 @@ import {
DialogHeader,
DialogTitle,
DialogTrigger,
-} from "@/components/ui/dialog"
+} from "@/components/ui/dialog";
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
-import { Button } from "@/components/ui/button"
-import { Card } from "@/components/ui/card"
-import { getAuthSession } from "@/lib/auth"
-import docker from "@/lib/docker"
-import { db, image, user } from "@/lib/drizzle/db"
-import { createSession, deleteSession, manageSession } from "@/lib/util/session"
-import { eq } from "drizzle-orm"
-import { Container, Loader2, Pause, Play, Square, TrashIcon } from "lucide-react"
-import { revalidatePath } from "next/cache"
-import Image from "next/image"
-import Link from "next/link"
-import { redirect } from "next/navigation"
-import { Suspense } from "react"
+import { AspectRatio } from "@/components/ui/aspect-ratio";
+import { Button } from "@/components/ui/button";
+import { Card } from "@/components/ui/card";
+import { getAuthSession } from "@/lib/auth";
+import docker from "@/lib/docker";
+import { db, image, user } from "@/lib/drizzle/db";
+import { createSession, deleteSession, manageSession } from "@/lib/util/session";
+import { eq } from "drizzle-orm";
+import { Container, Loader2, Pause, Play, Square, TrashIcon } from "lucide-react";
+import { revalidatePath } from "next/cache";
+import Image from "next/image";
+import Link from "next/link";
+import { redirect } from "next/navigation";
+import { Suspense } from "react";
const errorCatcher = (error: string) => {
- throw new Error(error)
-}
+ throw new Error(error);
+};
export default async function Dashboard() {
- const userSession = await getAuthSession()
- const images = await db.select().from(image).catch(errorCatcher)
+ const userSession = await getAuthSession();
+ const images = await db.select().from(image).catch(errorCatcher);
const { userId } = (
await db
.select({
@@ -37,7 +39,7 @@ export default async function Dashboard() {
.from(user)
.where(eq(user.email, userSession?.user?.email as string))
.catch(errorCatcher)
- )[0]
+ )[0];
const sessions = await db.query.session
.findMany({
with: {
@@ -45,192 +47,209 @@ export default async function Dashboard() {
},
where: (users, { eq }) => eq(users.userId, userId),
})
- .catch(errorCatcher)
+ .catch(errorCatcher);
return (
-
- }>
- {images.map((image) => (
-
-
-
-
-
-
{image.friendlyName}
-
{image.category}
-
-
-
-
-
-
-
- ))}
-
-
- {sessions.length ? (
-
-
+
+
+ Workspaces
+ Sessions
+
+
+
}>
- {sessions
- ? sessions.map(async (session) => {
- const { State } = await docker.getContainer(session.id).inspect()
- const expiresAt = new Date(session.expiresAt)
- return (
-
-
-
+ {images.map((image) => (
+
+
+
+
+
+
{image.friendlyName}
+
{image.category}
+
+
+
+
+
- {session.imagePreview ? (
-
- ) : (
-
-
-
- )}
-
- {!State.Paused && State.Running ? (
-
-
-
-
-
- ) : null}
- {!State.Paused && State.Running ? (
-
- ) : State.Paused ? (
-
- ) : null}
- {!State.Running ? (
-
- ) : State.Running ? (
-
- ) : null}
-
-
-
- )
- })
- : null}
+ Launch
+
+
+
+
+ ))}
-
- ) : null}
+
+
+ }>
+ {sessions.length ? (
+ sessions.map(async (session) => {
+ const { State } = await docker.getContainer(session.id).inspect();
+ const expiresAt = new Date(session.expiresAt);
+ return (
+
+
+
+
+
+
{session.image.friendlyName}
+
+ {session.id.slice(0, 6)}
+
+
+ Expires at{" "}
+ {`${expiresAt.toLocaleTimeString()} on ${expiresAt.getMonth()}/${expiresAt.getDate()}/${expiresAt.getFullYear()}`}
+
+
+
+ {session.imagePreview ? (
+
+
+
+ ) : (
+
+
+
+ )}
+
+ {!State.Paused && State.Running ? (
+
+
+
+
+
+ ) : null}
+ {!State.Paused && State.Running ? (
+
+ ) : State.Paused ? (
+
+ ) : null}
+ {!State.Running ? (
+
+ ) : State.Running ? (
+
+ ) : null}
+
+
+
+ );
+ })
+ ) : (
+
+
No sessions found
+
Start a new session to see it here
+
+ )}
+
+
+
- )
+ );
}
diff --git a/src/app/api/auth/[...nextauth]/route.ts b/src/app/api/auth/[...nextauth]/route.ts
index 054b883..faa8b92 100644
--- a/src/app/api/auth/[...nextauth]/route.ts
+++ b/src/app/api/auth/[...nextauth]/route.ts
@@ -1,5 +1,5 @@
-import { authConfig } from "@/lib/auth"
-import NextAuth from "next-auth"
+import { authConfig } from "@/lib/auth";
+import NextAuth from "next-auth";
-const handler = NextAuth(authConfig)
-export { handler as GET, handler as POST }
+const handler = NextAuth(authConfig);
+export { handler as GET, handler as POST };
diff --git a/src/app/api/session/files/[slug]/route.ts b/src/app/api/session/files/[slug]/route.ts
new file mode 100644
index 0000000..f95a8d2
--- /dev/null
+++ b/src/app/api/session/files/[slug]/route.ts
@@ -0,0 +1,70 @@
+import { execSync } from "node:child_process";
+import fs from "node:fs";
+import { getAuthSession } from "@/lib/auth";
+import { getSession } from "@/lib/util/get-session";
+import type { NextRequest } from "next/server";
+
+export async function GET(_req: NextRequest, { params }: { params: { slug: string } }) {
+ const userSession = await getAuthSession();
+ if (!userSession) {
+ return Response.json({ error: "Unauthorized" }, { status: 401 });
+ }
+ const { id } = (await getSession(params.slug, userSession)) || {};
+ if (!id) {
+ return Response.json({ error: "Not Found" }, { status: 404 });
+ }
+ execSync(`docker exec ${id} mkdir -p /home/stardust/Downloads`);
+ const files = execSync(`docker exec ${id} ls /home/stardust/Downloads`).toString().split("\n").filter(Boolean);
+ return Response.json(files);
+}
+export async function POST(req: NextRequest, { params }: { params: { slug: string } }) {
+ const userSession = await getAuthSession();
+ const { fileName } = await req.json();
+ if (!userSession) {
+ return Response.json({ error: "Unauthorized" }, { status: 401 });
+ }
+ const { id } = (await getSession(params.slug, userSession)) || {};
+ if (!id) {
+ return Response.json({ error: "Not Found" }, { status: 404 });
+ }
+ try {
+ const dirPath = `${process.cwd()}/.assets/uploads/${id}`;
+ if (!fs.existsSync(dirPath)) {
+ fs.mkdirSync(dirPath, { recursive: true });
+ }
+ execSync(`docker cp "${id}:/home/stardust/Downloads/${fileName}" "${dirPath}/${fileName}"`);
+ const blob = new Blob([fs.readFileSync(`${dirPath}/${fileName}`)]);
+ fs.unlinkSync(`${dirPath}/${fileName}`);
+ return new Response(blob, {
+ headers: {
+ "Content-Disposition": `attachment; filename=${fileName}`,
+ "Content-Type": "application/octet-stream",
+ },
+ });
+ } catch (e) {
+ console.error("Download failed: %e", e);
+ return Response.json({ error: "Download failed" }, { status: 500 });
+ }
+}
+export async function PUT(req: NextRequest, { params }: { params: { slug: string } }) {
+ const userSession = await getAuthSession();
+ const buffer = await req.arrayBuffer();
+ const fileName = req.nextUrl.searchParams.get("name");
+ if (!userSession) {
+ return Response.json({ error: "Unauthorized" }, { status: 401 });
+ }
+ const { id } = (await getSession(params.slug, userSession)) || {};
+ if (!id) {
+ return Response.json({ error: "Not Found" }, { status: 404 });
+ }
+ const dirPath = `${process.cwd()}/.assets/uploads/${id}`;
+ if (!fs.existsSync(dirPath)) {
+ fs.mkdirSync(dirPath, { recursive: true });
+ }
+ const path = `${dirPath}/${fileName}`;
+ fs.writeFileSync(path, Buffer.from(buffer));
+ execSync(`docker exec ${id} mkdir -p /home/stardust/Uploads`);
+ execSync(`docker cp "${path}" "${id}:/home/stardust/Uploads/${fileName}"`);
+ fs.unlinkSync(`${dirPath}/${fileName}`);
+ return Response.json({ success: true });
+}
diff --git a/src/app/api/session/preview/route.ts b/src/app/api/session/preview/route.ts
index 1de140b..42ba267 100644
--- a/src/app/api/session/preview/route.ts
+++ b/src/app/api/session/preview/route.ts
@@ -1,29 +1,28 @@
-import { getAuthSession } from "@/lib/auth"
-import { db, session } from "@/lib/drizzle/db"
-import { getSession } from "@/lib/util/get-session"
-import { eq } from "drizzle-orm"
-import { getServerSession } from "next-auth"
-import { revalidatePath } from "next/cache"
-import type { NextRequest } from "next/server"
+import { getAuthSession } from "@/lib/auth";
+import { db, session } from "@/lib/drizzle/db";
+import { getSession } from "@/lib/util/get-session";
+import { eq } from "drizzle-orm";
+import { revalidatePath } from "next/cache";
+import type { NextRequest } from "next/server";
export async function POST(req: NextRequest) {
- const { imagePreview, containerId } = await req.json()
- const userSession = await getAuthSession()
+ const { imagePreview, containerId } = await req.json();
+ const userSession = await getAuthSession();
if (!userSession) {
- return Response.json({ error: "Unauthorized" }, { status: 401 })
+ return Response.json({ error: "Unauthorized" }, { status: 401 });
}
- const containerSession = await getSession(containerId, userSession)
+ const containerSession = await getSession(containerId, userSession);
if (!containerSession) {
- return Response.json({ error: "Not Found" }, { status: 404 })
+ return Response.json({ error: "Not Found" }, { status: 404 });
}
await db
.update(session)
.set({ imagePreview })
.where(eq(session.id, containerSession.id))
.catch((e) => {
- console.error("Update failed: %e", e)
- return Response.json({ error: "Update failed" }, { status: 500 })
- })
- revalidatePath("/")
- return Response.json({ success: true })
+ console.error("Update failed: %e", e);
+ return Response.json({ error: "Update failed" }, { status: 500 });
+ });
+ revalidatePath("/");
+ return Response.json({ success: true });
}
diff --git a/src/app/api/vnc/[slug]/route.ts b/src/app/api/vnc/[slug]/route.ts
index 54b4db9..ba39ce7 100644
--- a/src/app/api/vnc/[slug]/route.ts
+++ b/src/app/api/vnc/[slug]/route.ts
@@ -1,63 +1,64 @@
-import { getAuthSession } from "@/lib/auth"
-import docker from "@/lib/docker"
-import { getSession as getContainerSession } from "@/lib/util/get-session"
-import type { IncomingMessage } from "node:http"
-import net from "node:net"
-import { getSession } from "next-auth/react"
-import type { NextRequest } from "next/server"
-import type { WebSocket, WebSocketServer } from "ws"
+import { getAuthSession } from "@/lib/auth";
+import docker from "@/lib/docker";
+import { getSession as getContainerSession } from "@/lib/util/get-session";
+import type { IncomingMessage } from "node:http";
+import net from "node:net";
+import { getSession } from "next-auth/react";
+import type { NextRequest } from "next/server";
+import type { WebSocket, WebSocketServer } from "ws";
export async function GET(_req: NextRequest, { params }: { params: { slug: string } }) {
- const userSession = await getAuthSession()
+ const userSession = await getAuthSession();
if (!userSession || !userSession.user) {
- return Response.json({ error: "Unauthorized" }, { status: 401 })
+ return Response.json({ error: "Unauthorized" }, { status: 401 });
}
- const containerSession = await getContainerSession(params.slug, userSession)
+ const containerSession = await getContainerSession(params.slug, userSession);
if (!containerSession) {
- return Response.json({ exists: false, error: "Container not found" }, { status: 404 })
+ return Response.json({ exists: false, error: "Container not found" }, { status: 404 });
}
- const container = docker.getContainer(containerSession.id)
- const { State } = await container.inspect()
+ const container = docker.getContainer(containerSession.id);
+ const { State } = await container.inspect();
if (!State.Running) {
- await container.start()
+ await container.start();
}
- return Response.json({ exists: true, paused: State.Paused })
+ return Response.json({ exists: true, paused: State.Paused });
}
+
export async function SOCKET(ws: WebSocket, req: IncomingMessage, _server: WebSocketServer) {
- const containerId = req.url?.split("/")[3]
+ const containerId = req.url?.split("/")[3];
if (!containerId) {
- ws.close()
- return
+ ws.close();
+ return;
}
- const userSession = await getSession({ req })
+ const userSession = await getSession({ req });
if (!userSession || !userSession.user) {
- ws.close()
- return
+ ws.close();
+ return;
}
- const containerSession = await getContainerSession(containerId, userSession)
+ const containerSession = await getContainerSession(containerId, userSession);
if (!containerSession) {
- ws.close()
- return
+ ws.close();
+ return;
}
- const tcpSocket = net.connect(containerSession.vncPort, process.env.CONTAINER_HOST as string)
+ const tcpSocket = net.connect(containerSession.vncPort, process.env.CONTAINER_HOST as string);
ws.on("message", (message: Uint8Array) => {
- tcpSocket.write(message)
- })
+ tcpSocket.write(message);
+ });
ws.on("close", (code, reason) => {
- console.log(`Connection closed due to ${reason} with code ${code}`)
- tcpSocket.end()
- })
+ console.log(`Connection closed due to ${reason} with code ${code}`);
+ tcpSocket.end();
+ });
tcpSocket.on("data", (data) => {
- ws.send(data)
- })
+ ws.send(data);
+ });
tcpSocket.on("error", (err) => {
- console.error(err)
- ws.close()
- })
+ console.error(err);
+ ws.close();
+ });
tcpSocket.on("close", () => {
- ws.close()
- })
+ ws.close();
+ });
}
diff --git a/src/app/auth/error/page.tsx b/src/app/auth/error/page.tsx
index 522fb65..cec3d20 100644
--- a/src/app/auth/error/page.tsx
+++ b/src/app/auth/error/page.tsx
@@ -1,12 +1,12 @@
-"use client"
+"use client";
-import { CardContent, CardHeader } from "@/components/ui/card"
-import { ShieldX } from "lucide-react"
-import { useSearchParams } from "next/navigation"
+import { CardContent, CardHeader } from "@/components/ui/card";
+import { ShieldX } from "lucide-react";
+import { useSearchParams } from "next/navigation";
export default function AuthError() {
- const searchParams = useSearchParams()
- const error = searchParams.get("error")
+ const searchParams = useSearchParams();
+ const error = searchParams.get("error");
return (
<>
@@ -23,5 +23,5 @@ export default function AuthError() {
Please try again. If the problem persists, please contact support.
>
- )
+ );
}
diff --git a/src/app/auth/layout.tsx b/src/app/auth/layout.tsx
index 04b6f65..194ff47 100644
--- a/src/app/auth/layout.tsx
+++ b/src/app/auth/layout.tsx
@@ -1,6 +1,6 @@
-import ModeToggle from "@/components/mode-toggle"
-import { Card, CardTitle } from "@/components/ui/card"
-import { Sparkles } from "lucide-react"
+import ModeToggle from "@/components/mode-toggle";
+import { Card, CardTitle } from "@/components/ui/card";
+import { Sparkles } from "lucide-react";
export default function LoginLayout({ children }: Readonly<{ children: React.ReactNode }>) {
return (
@@ -14,5 +14,5 @@ export default function LoginLayout({ children }: Readonly<{ children: React.Rea
- )
+ );
}
diff --git a/src/app/auth/login/page.tsx b/src/app/auth/login/page.tsx
index 78e3df1..374f90b 100644
--- a/src/app/auth/login/page.tsx
+++ b/src/app/auth/login/page.tsx
@@ -1,20 +1,20 @@
-"use client"
+"use client";
-import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
-import { Button } from "@/components/ui/button"
-import { CardContent } from "@/components/ui/card"
-import { AlertCircle, Loader2, LogIn } from "lucide-react"
-import { signIn, useSession } from "next-auth/react"
-import { redirect, useSearchParams } from "next/navigation"
-import { useState } from "react"
+import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
+import { Button } from "@/components/ui/button";
+import { CardContent } from "@/components/ui/card";
+import { AlertCircle, Loader2, LogIn } from "lucide-react";
+import { signIn, useSession } from "next-auth/react";
+import { redirect, useSearchParams } from "next/navigation";
+import { useState } from "react";
export default function Login() {
- const searchParams = useSearchParams()
- const [loading, setLoading] = useState(false)
- const error = searchParams.get("error")
- const { data: session } = useSession()
+ const searchParams = useSearchParams();
+ const [loading, setLoading] = useState(false);
+ const error = searchParams.get("error");
+ const { data: session } = useSession();
if (session) {
- redirect("/")
+ redirect("/");
}
return (
@@ -30,8 +30,8 @@ export default function Login() {
disabled={loading}
className="my-1 w-full"
onClick={() => {
- setLoading(true)
- signIn("auth0")
+ setLoading(true);
+ signIn("auth0");
}}
>
{loading ? : }
@@ -39,5 +39,5 @@ export default function Login() {
- )
+ );
}
diff --git a/src/app/auth/logout/page.tsx b/src/app/auth/logout/page.tsx
index 76436b0..8d74564 100644
--- a/src/app/auth/logout/page.tsx
+++ b/src/app/auth/logout/page.tsx
@@ -1,16 +1,16 @@
-"use client"
+"use client";
-import { Button } from "@/components/ui/button"
-import { CardContent } from "@/components/ui/card"
-import { ChevronLeft, Loader2, LogOut } from "lucide-react"
-import { signOut } from "next-auth/react"
-import { useRouter } from "next/navigation"
-import { useState } from "react"
-import { toast } from "sonner"
+import { Button } from "@/components/ui/button";
+import { CardContent } from "@/components/ui/card";
+import { ChevronLeft, Loader2, LogOut } from "lucide-react";
+import { signOut } from "next-auth/react";
+import { useRouter } from "next/navigation";
+import { useState } from "react";
+import { toast } from "sonner";
export default function SignOut() {
- const [loading, setLoading] = useState(false)
- const router = useRouter()
+ const [loading, setLoading] = useState(false);
+ const router = useRouter();
return (
Are you sure you want to log out?
@@ -18,10 +18,10 @@ export default function SignOut() {
disabled={loading}
className="mt-6 w-full"
onClick={async () => {
- setLoading(true)
- await signOut()
- toast("You have been logged out")
- router.push("/auth/login")
+ setLoading(true);
+ await signOut();
+ toast("You have been logged out");
+ router.push("/auth/login");
}}
>
{!loading ? : }
@@ -37,5 +37,5 @@ export default function SignOut() {
Go back
- )
+ );
}
diff --git a/src/app/auth/verify/page.tsx b/src/app/auth/verify/page.tsx
index 1b4c3aa..439f9f8 100644
--- a/src/app/auth/verify/page.tsx
+++ b/src/app/auth/verify/page.tsx
@@ -1,12 +1,12 @@
-import { CardContent, CardFooter, CardTitle } from "@/components/ui/card"
-import { getAuthSession } from "@/lib/auth"
-import { Mail } from "lucide-react"
-import { redirect } from "next/navigation"
+import { CardContent, CardFooter, CardTitle } from "@/components/ui/card";
+import { getAuthSession } from "@/lib/auth";
+import { Mail } from "lucide-react";
+import { redirect } from "next/navigation";
export default async function EmailVerify() {
- const session = await getAuthSession()
+ const session = await getAuthSession();
if (session) {
- redirect("/")
+ redirect("/");
}
return (
<>
@@ -26,5 +26,5 @@ export default async function EmailVerify() {
>
- )
+ );
}
diff --git a/src/app/globals.css b/src/app/globals.css
index 5e03a9e..b8ee6b0 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -3,200 +3,147 @@
@tailwind utilities;
@layer base {
- :root {
- --background: 0 0% 100%;
- --foreground: 240 10% 3.9%;
-
- --card: 0 0% 100%;
- --card-foreground: 240 10% 3.9%;
-
- --popover: 0 0% 100%;
- --popover-foreground: 240 10% 3.9%;
-
- --primary: 240 5.9% 10%;
- --primary-foreground: 0 0% 98%;
-
- --secondary: 240 4.8% 95.9%;
- --secondary-foreground: 240 5.9% 10%;
-
- --muted: 240 4.8% 95.9%;
- --muted-foreground: 240 3.8% 46.1%;
-
- --accent: 240 4.8% 95.9%;
- --accent-foreground: 240 5.9% 10%;
-
- --destructive: 0 84.2% 60.2%;
- --destructive-foreground: 0 0% 98%;
-
- --border: 240 5.9% 90%;
- --input: 240 5.9% 90%;
- --ring: 240 10% 3.9%;
-
- --radius: 0.5rem;
- }
-
- .dark {
- --background: 240 10% 3.9%;
- --foreground: 0 0% 98%;
-
- --card: 240 10% 3.9%;
- --card-foreground: 0 0% 98%;
-
- --popover: 240 10% 3.9%;
- --popover-foreground: 0 0% 98%;
-
- --primary: 0 0% 98%;
- --primary-foreground: 240 5.9% 10%;
-
- --secondary: 240 3.7% 15.9%;
- --secondary-foreground: 0 0% 98%;
-
- --muted: 240 3.7% 15.9%;
- --muted-foreground: 240 5% 64.9%;
-
- --accent: 240 3.7% 15.9%;
- --accent-foreground: 0 0% 98%;
-
- --destructive: 0 62.8% 30.6%;
- --destructive-foreground: 0 0% 98%;
-
- --border: 240 3.7% 15.9%;
- --input: 240 3.7% 15.9%;
- --ring: 240 4.9% 83.9%;
- }
- .mocha {
- --background: 240 21.053% 14.902%; /* base */
- --foreground: 226.154 63.934% 88.039%; /* text */
-
- --muted: 236.842 16.239% 22.941%; /* surface0 */
- --muted-foreground: 226.667 35.294% 80%; /* subtext1 */
-
- --popover: 240 21.053% 14.902%; /* base */
- --popover-foreground: 226.154 63.934% 88.039%; /* text */
-
- --card: 240 21.053% 14.902%; /* base */
- --card-foreground: 226.154 63.934% 88.039%; /* text */
-
- --border: 234.286 13.208% 31.176%; /* surface1 */
- --input: 234.286 13.208% 31.176%; /* surface1 */
-
- --primary: 267.407 83.505% 80.98%; /* mauve */
- --primary-foreground: 240 21.053% 14.902%; /* base */
-
- --secondary: 236.842 16.239% 22.941%; /* surface0 */
- --secondary-foreground: 226.154 63.934% 88.039%; /* text */
-
- --accent: 236.842 16.239% 22.941%; /* surface0 */
- --accent-foreground: 226.154 63.934% 88.039%; /* text */
-
- --destructive: 343.269 81.25% 74.902%; /* red */
- --destructive-foreground: 240 21.311% 11.961%; /* mantle */
-
- --ring: 226.154 63.934% 88.039%; /* text */
- }
- .macchiato {
- --background: 231.818 23.404% 18.431%; /* base */
- --foreground: 227.442 68.254% 87.647%; /* text */
-
- --muted: 230.4 18.797% 26.078%; /* surface0 */
- --muted-foreground: 228 39.216% 80%; /* subtext1 */
-
- --popover: 231.818 23.404% 18.431%; /* base */
- --popover-foreground: 227.442 68.254% 87.647%; /* text */
-
- --card: 231.818 23.404% 18.431%; /* base */
- --card-foreground: 227.442 68.254% 87.647%; /* text */
-
- --border: 231.111 15.607% 33.922%; /* surface1 */
- --input: 231.111 15.607% 33.922%; /* surface1 */
-
- --primary: 266.512 82.692% 79.608%; /* mauve */
- --primary-foreground: 231.818 23.404% 18.431%; /* base */
-
- --secondary: 230.4 18.797% 26.078%; /* surface0 */
- --secondary-foreground: 227.442 68.254% 87.647%; /* text */
-
- --accent: 230.4 18.797% 26.078%; /* surface0 */
- --accent-foreground: 227.442 68.254% 87.647%; /* text */
-
- --destructive: 351.176 73.913% 72.941%; /* red */
- --destructive-foreground: 233.333 23.077% 15.294%; /* mantle */
-
- --ring: 227.442 68.254% 87.647%; /* text */
-
- --radius: 0.5rem;
- }
- .frappe {
- --background: 229.091 18.644% 23.137%; /* base */
- --foreground: 227.234 70.149% 86.863%; /* text */
-
- --muted: 230 15.584% 30.196%; /* surface0 */
- --muted-foreground: 226.667 43.689% 79.804%; /* subtext1 */
-
- --popover: 229.091 18.644% 23.137%; /* base */
- --popover-foreground: 227.234 70.149% 86.863%; /* text */
-
- --card: 229.091 18.644% 23.137%; /* base */
- --card-foreground: 227.234 70.149% 86.863%; /* text */
-
- --border: 227.143 14.737% 37.255%; /* surface1 */
- --input: 227.143 14.737% 37.255%; /* surface1 */
-
- --primary: 276.667 59.016% 76.078%; /* mauve */
- --primary-foreground: 229.091 18.644% 23.137%; /* base */
-
- --secondary: 230 15.584% 30.196%; /* surface0 */
- --secondary-foreground: 227.234 70.149% 86.863%; /* text */
-
- --accent: 230 15.584% 30.196%; /* surface0 */
- --accent-foreground: 227.234 70.149% 86.863%; /* text */
-
- --destructive: 358.812 67.785% 70.784%; /* red */
- --destructive-foreground: 230.526 18.812% 19.804%; /* mantle */
-
- --ring: 227.234 70.149% 86.863%; /* text */
-
- --radius: 0.5rem;
- }
- .latte {
- --background: 220 23.077% 94.902%; /* base */
- --foreground: 233.793 16.022% 35.49%; /* text */
-
- --muted: 222.857 15.909% 82.745%; /* surface0 */
- --muted-foreground: 233.333 12.796% 41.373%; /* subtext1 */
-
- --popover: 220 23.077% 94.902%; /* base */
- --popover-foreground: 233.793 16.022% 35.49%; /* text */
-
- --card: 220 23.077% 94.902%; /* base */
- --card-foreground: 233.793 16.022% 35.49%; /* text */
-
- --border: 225 13.559% 76.863%; /* surface1 */
- --input: 225 13.559% 76.863%; /* surface1 */
-
- --primary: 266.044 85.047% 58.039%; /* mauve */
- --primary-foreground: 220 23.077% 94.902%; /* base */
-
- --secondary: 222.857 15.909% 82.745%; /* surface0 */
- --secondary-foreground: 233.793 16.022% 35.49%; /* text */
-
- --accent: 222.857 15.909% 82.745%; /* surface0 */
- --accent-foreground: 233.793 16.022% 35.49%; /* text */
-
- --destructive: 347.077 86.667% 44.118%; /* red */
- --destructive-foreground: 220 21.951% 91.961%; /* mantle */
-
- --ring: 233.793 16.022% 35.49%; /* text */
-
- --radius: 0.5rem;
- }
+ :root {
+ --background: 0 0% 100%;
+ --foreground: 240 10% 3.9%;
+
+ --card: 0 0% 100%;
+ --card-foreground: 240 10% 3.9%;
+
+ --popover: 0 0% 100%;
+ --popover-foreground: 240 10% 3.9%;
+
+ --primary: 240 5.9% 10%;
+ --primary-foreground: 0 0% 98%;
+
+ --secondary: 240 4.8% 95.9%;
+ --secondary-foreground: 240 5.9% 10%;
+
+ --muted: 240 4.8% 95.9%;
+ --muted-foreground: 240 3.8% 46.1%;
+
+ --accent: 240 4.8% 95.9%;
+ --accent-foreground: 240 5.9% 10%;
+
+ --destructive: 0 84.2% 60.2%;
+ --destructive-foreground: 0 0% 98%;
+
+ --border: 240 5.9% 90%;
+ --input: 240 5.9% 90%;
+ --ring: 240 10% 3.9%;
+
+ --radius: 0.5rem;
+ }
+
+ .dark {
+ --background: 240 10% 3.9%;
+ --foreground: 0 0% 98%;
+
+ --card: 240 10% 3.9%;
+ --card-foreground: 0 0% 98%;
+
+ --popover: 240 10% 3.9%;
+ --popover-foreground: 0 0% 98%;
+
+ --primary: 0 0% 98%;
+ --primary-foreground: 240 5.9% 10%;
+
+ --secondary: 240 3.7% 15.9%;
+ --secondary-foreground: 0 0% 98%;
+
+ --muted: 240 3.7% 15.9%;
+ --muted-foreground: 240 5% 64.9%;
+
+ --accent: 240 3.7% 15.9%;
+ --accent-foreground: 0 0% 98%;
+
+ --destructive: 0 62.8% 30.6%;
+ --destructive-foreground: 0 0% 98%;
+
+ --border: 240 3.7% 15.9%;
+ --input: 240 3.7% 15.9%;
+ --ring: 240 4.9% 83.9%;
+ }
+
+ .blue {
+ --background: 222.2 84% 4.9%;
+ --foreground: 210 40% 98%;
+ --card: 222.2 84% 4.9%;
+ --card-foreground: 210 40% 98%;
+ --popover: 222.2 84% 4.9%;
+ --popover-foreground: 210 40% 98%;
+ --primary: 217.2 91.2% 59.8%;
+ --primary-foreground: 222.2 47.4% 11.2%;
+ --secondary: 217.2 32.6% 17.5%;
+ --secondary-foreground: 210 40% 98%;
+ --muted: 217.2 32.6% 17.5%;
+ --muted-foreground: 215 20.2% 65.1%;
+ --accent: 217.2 32.6% 17.5%;
+ --accent-foreground: 210 40% 98%;
+ --destructive: 0 62.8% 30.6%;
+ --destructive-foreground: 210 40% 98%;
+ --border: 217.2 32.6% 17.5%;
+ --input: 217.2 32.6% 17.5%;
+ --ring: 224.3 76.3% 48%;
+ }
+
+ .mocha {
+ --background: 240 21.053% 14.902%;
+ /* base */
+ --foreground: 226.154 63.934% 88.039%;
+ /* text */
+
+ --muted: 236.842 16.239% 22.941%;
+ /* surface0 */
+ --muted-foreground: 226.667 35.294% 80%;
+ /* subtext1 */
+
+ --popover: 240 21.053% 14.902%;
+ /* base */
+ --popover-foreground: 226.154 63.934% 88.039%;
+ /* text */
+
+ --card: 240 21.053% 14.902%;
+ /* base */
+ --card-foreground: 226.154 63.934% 88.039%;
+ /* text */
+
+ --border: 234.286 13.208% 31.176%;
+ /* surface1 */
+ --input: 234.286 13.208% 31.176%;
+ /* surface1 */
+
+ --primary: 267.407 83.505% 80.98%;
+ /* mauve */
+ --primary-foreground: 240 21.053% 14.902%;
+ /* base */
+
+ --secondary: 236.842 16.239% 22.941%;
+ /* surface0 */
+ --secondary-foreground: 226.154 63.934% 88.039%;
+ /* text */
+
+ --accent: 236.842 16.239% 22.941%;
+ /* surface0 */
+ --accent-foreground: 226.154 63.934% 88.039%;
+ /* text */
+
+ --destructive: 343.269 81.25% 74.902%;
+ /* red */
+ --destructive-foreground: 240 21.311% 11.961%;
+ /* mantle */
+
+ --ring: 226.154 63.934% 88.039%;
+ /* text */
+ }
}
@layer base {
- * {
- @apply border-border;
- }
- body {
- @apply bg-background text-foreground;
- }
+ * {
+ @apply border-border;
+ }
+
+ body {
+ @apply bg-background text-foreground;
+ }
}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index de3d847..e362efd 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,12 +1,13 @@
-import { Toaster } from "@/components/ui/sonner"
-import { getAuthSession } from "@/lib/auth"
-import { readFileSync } from "node:fs"
-import type { Metadata } from "next"
-import { ThemeProvider } from "next-themes"
-import { Inter } from "next/font/google"
-import "./globals.css"
-import { Session } from "./providers"
-const inter = Inter({ subsets: ["latin"] })
+import { readFileSync } from "node:fs";
+import { Toaster } from "@/components/ui/sonner";
+import { getAuthSession } from "@/lib/auth";
+import type { Metadata } from "next";
+import { ThemeProvider } from "next-themes";
+import { Inter, JetBrains_Mono } from "next/font/google";
+import "./globals.css";
+import { Session } from "./providers";
+const inter = Inter({ subsets: ["latin"] });
+const jetbrains = JetBrains_Mono({ subsets: ["latin"], variable: "--mono" });
export const metadata: Metadata = {
title: "Stardust",
@@ -17,17 +18,17 @@ export const metadata: Metadata = {
type: "website",
url: "https://stardust.spaceness.one", // probably going to be a personal website for stardust,
},
-}
+};
export default async function RootLayout({
children,
}: Readonly<{
- children: React.ReactNode
+ children: React.ReactNode;
}>) {
- const config = JSON.parse(readFileSync(`${process.cwd()}/config.json`, "utf-8"))
+ const config = JSON.parse(readFileSync(`${process.cwd()}/config.json`, "utf-8"));
return (
-
+
{children}
- )
+ );
}
diff --git a/src/app/not-found.tsx b/src/app/not-found.tsx
index d8a0d5c..b24bc70 100644
--- a/src/app/not-found.tsx
+++ b/src/app/not-found.tsx
@@ -1,6 +1,6 @@
-import { Button } from "@/components/ui/button"
-import { Sparkles } from "lucide-react"
-import Link from "next/link"
+import { Button } from "@/components/ui/button";
+import { Sparkles } from "lucide-react";
+import Link from "next/link";
export default function NotFound() {
return (
@@ -19,5 +19,5 @@ export default function NotFound() {
- )
+ );
}
diff --git a/src/app/providers.tsx b/src/app/providers.tsx
index cb45203..35b3530 100644
--- a/src/app/providers.tsx
+++ b/src/app/providers.tsx
@@ -1,5 +1,5 @@
-"use client"
+"use client";
-import { SessionProvider, type SessionProviderProps } from "next-auth/react"
+import { SessionProvider, type SessionProviderProps } from "next-auth/react";
-export const Session = ({ ...props }: SessionProviderProps) =>
+export const Session = ({ ...props }: SessionProviderProps) => ;
diff --git a/src/app/view/[slug]/page.tsx b/src/app/view/[slug]/page.tsx
index 0270721..dedd205 100644
--- a/src/app/view/[slug]/page.tsx
+++ b/src/app/view/[slug]/page.tsx
@@ -1,6 +1,7 @@
-"use client"
+"use client";
-import { deleteSession, manageSession } from "@/actions/client-session"
+import { deleteSession, manageSession } from "@/actions/client-session";
+import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import {
AlertDialog,
AlertDialogAction,
@@ -11,22 +12,29 @@ import {
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
-} from "@/components/ui/alert-dialog"
-import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
+} from "@/components/ui/alert-dialog";
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
-import { Button } from "@/components/ui/button"
-import { Label } from "@/components/ui/label"
-import { Separator } from "@/components/ui/separator"
+import { Button } from "@/components/ui/button";
+import { Label } from "@/components/ui/label";
+import { Separator } from "@/components/ui/separator";
-import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "@/components/ui/sheet"
-import { Slider } from "@/components/ui/slider"
-import { Switch } from "@/components/ui/switch"
-import { Textarea } from "@/components/ui/textarea"
-import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"
-import type { VncViewerHandle } from "@/components/vnc-screen"
+import { Card } from "@/components/ui/card";
+import { Input } from "@/components/ui/input";
+import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "@/components/ui/sheet";
+import { Skeleton } from "@/components/ui/skeleton";
+import { Slider } from "@/components/ui/slider";
+import { Switch } from "@/components/ui/switch";
+import { Textarea } from "@/components/ui/textarea";
+import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
+import type { VncViewerHandle } from "@/components/vnc-screen";
+import { fetcher } from "@/lib/utils";
import {
Camera,
ChevronRight,
+ Download,
+ File,
+ Info,
LogOut,
LucideHome,
Maximize,
@@ -35,106 +43,123 @@ import {
RotateCw,
Sparkles,
TrashIcon,
-} from "lucide-react"
-import Link from "next/link"
-import { notFound, useSearchParams } from "next/navigation"
-import { Suspense, lazy, useEffect, useRef, useState } from "react"
-import { toast } from "sonner"
-type ScalingValues = "remote" | "local" | "none"
+} from "lucide-react";
+import Link from "next/link";
+import { notFound, useSearchParams } from "next/navigation";
+import { Suspense, lazy, useEffect, useRef, useState } from "react";
+import { toast } from "sonner";
+import useSWR, { useSWRConfig } from "swr";
+type ScalingValues = "remote" | "local" | "none";
const Loading = ({ text }: { text: string }) => (
-
-
+
+
{text}
-)
-const VncScreen = lazy(() => import("@/components/vnc-screen"))
+);
+const VncScreen = lazy(() => import("@/components/vnc-screen"));
export default function View({ params }: { params: { slug: string } }) {
- const vncRef = useRef
(null)
+ const vncRef = useRef(null);
const [session, setSession] = useState<{
- exists: boolean
- url: string | null
- paused?: boolean
- } | null>(null)
- const [connected, setConnected] = useState(false)
- const [pausing, setPausing] = useState(false)
- const [fullScreen, setFullScreen] = useState(false)
- const [sidebarOpen, setSidebarOpen] = useState(false)
- const [workingClipboard, setWorkingClipboard] = useState(true)
+ exists: boolean;
+ url: string | null;
+ paused?: boolean;
+ } | null>(null);
+ const [connected, setConnected] = useState(false);
+ const [fullScreen, setFullScreen] = useState(false);
+ const [sidebarOpen, setSidebarOpen] = useState(false);
+ const [workingClipboard, setWorkingClipboard] = useState(true);
// noVNC options start
- const [clipboard, setClipboard] = useState("")
- const [viewOnly, setViewOnly] = useState(false)
- const [qualityLevel, setQualityLevel] = useState(6)
- const [compressionLevel, setCompressionLevel] = useState(2)
- const [clipViewport, setClipViewport] = useState(false)
- const [scaling, setScaling] = useState("remote")
+ const [clipboard, setClipboard] = useState("");
+ const [viewOnly, setViewOnly] = useState(false);
+ const [qualityLevel, setQualityLevel] = useState(6);
+ const [compressionLevel, setCompressionLevel] = useState(2);
+ const [clipViewport, setClipViewport] = useState(false);
+ const [scaling, setScaling] = useState("remote");
// noVNC options end
- const searchParams = useSearchParams()
+ const searchParams = useSearchParams();
+ const { mutate } = useSWRConfig();
+ const {
+ data: filesList,
+ error: filesError,
+ isLoading: filesLoading,
+ } = useSWR(`/api/session/files/${params.slug}`, fetcher, { refreshInterval: 10000 });
useEffect(() => {
try {
if (searchParams.get("nocheck") === "true") {
- setSession({ exists: true, url: `/api/vnc/${params.slug}` })
+ setSession({ exists: true, url: `/api/vnc/${params.slug}` });
} else {
fetch(`/api/vnc/${params.slug}`)
.then((res) => res.json())
.then((data) => {
if (data.exists) {
- setSession({ exists: true, url: `/api/vnc/${params.slug}`, paused: data.paused ?? false })
+ setSession({
+ exists: true,
+ url: `/api/vnc/${params.slug}`,
+ paused: data.paused ?? false,
+ });
} else {
- setSession({ exists: false, url: null })
+ setSession({ exists: false, url: null });
}
- })
+ });
}
} catch (error) {
- throw new Error(error as string)
+ throw new Error(error as string);
}
- }, [params.slug, searchParams])
+ }, [params.slug, searchParams]);
useEffect(() => {
if (connected && vncRef.current?.rfb) {
- vncRef.current.rfb.viewOnly = viewOnly
- vncRef.current.rfb.qualityLevel = qualityLevel
- vncRef.current.rfb.compressionLevel = compressionLevel
- vncRef.current.rfb.clipViewport = clipViewport
- vncRef.current.rfb.resizeSession = scaling === "remote"
- vncRef.current.rfb.scaleViewport = scaling === "local"
+ vncRef.current.rfb.viewOnly = viewOnly;
+ vncRef.current.rfb.qualityLevel = qualityLevel;
+ vncRef.current.rfb.compressionLevel = compressionLevel;
+ vncRef.current.rfb.clipViewport = clipViewport;
+ vncRef.current.rfb.resizeSession = scaling === "remote";
+ vncRef.current.rfb.scaleViewport = scaling === "local";
}
- }, [connected, viewOnly, qualityLevel, compressionLevel, scaling, clipViewport])
+ }, [connected, viewOnly, qualityLevel, compressionLevel, scaling, clipViewport]);
// jank, but it works ¯\_(ツ)_/¯
useEffect(() => {
const interval = setInterval(() => {
if (connected && vncRef.current?.rfb && document.hasFocus()) {
- vncRef.current.rfb.focus()
+ vncRef.current.rfb.focus();
navigator.clipboard
.readText()
.then((text) => {
if (text !== clipboard) {
- setWorkingClipboard(true)
- vncRef.current?.clipboardPaste(text)
- setClipboard(text)
+ setWorkingClipboard(true);
+ vncRef.current?.clipboardPaste(text);
+ setClipboard(text);
}
})
- .catch(() => setWorkingClipboard(false))
+ .catch(() => setWorkingClipboard(false));
}
- }, 250)
- return () => clearInterval(interval)
- }, [connected, clipboard])
+ }, 250);
+ return () => clearInterval(interval);
+ }, [connected, clipboard]);
useEffect(() => {
const interval = setInterval(() => {
if (connected && vncRef.current?.rfb) {
fetch("/api/session/preview", {
method: "POST",
- body: JSON.stringify({ imagePreview: vncRef.current.rfb?.toDataURL(), containerId: params.slug }),
- })
+ body: JSON.stringify({
+ imagePreview: vncRef.current.rfb?.toDataURL(undefined, 0.25),
+ containerId: params.slug,
+ }),
+ });
}
- }, 10000)
- return () => clearInterval(interval)
- }, [connected, params.slug])
+ }, 10000);
+ return () => clearInterval(interval);
+ }, [connected, params.slug]);
useEffect(() => {
if (document.fullscreenElement === null && fullScreen) {
- document.documentElement.requestFullscreen()
+ document.documentElement.requestFullscreen();
} else if (document.fullscreenElement !== null && !fullScreen) {
- document.exitFullscreen()
+ document.exitFullscreen();
}
- }, [fullScreen])
+ }, [fullScreen]);
+ // biome-ignore lint:
+ useEffect(() => {
+ if (sidebarOpen) mutate(`/api/session/files/${params.slug}`);
+ }, [sidebarOpen]);
return (
{connected ? (
@@ -149,27 +174,135 @@ export default function View({ params }: { params: { slug: string } }) {
) : null}
-
+
Settings
Clipboard
- Contents of the clipboard will be shared with the remote machine.
+ Contents of the clipboard will be shared with the remote machine.
- )
+ );
}
diff --git a/src/app/view/not-found.tsx b/src/app/view/not-found.tsx
index eae798e..ba899fd 100644
--- a/src/app/view/not-found.tsx
+++ b/src/app/view/not-found.tsx
@@ -6,5 +6,5 @@ export default function NotFound() {
Session not found
- )
+ );
}
diff --git a/src/components/mode-toggle.tsx b/src/components/mode-toggle.tsx
index ef262c3..be60e33 100644
--- a/src/components/mode-toggle.tsx
+++ b/src/components/mode-toggle.tsx
@@ -1,19 +1,19 @@
-"use client"
+"use client";
-import { Button } from "@/components/ui/button"
+import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuTrigger,
-} from "@/components/ui/dropdown-menu"
-import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"
-import { SwatchBook } from "lucide-react"
-import { useTheme } from "next-themes"
+} from "@/components/ui/dropdown-menu";
+import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
+import { SwatchBook } from "lucide-react";
+import { useTheme } from "next-themes";
export default function ModeToggle({ className }: Readonly<{ className?: string }>) {
- const { themes, setTheme } = useTheme()
+ const { themes, setTheme } = useTheme();
return (
@@ -40,5 +40,5 @@ export default function ModeToggle({ className }: Readonly<{ className?: string
))}
- )
+ );
}
diff --git a/src/components/navbar.tsx b/src/components/navbar.tsx
index 00513a7..1d7b556 100644
--- a/src/components/navbar.tsx
+++ b/src/components/navbar.tsx
@@ -1,6 +1,6 @@
-"use client"
+"use client";
-import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
+import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import {
DropdownMenu,
DropdownMenuContent,
@@ -8,28 +8,35 @@ import {
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
-} from "@/components/ui/dropdown-menu"
+ DropdownMenuSub,
+ DropdownMenuSubContent,
+ DropdownMenuSubTrigger,
+ DropdownMenuPortal,
+} from "@/components/ui/dropdown-menu";
import {
NavigationMenu,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
navigationMenuTriggerStyle,
-} from "@/components/ui/navigation-menu"
-import type { SelectUser } from "@/lib/drizzle/schema"
-import { cn } from "@/lib/utils"
-import { ComputerIcon, Settings, Sparkles } from "lucide-react"
-import type { Session } from "next-auth"
-import Link from "next/link"
-import ModeToggle from "./mode-toggle"
+} from "@/components/ui/navigation-menu";
+import type { SelectUser } from "@/lib/drizzle/schema";
+import { cn } from "@/lib/utils";
+import { ComputerIcon, LogOut, Settings, Sparkles, SwatchBook, User } from "lucide-react";
+import type { Route } from "next";
+import type { Session } from "next-auth";
+import Link from "next/link";
+import { Fragment } from "react";
+import { useTheme } from "next-themes";
export default function Navigation({ dbUser, session }: { dbUser: SelectUser; session: Session | null }) {
- const { name, email, image } = session?.user || {}
+ const { name, email, image } = session?.user || {};
+ const { themes, setTheme } = useTheme();
const navigationItems: {
- icon: React.ReactNode
- label: string
- href: string
- adminOnly: boolean
+ icon: React.ReactNode;
+ label: string;
+ href: Route;
+ adminOnly: boolean;
}[] = [
{
icon: ,
@@ -43,31 +50,31 @@ export default function Navigation({ dbUser, session }: { dbUser: SelectUser; se
icon: ,
adminOnly: true,
},
- ]
+ ];
return (
- Stardust
+ Stardust
- {navigationItems.map((item) =>
- // biome-ignore lint: lint/correctness/useJsxKeyInIterable
- !item.adminOnly || (item.adminOnly && dbUser.isAdmin) ? (
-
-
-
- {item.icon} {item.label}
-
-
-
- ) : null,
- )}
+ {navigationItems.map((item) => (
+
+ {!item.adminOnly || (item.adminOnly && dbUser.isAdmin) ? (
+
+
+
+ {item.icon} {item.label}
+
+
+
+ ) : null}
+
+ ))}
-
@@ -79,22 +86,44 @@ export default function Navigation({ dbUser, session }: { dbUser: SelectUser; se
{name || email}
- {dbUser.isAdmin && (
+ {dbUser.isAdmin ? (
Admin
- )}
+ ) : null}
{name ? email : name}
- Profile
+
+
+ Profile
+
+
+
+
+ Theme
+
+
+
+ {themes.map((theme) => (
+ setTheme(theme)}>
+ {theme.charAt(0).toUpperCase() + theme.slice(1)}
+
+ ))}
+
+
+
- Log Out
+
+
+
+ Log Out
+
- )
+ );
}
diff --git a/src/components/submit-button.tsx b/src/components/submit-button.tsx
index 2dc72bf..ff768c3 100644
--- a/src/components/submit-button.tsx
+++ b/src/components/submit-button.tsx
@@ -1,16 +1,16 @@
-"use client"
-import { Loader2 } from "lucide-react"
-import { useFormStatus } from "react-dom"
-import { Button, type ButtonProps } from "./ui/button"
+"use client";
+import { Loader2 } from "lucide-react";
+import { useFormStatus } from "react-dom";
+import { Button, type ButtonProps } from "./ui/button";
export function StyledSubmit({
pendingText = "Loading...",
pendingSpinner = false,
...props
}: ButtonProps & { pendingText?: string; pendingSpinner?: boolean }) {
- const { pending } = useFormStatus()
+ const { pending } = useFormStatus();
return (
{pending ? pendingSpinner ? : pendingText : props.children}
- )
+ );
}
diff --git a/src/components/ui/alert-dialog.tsx b/src/components/ui/alert-dialog.tsx
index 04d1858..43ff998 100644
--- a/src/components/ui/alert-dialog.tsx
+++ b/src/components/ui/alert-dialog.tsx
@@ -1,16 +1,16 @@
-"use client"
+"use client";
-import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
-import * as React from "react"
+import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";
+import * as React from "react";
-import { buttonVariants } from "@/components/ui/button"
-import { cn } from "@/lib/utils"
+import { buttonVariants } from "@/components/ui/button";
+import { cn } from "@/lib/utils";
-const AlertDialog = AlertDialogPrimitive.Root
+const AlertDialog = AlertDialogPrimitive.Root;
-const AlertDialogTrigger = AlertDialogPrimitive.Trigger
+const AlertDialogTrigger = AlertDialogPrimitive.Trigger;
-const AlertDialogPortal = AlertDialogPrimitive.Portal
+const AlertDialogPortal = AlertDialogPrimitive.Portal;
const AlertDialogOverlay = React.forwardRef<
React.ElementRef,
@@ -24,8 +24,8 @@ const AlertDialogOverlay = React.forwardRef<
{...props}
ref={ref}
/>
-))
-AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
+));
+AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName;
const AlertDialogContent = React.forwardRef<
React.ElementRef,
@@ -42,42 +42,42 @@ const AlertDialogContent = React.forwardRef<
{...props}
/>
-))
-AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
+));
+AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName;
const AlertDialogHeader = ({ className, ...props }: React.HTMLAttributes) => (
-)
-AlertDialogHeader.displayName = "AlertDialogHeader"
+);
+AlertDialogHeader.displayName = "AlertDialogHeader";
const AlertDialogFooter = ({ className, ...props }: React.HTMLAttributes) => (
-)
-AlertDialogFooter.displayName = "AlertDialogFooter"
+);
+AlertDialogFooter.displayName = "AlertDialogFooter";
const AlertDialogTitle = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
-))
-AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
+));
+AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName;
const AlertDialogDescription = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
-))
-AlertDialogDescription.displayName = AlertDialogPrimitive.Description.displayName
+));
+AlertDialogDescription.displayName = AlertDialogPrimitive.Description.displayName;
const AlertDialogAction = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
-))
-AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
+));
+AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName;
const AlertDialogCancel = React.forwardRef<
React.ElementRef,
@@ -88,8 +88,8 @@ const AlertDialogCancel = React.forwardRef<
className={cn(buttonVariants({ variant: "outline" }), "mt-2 sm:mt-0", className)}
{...props}
/>
-))
-AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
+));
+AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName;
export {
AlertDialog,
@@ -103,4 +103,4 @@ export {
AlertDialogPortal,
AlertDialogTitle,
AlertDialogTrigger,
-}
+};
diff --git a/src/components/ui/alert.tsx b/src/components/ui/alert.tsx
index 82ba23b..488dc4e 100644
--- a/src/components/ui/alert.tsx
+++ b/src/components/ui/alert.tsx
@@ -1,6 +1,6 @@
-import { cn } from "@/lib/utils"
-import { cva, type VariantProps } from "class-variance-authority"
-import * as React from "react"
+import { cn } from "@/lib/utils";
+import { cva, type VariantProps } from "class-variance-authority";
+import * as React from "react";
const alertVariants = cva(
"relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
@@ -15,28 +15,28 @@ const alertVariants = cva(
variant: "default",
},
},
-)
+);
const Alert = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes & VariantProps
>(({ className, variant, ...props }, ref) => (
-))
-Alert.displayName = "Alert"
+));
+Alert.displayName = "Alert";
const AlertTitle = React.forwardRef>(
({ className, ...props }, ref) => (
),
-)
-AlertTitle.displayName = "AlertTitle"
+);
+AlertTitle.displayName = "AlertTitle";
const AlertDescription = React.forwardRef>(
({ className, ...props }, ref) => (
),
-)
-AlertDescription.displayName = "AlertDescription"
+);
+AlertDescription.displayName = "AlertDescription";
-export { Alert, AlertDescription, AlertTitle }
+export { Alert, AlertDescription, AlertTitle };
diff --git a/src/components/ui/aspect-ratio.tsx b/src/components/ui/aspect-ratio.tsx
new file mode 100644
index 0000000..359bc94
--- /dev/null
+++ b/src/components/ui/aspect-ratio.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio";
+
+const AspectRatio = AspectRatioPrimitive.Root;
+
+export { AspectRatio };
diff --git a/src/components/ui/avatar.tsx b/src/components/ui/avatar.tsx
index cea34bc..6431753 100644
--- a/src/components/ui/avatar.tsx
+++ b/src/components/ui/avatar.tsx
@@ -1,8 +1,8 @@
-"use client"
+"use client";
-import { cn } from "@/lib/utils"
-import * as AvatarPrimitive from "@radix-ui/react-avatar"
-import * as React from "react"
+import { cn } from "@/lib/utils";
+import * as AvatarPrimitive from "@radix-ui/react-avatar";
+import * as React from "react";
const Avatar = React.forwardRef<
React.ElementRef,
@@ -13,16 +13,16 @@ const Avatar = React.forwardRef<
className={cn("relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full", className)}
{...props}
/>
-))
-Avatar.displayName = AvatarPrimitive.Root.displayName
+));
+Avatar.displayName = AvatarPrimitive.Root.displayName;
const AvatarImage = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
-))
-AvatarImage.displayName = AvatarPrimitive.Image.displayName
+));
+AvatarImage.displayName = AvatarPrimitive.Image.displayName;
const AvatarFallback = React.forwardRef<
React.ElementRef,
@@ -33,7 +33,7 @@ const AvatarFallback = React.forwardRef<
className={cn("flex h-full w-full items-center justify-center rounded-full bg-muted", className)}
{...props}
/>
-))
-AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
+));
+AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
-export { Avatar, AvatarFallback, AvatarImage }
+export { Avatar, AvatarFallback, AvatarImage };
diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx
index 5dbaeed..5dc037c 100644
--- a/src/components/ui/button.tsx
+++ b/src/components/ui/button.tsx
@@ -1,7 +1,7 @@
-import { cn } from "@/lib/utils"
-import { Slot } from "@radix-ui/react-slot"
-import { cva, type VariantProps } from "class-variance-authority"
-import * as React from "react"
+import { cn } from "@/lib/utils";
+import { Slot } from "@radix-ui/react-slot";
+import { cva, type VariantProps } from "class-variance-authority";
+import * as React from "react";
const buttonVariants = cva(
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
@@ -27,20 +27,20 @@ const buttonVariants = cva(
size: "default",
},
},
-)
+);
export interface ButtonProps
extends React.ButtonHTMLAttributes,
VariantProps {
- asChild?: boolean
+ asChild?: boolean;
}
const Button = React.forwardRef(
({ className, variant, size, asChild = false, ...props }, ref) => {
- const Comp = asChild ? Slot : "button"
- return
+ const Comp = asChild ? Slot : "button";
+ return ;
},
-)
-Button.displayName = "Button"
+);
+Button.displayName = "Button";
-export { Button, buttonVariants }
+export { Button, buttonVariants };
diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx
index a1f7a33..9621baa 100644
--- a/src/components/ui/card.tsx
+++ b/src/components/ui/card.tsx
@@ -1,42 +1,42 @@
-import { cn } from "@/lib/utils"
-import * as React from "react"
+import { cn } from "@/lib/utils";
+import * as React from "react";
const Card = React.forwardRef>(({ className, ...props }, ref) => (
-))
-Card.displayName = "Card"
+));
+Card.displayName = "Card";
const CardHeader = React.forwardRef>(
({ className, ...props }, ref) => (
),
-)
-CardHeader.displayName = "CardHeader"
+);
+CardHeader.displayName = "CardHeader";
const CardTitle = React.forwardRef>(
({ className, ...props }, ref) => (
),
-)
-CardTitle.displayName = "CardTitle"
+);
+CardTitle.displayName = "CardTitle";
const CardDescription = React.forwardRef>(
({ className, ...props }, ref) => (
),
-)
-CardDescription.displayName = "CardDescription"
+);
+CardDescription.displayName = "CardDescription";
const CardContent = React.forwardRef>(
({ className, ...props }, ref) =>
,
-)
-CardContent.displayName = "CardContent"
+);
+CardContent.displayName = "CardContent";
const CardFooter = React.forwardRef>(
({ className, ...props }, ref) => (
),
-)
-CardFooter.displayName = "CardFooter"
+);
+CardFooter.displayName = "CardFooter";
-export { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle }
+export { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle };
diff --git a/src/components/ui/context-menu.tsx b/src/components/ui/context-menu.tsx
index e546713..7613781 100644
--- a/src/components/ui/context-menu.tsx
+++ b/src/components/ui/context-menu.tsx
@@ -1,27 +1,27 @@
-"use client"
+"use client";
-import * as ContextMenuPrimitive from "@radix-ui/react-context-menu"
-import { Check, ChevronRight, Circle } from "lucide-react"
-import * as React from "react"
+import * as ContextMenuPrimitive from "@radix-ui/react-context-menu";
+import { Check, ChevronRight, Circle } from "lucide-react";
+import * as React from "react";
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/utils";
-const ContextMenu = ContextMenuPrimitive.Root
+const ContextMenu = ContextMenuPrimitive.Root;
-const ContextMenuTrigger = ContextMenuPrimitive.Trigger
+const ContextMenuTrigger = ContextMenuPrimitive.Trigger;
-const ContextMenuGroup = ContextMenuPrimitive.Group
+const ContextMenuGroup = ContextMenuPrimitive.Group;
-const ContextMenuPortal = ContextMenuPrimitive.Portal
+const ContextMenuPortal = ContextMenuPrimitive.Portal;
-const ContextMenuSub = ContextMenuPrimitive.Sub
+const ContextMenuSub = ContextMenuPrimitive.Sub;
-const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup
+const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup;
const ContextMenuSubTrigger = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef & {
- inset?: boolean
+ inset?: boolean;
}
>(({ className, inset, children, ...props }, ref) => (
-))
-ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName
+));
+ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName;
const ContextMenuSubContent = React.forwardRef<
React.ElementRef,
@@ -51,8 +51,8 @@ const ContextMenuSubContent = React.forwardRef<
)}
{...props}
/>
-))
-ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName
+));
+ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName;
const ContextMenuContent = React.forwardRef<
React.ElementRef,
@@ -68,13 +68,13 @@ const ContextMenuContent = React.forwardRef<
{...props}
/>
-))
-ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName
+));
+ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName;
const ContextMenuItem = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef & {
- inset?: boolean
+ inset?: boolean;
}
>(({ className, inset, ...props }, ref) => (
-))
-ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName
+));
+ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName;
const ContextMenuCheckboxItem = React.forwardRef<
React.ElementRef,
@@ -109,8 +109,8 @@ const ContextMenuCheckboxItem = React.forwardRef<
{children}
-))
-ContextMenuCheckboxItem.displayName = ContextMenuPrimitive.CheckboxItem.displayName
+));
+ContextMenuCheckboxItem.displayName = ContextMenuPrimitive.CheckboxItem.displayName;
const ContextMenuRadioItem = React.forwardRef<
React.ElementRef,
@@ -131,13 +131,13 @@ const ContextMenuRadioItem = React.forwardRef<
{children}
-))
-ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName
+));
+ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName;
const ContextMenuLabel = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef & {
- inset?: boolean
+ inset?: boolean;
}
>(({ className, inset, ...props }, ref) => (
-))
-ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName
+));
+ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName;
const ContextMenuSeparator = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
-))
-ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName
+));
+ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName;
const ContextMenuShortcut = ({ className, ...props }: React.HTMLAttributes) => {
- return
-}
-ContextMenuShortcut.displayName = "ContextMenuShortcut"
+ return ;
+};
+ContextMenuShortcut.displayName = "ContextMenuShortcut";
export {
ContextMenu,
@@ -177,4 +177,4 @@ export {
ContextMenuSubContent,
ContextMenuSubTrigger,
ContextMenuTrigger,
-}
+};
diff --git a/src/components/ui/dialog.tsx b/src/components/ui/dialog.tsx
index 5df750d..3032ace 100644
--- a/src/components/ui/dialog.tsx
+++ b/src/components/ui/dialog.tsx
@@ -1,18 +1,18 @@
-"use client"
+"use client";
-import * as DialogPrimitive from "@radix-ui/react-dialog"
-import { X } from "lucide-react"
-import * as React from "react"
+import * as DialogPrimitive from "@radix-ui/react-dialog";
+import { X } from "lucide-react";
+import * as React from "react";
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/utils";
-const Dialog = DialogPrimitive.Root
+const Dialog = DialogPrimitive.Root;
-const DialogTrigger = DialogPrimitive.Trigger
+const DialogTrigger = DialogPrimitive.Trigger;
-const DialogPortal = DialogPrimitive.Portal
+const DialogPortal = DialogPrimitive.Portal;
-const DialogClose = DialogPrimitive.Close
+const DialogClose = DialogPrimitive.Close;
const DialogOverlay = React.forwardRef<
React.ElementRef,
@@ -26,8 +26,8 @@ const DialogOverlay = React.forwardRef<
)}
{...props}
/>
-))
-DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
+));
+DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
const DialogContent = React.forwardRef<
React.ElementRef,
@@ -50,18 +50,18 @@ const DialogContent = React.forwardRef<
-))
-DialogContent.displayName = DialogPrimitive.Content.displayName
+));
+DialogContent.displayName = DialogPrimitive.Content.displayName;
const DialogHeader = ({ className, ...props }: React.HTMLAttributes) => (
-)
-DialogHeader.displayName = "DialogHeader"
+);
+DialogHeader.displayName = "DialogHeader";
const DialogFooter = ({ className, ...props }: React.HTMLAttributes) => (
-)
-DialogFooter.displayName = "DialogFooter"
+);
+DialogFooter.displayName = "DialogFooter";
const DialogTitle = React.forwardRef<
React.ElementRef,
@@ -72,16 +72,16 @@ const DialogTitle = React.forwardRef<
className={cn("text-lg font-semibold leading-none tracking-tight", className)}
{...props}
/>
-))
-DialogTitle.displayName = DialogPrimitive.Title.displayName
+));
+DialogTitle.displayName = DialogPrimitive.Title.displayName;
const DialogDescription = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
-))
-DialogDescription.displayName = DialogPrimitive.Description.displayName
+));
+DialogDescription.displayName = DialogPrimitive.Description.displayName;
export {
Dialog,
@@ -94,4 +94,4 @@ export {
DialogPortal,
DialogTitle,
DialogTrigger,
-}
+};
diff --git a/src/components/ui/dropdown-menu.tsx b/src/components/ui/dropdown-menu.tsx
index b95f5c8..c710f89 100644
--- a/src/components/ui/dropdown-menu.tsx
+++ b/src/components/ui/dropdown-menu.tsx
@@ -1,26 +1,26 @@
-"use client"
+"use client";
-import { cn } from "@/lib/utils"
-import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
-import { Check, ChevronRight, Circle } from "lucide-react"
-import * as React from "react"
+import { cn } from "@/lib/utils";
+import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
+import { Check, ChevronRight, Circle } from "lucide-react";
+import * as React from "react";
-const DropdownMenu = DropdownMenuPrimitive.Root
+const DropdownMenu = DropdownMenuPrimitive.Root;
-const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
+const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
-const DropdownMenuGroup = DropdownMenuPrimitive.Group
+const DropdownMenuGroup = DropdownMenuPrimitive.Group;
-const DropdownMenuPortal = DropdownMenuPrimitive.Portal
+const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
-const DropdownMenuSub = DropdownMenuPrimitive.Sub
+const DropdownMenuSub = DropdownMenuPrimitive.Sub;
-const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
+const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
const DropdownMenuSubTrigger = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef & {
- inset?: boolean
+ inset?: boolean;
}
>(({ className, inset, children, ...props }, ref) => (
-))
-DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName
+));
+DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName;
const DropdownMenuSubContent = React.forwardRef<
React.ElementRef,
@@ -50,8 +50,8 @@ const DropdownMenuSubContent = React.forwardRef<
)}
{...props}
/>
-))
-DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName
+));
+DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName;
const DropdownMenuContent = React.forwardRef<
React.ElementRef,
@@ -68,13 +68,13 @@ const DropdownMenuContent = React.forwardRef<
{...props}
/>
-))
-DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
+));
+DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
const DropdownMenuItem = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef & {
- inset?: boolean
+ inset?: boolean;
}
>(({ className, inset, ...props }, ref) => (
-))
-DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
+));
+DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
const DropdownMenuCheckboxItem = React.forwardRef<
React.ElementRef,
@@ -109,8 +109,8 @@ const DropdownMenuCheckboxItem = React.forwardRef<
{children}
-))
-DropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName
+));
+DropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName;
const DropdownMenuRadioItem = React.forwardRef<
React.ElementRef,
@@ -131,13 +131,13 @@ const DropdownMenuRadioItem = React.forwardRef<
{children}
-))
-DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
+));
+DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
const DropdownMenuLabel = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef & {
- inset?: boolean
+ inset?: boolean;
}
>(({ className, inset, ...props }, ref) => (
-))
-DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
+));
+DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
const DropdownMenuSeparator = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
-))
-DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
+));
+DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
const DropdownMenuShortcut = ({ className, ...props }: React.HTMLAttributes) => {
- return
-}
-DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
+ return ;
+};
+DropdownMenuShortcut.displayName = "DropdownMenuShortcut";
export {
DropdownMenu,
@@ -177,4 +177,4 @@ export {
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
-}
+};
diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx
new file mode 100644
index 0000000..135d96e
--- /dev/null
+++ b/src/components/ui/input.tsx
@@ -0,0 +1,22 @@
+import * as React from "react";
+
+import { cn } from "@/lib/utils";
+
+export interface InputProps extends React.InputHTMLAttributes {}
+
+const Input = React.forwardRef(({ className, type, ...props }, ref) => {
+ return (
+
+ );
+});
+Input.displayName = "Input";
+
+export { Input };
diff --git a/src/components/ui/label.tsx b/src/components/ui/label.tsx
index 734d28f..5b1e4bb 100644
--- a/src/components/ui/label.tsx
+++ b/src/components/ui/label.tsx
@@ -1,19 +1,19 @@
-"use client"
+"use client";
-import * as LabelPrimitive from "@radix-ui/react-label"
-import { cva, type VariantProps } from "class-variance-authority"
-import * as React from "react"
+import * as LabelPrimitive from "@radix-ui/react-label";
+import { cva, type VariantProps } from "class-variance-authority";
+import * as React from "react";
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/utils";
-const labelVariants = cva("text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70")
+const labelVariants = cva("text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70");
const Label = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef & VariantProps
>(({ className, ...props }, ref) => (
-))
-Label.displayName = LabelPrimitive.Root.displayName
+));
+Label.displayName = LabelPrimitive.Root.displayName;
-export { Label }
+export { Label };
diff --git a/src/components/ui/navigation-menu.tsx b/src/components/ui/navigation-menu.tsx
index 0650a70..e7db156 100644
--- a/src/components/ui/navigation-menu.tsx
+++ b/src/components/ui/navigation-menu.tsx
@@ -1,8 +1,8 @@
-import { cn } from "@/lib/utils"
-import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
-import { cva } from "class-variance-authority"
-import { ChevronDown } from "lucide-react"
-import * as React from "react"
+import { cn } from "@/lib/utils";
+import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu";
+import { cva } from "class-variance-authority";
+import { ChevronDown } from "lucide-react";
+import * as React from "react";
const NavigationMenu = React.forwardRef<
React.ElementRef,
@@ -16,8 +16,8 @@ const NavigationMenu = React.forwardRef<
{children}
-))
-NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName
+));
+NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName;
const NavigationMenuList = React.forwardRef<
React.ElementRef,
@@ -28,14 +28,14 @@ const NavigationMenuList = React.forwardRef<
className={cn("group flex flex-1 list-none items-center justify-center space-x-1", className)}
{...props}
/>
-))
-NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName
+));
+NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName;
-const NavigationMenuItem = NavigationMenuPrimitive.Item
+const NavigationMenuItem = NavigationMenuPrimitive.Item;
const navigationMenuTriggerStyle = cva(
"group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50",
-)
+);
const NavigationMenuTrigger = React.forwardRef<
React.ElementRef,
@@ -52,8 +52,8 @@ const NavigationMenuTrigger = React.forwardRef<
aria-hidden="true"
/>
-))
-NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName
+));
+NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName;
const NavigationMenuContent = React.forwardRef<
React.ElementRef,
@@ -67,10 +67,10 @@ const NavigationMenuContent = React.forwardRef<
)}
{...props}
/>
-))
-NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName
+));
+NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName;
-const NavigationMenuLink = NavigationMenuPrimitive.Link
+const NavigationMenuLink = NavigationMenuPrimitive.Link;
const NavigationMenuViewport = React.forwardRef<
React.ElementRef,
@@ -86,8 +86,8 @@ const NavigationMenuViewport = React.forwardRef<
{...props}
/>
-))
-NavigationMenuViewport.displayName = NavigationMenuPrimitive.Viewport.displayName
+));
+NavigationMenuViewport.displayName = NavigationMenuPrimitive.Viewport.displayName;
const NavigationMenuIndicator = React.forwardRef<
React.ElementRef,
@@ -103,8 +103,8 @@ const NavigationMenuIndicator = React.forwardRef<
>
-))
-NavigationMenuIndicator.displayName = NavigationMenuPrimitive.Indicator.displayName
+));
+NavigationMenuIndicator.displayName = NavigationMenuPrimitive.Indicator.displayName;
export {
NavigationMenu,
@@ -116,4 +116,4 @@ export {
NavigationMenuTrigger,
NavigationMenuViewport,
navigationMenuTriggerStyle,
-}
+};
diff --git a/src/components/ui/popover.tsx b/src/components/ui/popover.tsx
index ea45da6..565e87b 100644
--- a/src/components/ui/popover.tsx
+++ b/src/components/ui/popover.tsx
@@ -1,13 +1,13 @@
-"use client"
+"use client";
-import * as PopoverPrimitive from "@radix-ui/react-popover"
-import * as React from "react"
+import * as PopoverPrimitive from "@radix-ui/react-popover";
+import * as React from "react";
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/utils";
-const Popover = PopoverPrimitive.Root
+const Popover = PopoverPrimitive.Root;
-const PopoverTrigger = PopoverPrimitive.Trigger
+const PopoverTrigger = PopoverPrimitive.Trigger;
const PopoverContent = React.forwardRef<
React.ElementRef,
@@ -25,7 +25,7 @@ const PopoverContent = React.forwardRef<
{...props}
/>
-))
-PopoverContent.displayName = PopoverPrimitive.Content.displayName
+));
+PopoverContent.displayName = PopoverPrimitive.Content.displayName;
-export { Popover, PopoverContent, PopoverTrigger }
+export { Popover, PopoverContent, PopoverTrigger };
diff --git a/src/components/ui/select.tsx b/src/components/ui/select.tsx
index a980fca..576bccd 100644
--- a/src/components/ui/select.tsx
+++ b/src/components/ui/select.tsx
@@ -1,16 +1,16 @@
-"use client"
+"use client";
-import * as SelectPrimitive from "@radix-ui/react-select"
-import { Check, ChevronDown, ChevronUp } from "lucide-react"
-import * as React from "react"
+import * as SelectPrimitive from "@radix-ui/react-select";
+import { Check, ChevronDown, ChevronUp } from "lucide-react";
+import * as React from "react";
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/utils";
-const Select = SelectPrimitive.Root
+const Select = SelectPrimitive.Root;
-const SelectGroup = SelectPrimitive.Group
+const SelectGroup = SelectPrimitive.Group;
-const SelectValue = SelectPrimitive.Value
+const SelectValue = SelectPrimitive.Value;
const SelectTrigger = React.forwardRef<
React.ElementRef,
@@ -29,8 +29,8 @@ const SelectTrigger = React.forwardRef<
-))
-SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
+));
+SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
const SelectScrollUpButton = React.forwardRef<
React.ElementRef,
@@ -43,8 +43,8 @@ const SelectScrollUpButton = React.forwardRef<
>
-))
-SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
+));
+SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
const SelectScrollDownButton = React.forwardRef<
React.ElementRef,
@@ -57,8 +57,8 @@ const SelectScrollDownButton = React.forwardRef<
>
-))
-SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName
+));
+SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName;
const SelectContent = React.forwardRef<
React.ElementRef,
@@ -89,16 +89,16 @@ const SelectContent = React.forwardRef<
-))
-SelectContent.displayName = SelectPrimitive.Content.displayName
+));
+SelectContent.displayName = SelectPrimitive.Content.displayName;
const SelectLabel = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
-))
-SelectLabel.displayName = SelectPrimitive.Label.displayName
+));
+SelectLabel.displayName = SelectPrimitive.Label.displayName;
const SelectItem = React.forwardRef<
React.ElementRef,
@@ -120,16 +120,16 @@ const SelectItem = React.forwardRef<
{children}
-))
-SelectItem.displayName = SelectPrimitive.Item.displayName
+));
+SelectItem.displayName = SelectPrimitive.Item.displayName;
const SelectSeparator = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
-))
-SelectSeparator.displayName = SelectPrimitive.Separator.displayName
+));
+SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
export {
Select,
@@ -142,4 +142,4 @@ export {
SelectSeparator,
SelectTrigger,
SelectValue,
-}
+};
diff --git a/src/components/ui/separator.tsx b/src/components/ui/separator.tsx
index d063f31..8764d85 100644
--- a/src/components/ui/separator.tsx
+++ b/src/components/ui/separator.tsx
@@ -1,9 +1,9 @@
-"use client"
+"use client";
-import * as SeparatorPrimitive from "@radix-ui/react-separator"
-import * as React from "react"
+import * as SeparatorPrimitive from "@radix-ui/react-separator";
+import * as React from "react";
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/utils";
const Separator = React.forwardRef<
React.ElementRef,
@@ -16,7 +16,7 @@ const Separator = React.forwardRef<
className={cn("shrink-0 bg-border", orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]", className)}
{...props}
/>
-))
-Separator.displayName = SeparatorPrimitive.Root.displayName
+));
+Separator.displayName = SeparatorPrimitive.Root.displayName;
-export { Separator }
+export { Separator };
diff --git a/src/components/ui/sheet.tsx b/src/components/ui/sheet.tsx
index 6404776..5032577 100644
--- a/src/components/ui/sheet.tsx
+++ b/src/components/ui/sheet.tsx
@@ -1,19 +1,19 @@
-"use client"
+"use client";
-import * as SheetPrimitive from "@radix-ui/react-dialog"
-import { cva, type VariantProps } from "class-variance-authority"
-import { X } from "lucide-react"
-import * as React from "react"
+import * as SheetPrimitive from "@radix-ui/react-dialog";
+import { cva, type VariantProps } from "class-variance-authority";
+import { X } from "lucide-react";
+import * as React from "react";
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/utils";
-const Sheet = SheetPrimitive.Root
+const Sheet = SheetPrimitive.Root;
-const SheetTrigger = SheetPrimitive.Trigger
+const SheetTrigger = SheetPrimitive.Trigger;
-const SheetClose = SheetPrimitive.Close
+const SheetClose = SheetPrimitive.Close;
-const SheetPortal = SheetPrimitive.Portal
+const SheetPortal = SheetPrimitive.Portal;
const SheetOverlay = React.forwardRef<
React.ElementRef,
@@ -27,8 +27,8 @@ const SheetOverlay = React.forwardRef<
{...props}
ref={ref}
/>
-))
-SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
+));
+SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
const sheetVariants = cva(
"fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
@@ -47,7 +47,7 @@ const sheetVariants = cva(
side: "right",
},
},
-)
+);
interface SheetContentProps
extends React.ComponentPropsWithoutRef,
@@ -66,34 +66,34 @@ const SheetContent = React.forwardRef
),
-)
-SheetContent.displayName = SheetPrimitive.Content.displayName
+);
+SheetContent.displayName = SheetPrimitive.Content.displayName;
const SheetHeader = ({ className, ...props }: React.HTMLAttributes) => (
-)
-SheetHeader.displayName = "SheetHeader"
+);
+SheetHeader.displayName = "SheetHeader";
const SheetFooter = ({ className, ...props }: React.HTMLAttributes) => (
-)
-SheetFooter.displayName = "SheetFooter"
+);
+SheetFooter.displayName = "SheetFooter";
const SheetTitle = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
-))
-SheetTitle.displayName = SheetPrimitive.Title.displayName
+));
+SheetTitle.displayName = SheetPrimitive.Title.displayName;
const SheetDescription = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
-))
-SheetDescription.displayName = SheetPrimitive.Description.displayName
+));
+SheetDescription.displayName = SheetPrimitive.Description.displayName;
export {
Sheet,
@@ -106,4 +106,4 @@ export {
SheetPortal,
SheetTitle,
SheetTrigger,
-}
+};
diff --git a/src/components/ui/skeleton.tsx b/src/components/ui/skeleton.tsx
index d8b7ad7..a3f9273 100644
--- a/src/components/ui/skeleton.tsx
+++ b/src/components/ui/skeleton.tsx
@@ -1,7 +1,7 @@
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/utils";
function Skeleton({ className, ...props }: React.HTMLAttributes) {
- return
+ return
;
}
-export { Skeleton }
+export { Skeleton };
diff --git a/src/components/ui/slider.tsx b/src/components/ui/slider.tsx
index 3643e81..17cd6ab 100644
--- a/src/components/ui/slider.tsx
+++ b/src/components/ui/slider.tsx
@@ -1,9 +1,9 @@
-"use client"
+"use client";
-import * as SliderPrimitive from "@radix-ui/react-slider"
-import * as React from "react"
+import * as SliderPrimitive from "@radix-ui/react-slider";
+import * as React from "react";
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/utils";
const Slider = React.forwardRef<
React.ElementRef,
@@ -19,7 +19,7 @@ const Slider = React.forwardRef<
-))
-Slider.displayName = SliderPrimitive.Root.displayName
+));
+Slider.displayName = SliderPrimitive.Root.displayName;
-export { Slider }
+export { Slider };
diff --git a/src/components/ui/sonner.tsx b/src/components/ui/sonner.tsx
index 3495fba..7ae58ed 100644
--- a/src/components/ui/sonner.tsx
+++ b/src/components/ui/sonner.tsx
@@ -1,12 +1,12 @@
-"use client"
+"use client";
-import { useTheme } from "next-themes"
-import { Toaster as Sonner } from "sonner"
+import { useTheme } from "next-themes";
+import { Toaster as Sonner } from "sonner";
-type ToasterProps = React.ComponentProps
+type ToasterProps = React.ComponentProps;
const Toaster = ({ ...props }: ToasterProps) => {
- const { theme = "system" } = useTheme()
+ const { theme = "system" } = useTheme();
return (
{
}}
{...props}
/>
- )
-}
+ );
+};
-export { Toaster }
+export { Toaster };
diff --git a/src/components/ui/switch.tsx b/src/components/ui/switch.tsx
index b4b26a2..dc376c6 100644
--- a/src/components/ui/switch.tsx
+++ b/src/components/ui/switch.tsx
@@ -1,9 +1,9 @@
-"use client"
+"use client";
-import * as SwitchPrimitives from "@radix-ui/react-switch"
-import * as React from "react"
+import * as SwitchPrimitives from "@radix-ui/react-switch";
+import * as React from "react";
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/utils";
const Switch = React.forwardRef<
React.ElementRef,
@@ -23,7 +23,7 @@ const Switch = React.forwardRef<
)}
/>
-))
-Switch.displayName = SwitchPrimitives.Root.displayName
+));
+Switch.displayName = SwitchPrimitives.Root.displayName;
-export { Switch }
+export { Switch };
diff --git a/src/components/ui/tabs.tsx b/src/components/ui/tabs.tsx
new file mode 100644
index 0000000..2093808
--- /dev/null
+++ b/src/components/ui/tabs.tsx
@@ -0,0 +1,55 @@
+"use client";
+
+import * as React from "react";
+import * as TabsPrimitive from "@radix-ui/react-tabs";
+
+import { cn } from "@/lib/utils";
+
+const Tabs = TabsPrimitive.Root;
+
+const TabsList = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+TabsList.displayName = TabsPrimitive.List.displayName;
+
+const TabsTrigger = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
+
+const TabsContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+TabsContent.displayName = TabsPrimitive.Content.displayName;
+
+export { Tabs, TabsList, TabsTrigger, TabsContent };
diff --git a/src/components/ui/textarea.tsx b/src/components/ui/textarea.tsx
index 62ccd0f..415fcd3 100644
--- a/src/components/ui/textarea.tsx
+++ b/src/components/ui/textarea.tsx
@@ -1,6 +1,6 @@
-import * as React from "react"
+import * as React from "react";
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/utils";
export interface TextareaProps extends React.TextareaHTMLAttributes {}
@@ -14,8 +14,8 @@ const Textarea = React.forwardRef(({ classNa
ref={ref}
{...props}
/>
- )
-})
-Textarea.displayName = "Textarea"
+ );
+});
+Textarea.displayName = "Textarea";
-export { Textarea }
+export { Textarea };
diff --git a/src/components/ui/tooltip.tsx b/src/components/ui/tooltip.tsx
index 2d154ad..d40bad1 100644
--- a/src/components/ui/tooltip.tsx
+++ b/src/components/ui/tooltip.tsx
@@ -1,14 +1,14 @@
-"use client"
+"use client";
-import { cn } from "@/lib/utils"
-import * as TooltipPrimitive from "@radix-ui/react-tooltip"
-import * as React from "react"
+import { cn } from "@/lib/utils";
+import * as TooltipPrimitive from "@radix-ui/react-tooltip";
+import * as React from "react";
-const TooltipProvider = TooltipPrimitive.Provider
+const TooltipProvider = TooltipPrimitive.Provider;
-const Tooltip = TooltipPrimitive.Root
+const Tooltip = TooltipPrimitive.Root;
-const TooltipTrigger = TooltipPrimitive.Trigger
+const TooltipTrigger = TooltipPrimitive.Trigger;
const TooltipContent = React.forwardRef<
React.ElementRef,
@@ -23,7 +23,7 @@ const TooltipContent = React.forwardRef<
)}
{...props}
/>
-))
-TooltipContent.displayName = TooltipPrimitive.Content.displayName
+));
+TooltipContent.displayName = TooltipPrimitive.Content.displayName;
-export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger }
+export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger };
diff --git a/src/components/vnc-screen.tsx b/src/components/vnc-screen.tsx
index 3e62817..631ff79 100644
--- a/src/components/vnc-screen.tsx
+++ b/src/components/vnc-screen.tsx
@@ -1,60 +1,60 @@
-"use client"
+"use client";
-import RFB, { type NoVncCredentials, type NoVncEvents, type NoVncOptions } from "@novnc/novnc/core/rfb"
-import { type ReactNode, forwardRef, useEffect, useImperativeHandle, useRef, useState } from "react"
+import RFB, { type NoVncCredentials, type NoVncEvents, type NoVncOptions } from "@novnc/novnc/core/rfb";
+import { type ReactNode, forwardRef, useEffect, useImperativeHandle, useRef, useState } from "react";
-export type rfbOptions = Partial
+export type rfbOptions = Partial;
export type VncViewerProps = {
- url: string
- style?: object
- className?: string
- viewOnly?: boolean
- rfbOptions?: rfbOptions
- focusOnClick?: boolean
- clipViewport?: boolean
- dragViewport?: boolean
- scaleViewport?: boolean
- resizeSession?: boolean
- showDotCursor?: boolean
- background?: string
- qualityLevel?: number
- compressionLevel?: number
- autoConnect?: boolean
- retryDuration?: number
- debug?: boolean
- loader?: ReactNode
- onConnect?: (rfb?: RFB) => void
- onDisconnect?: (rfb?: RFB) => void
- onCredentialsRequired?: (rfb?: RFB) => void
- onSecurityFailure?: (e?: { detail: { status: number; reason: string } }) => void
- onClipboard?: (e?: { detail: { text: string } }) => void
- onBell?: () => void
- onDesktopName?: (e?: { detail: { name: string } }) => void
- onCapabilities?: (e?: { detail: { capabilities: RFB["capabilities"] } }) => void
-}
+ url: string;
+ style?: object;
+ className?: string;
+ viewOnly?: boolean;
+ rfbOptions?: rfbOptions;
+ focusOnClick?: boolean;
+ clipViewport?: boolean;
+ dragViewport?: boolean;
+ scaleViewport?: boolean;
+ resizeSession?: boolean;
+ showDotCursor?: boolean;
+ background?: string;
+ qualityLevel?: number;
+ compressionLevel?: number;
+ autoConnect?: boolean;
+ retryDuration?: number;
+ debug?: boolean;
+ loader?: ReactNode;
+ onConnect?: (rfb?: RFB) => void;
+ onDisconnect?: (rfb?: RFB) => void;
+ onCredentialsRequired?: (rfb?: RFB) => void;
+ onSecurityFailure?: (e?: { detail: { status: number; reason: string } }) => void;
+ onClipboard?: (e?: { detail: { text: string } }) => void;
+ onBell?: () => void;
+ onDesktopName?: (e?: { detail: { name: string } }) => void;
+ onCapabilities?: (e?: { detail: { capabilities: RFB["capabilities"] } }) => void;
+};
export type VncViewerHandle = {
- connect: () => void
- disconnect: () => void
- connected: boolean
- sendCredentials: (credentials: NoVncCredentials) => void
- sendKey: (keysym: number, code: string, down?: boolean) => void
- sendCtrlAltDel: () => void
- focus: () => void
- blur: () => void
- machineShutdown: () => void
- machineReboot: () => void
- machineReset: () => void
- clipboardPaste: (text: string) => void
- rfb: RFB | null
- eventListeners: EventListeners
-}
+ connect: () => void;
+ disconnect: () => void;
+ connected: boolean;
+ sendCredentials: (credentials: NoVncCredentials) => void;
+ sendKey: (keysym: number, code: string, down?: boolean) => void;
+ sendCtrlAltDel: () => void;
+ focus: () => void;
+ blur: () => void;
+ machineShutdown: () => void;
+ machineReboot: () => void;
+ machineReset: () => void;
+ clipboardPaste: (text: string) => void;
+ rfb: RFB | null;
+ eventListeners: EventListeners;
+};
export type EventListeners = {
// biome-ignore lint: lint/suspicious/noExplicitAny
- -readonly [key in keyof typeof Events]?: (e?: any) => void
-}
+ -readonly [key in keyof typeof Events]?: (e?: any) => void;
+};
// biome-ignore lint: whar
export enum Events {
connect,
@@ -98,205 +98,211 @@ const VncScreen = forwardRef(
},
ref,
) => {
- const rfb = useRef(null)
- const connected = useRef(autoConnect)
- const timeouts = useRef>>([])
- const eventListeners = useRef({})
- const screen = useRef(null)
- const [loading, setLoading] = useState(true)
+ const rfb = useRef(null);
+ const connected = useRef(autoConnect);
+ const timeouts = useRef>>([]);
+ const eventListeners = useRef({});
+ const screen = useRef(null);
+ const [loading, setLoading] = useState(true);
const logger = {
log: (...args: string[]) => {
- if (debug) console.log(...args)
+ if (debug) console.log(...args);
},
info: (...args: string[]) => {
- if (debug) console.info(...args)
+ if (debug) console.info(...args);
},
error: (...args: string[]) => {
- if (debug) console.error(...args)
+ if (debug) console.error(...args);
},
- }
- const getRfb = () => rfb.current
+ };
+ const getRfb = () => rfb.current;
// biome-ignore lint: someone was insane, i didn't write most of this
- const setRfb = (_rfb: RFB | null) => (rfb.current = _rfb)
+ const setRfb = (_rfb: RFB | null) => (rfb.current = _rfb);
- const getConnected = () => connected.current
+ const getConnected = () => connected.current;
// biome-ignore lint: someone was insane, i didn't write most of this
- const setConnected = (state: boolean) => (connected.current = state)
+ const setConnected = (state: boolean) => (connected.current = state);
const _onConnect = () => {
- const rfb = getRfb()
+ const rfb = getRfb();
if (onConnect) {
- onConnect(rfb ?? undefined)
- setLoading(false)
+ onConnect(rfb ?? undefined);
+ setLoading(false);
}
- logger.info("Connected to remote VNC.")
- setLoading(false)
- }
+ logger.info("Connected to remote VNC.");
+ setLoading(false);
+ };
const _onDisconnect = () => {
- const rfb = getRfb()
+ const rfb = getRfb();
if (onDisconnect) {
- onDisconnect(rfb ?? undefined)
- setLoading(true)
+ onDisconnect(rfb ?? undefined);
+ setLoading(true);
}
- const connected = getConnected()
+ const connected = getConnected();
if (connected) {
- logger.info(`Unexpectedly disconnected from remote VNC, retrying in ${retryDuration / 1000} seconds.`)
+ logger.info(`Unexpectedly disconnected from remote VNC, retrying in ${retryDuration / 1000} seconds.`);
- timeouts.current.push(setTimeout(connect, retryDuration))
+ timeouts.current.push(setTimeout(connect, retryDuration));
} else {
- logger.info("Disconnected from remote VNC.")
+ logger.info("Disconnected from remote VNC.");
}
- setLoading(true)
- }
+ setLoading(true);
+ };
const _onCredentialsRequired = () => {
- const rfb = getRfb()
+ const rfb = getRfb();
- if (onCredentialsRequired) onCredentialsRequired(rfb ?? undefined)
+ if (onCredentialsRequired) onCredentialsRequired(rfb ?? undefined);
- const password = rfbOptions?.credentials?.password ?? prompt("Password Required:")
+ const password = rfbOptions?.credentials?.password ?? prompt("Password Required:");
- if (password) rfb?.sendCredentials({ password: password } as NoVncCredentials)
- }
+ if (password) rfb?.sendCredentials({ password: password } as NoVncCredentials);
+ };
const _onDesktopName = (e: { detail: { name: string } }) => {
- if (onDesktopName) onDesktopName(e)
+ if (onDesktopName) onDesktopName(e);
- logger.info(`Desktop name is ${e.detail.name}`)
- }
+ logger.info(`Desktop name is ${e.detail.name}`);
+ };
const disconnect = () => {
- let rfb = getRfb()
+ let rfb = getRfb();
try {
if (rfb) {
- // biome-ignore lint: lint/complexity/noForEach
- timeouts.current.forEach((id) => clearTimeout(id))
- // biome-ignore lint: lint/complexity/noForEach
- ;(Object.keys(eventListeners.current) as (keyof typeof Events)[]).forEach((event) => {
+ for (const timeout of timeouts.current) {
+ clearTimeout(timeout);
+ }
+ const eventKeys = Object.keys(eventListeners.current) as (keyof typeof Events)[];
+
+ for (let i = 0; i < eventKeys.length; i++) {
+ const event = eventKeys[i];
if (eventListeners.current[event]) {
// biome-ignore lint: lint/suspicious/noExplicitAny
- rfb?.removeEventListener(event, eventListeners.current[event] as any)
- eventListeners.current[event] = undefined
+ rfb?.removeEventListener(event, eventListeners.current[event] as any);
+ eventListeners.current[event] = undefined;
}
- })
- rfb.disconnect()
- rfb = null
- setRfb(null)
- setConnected(false)
+ }
+
+ rfb.disconnect();
+ rfb = null;
+ setRfb(null);
+ setConnected(false);
// NOTE: This needs to be called since the event listener is removed.
// Even if the event listener is removed after rfb.disconnect(), the disconnect
// event is not fired.
- _onDisconnect()
+ _onDisconnect();
}
} catch (error) {
- logger.error(error as string)
- setRfb(null)
- setConnected(false)
+ logger.error(error as string);
+ setRfb(null);
+ setConnected(false);
}
- }
+ };
const connect = () => {
try {
- if (connected && !!rfb) disconnect()
+ if (connected && !!rfb) disconnect();
if (screen.current) {
- screen.current.innerHTML = ""
+ screen.current.innerHTML = "";
const _rfb = new RFB(
screen.current,
`${window.location.protocol.replace("http", "ws")}//${window.location.host}${url}`,
rfbOptions,
- )
-
- _rfb.viewOnly = viewOnly ?? false
- _rfb.focusOnClick = focusOnClick ?? true
- _rfb.clipViewport = clipViewport ?? false
- _rfb.dragViewport = dragViewport ?? false
- _rfb.resizeSession = resizeSession ?? false
- _rfb.scaleViewport = scaleViewport ?? false
- _rfb.showDotCursor = showDotCursor ?? false
- _rfb.background = background ?? ""
- _rfb.qualityLevel = qualityLevel ?? 6
- _rfb.compressionLevel = compressionLevel ?? 2
- setRfb(_rfb)
-
- eventListeners.current.connect = _onConnect
- eventListeners.current.disconnect = _onDisconnect
- eventListeners.current.credentialsrequired = _onCredentialsRequired
- eventListeners.current.securityfailure = onSecurityFailure
- eventListeners.current.clipboard = onClipboard
- eventListeners.current.bell = onBell
- eventListeners.current.desktopname = _onDesktopName
- eventListeners.current.capabilities = onCapabilities
- // biome-ignore lint: lint/complexity/noForEach
- ;(Object.keys(eventListeners.current) as (keyof typeof Events)[]).forEach((event) => {
+ );
+
+ _rfb.viewOnly = viewOnly ?? false;
+ _rfb.focusOnClick = focusOnClick ?? true;
+ _rfb.clipViewport = clipViewport ?? false;
+ _rfb.dragViewport = dragViewport ?? false;
+ _rfb.resizeSession = resizeSession ?? false;
+ _rfb.scaleViewport = scaleViewport ?? false;
+ _rfb.showDotCursor = showDotCursor ?? false;
+ _rfb.background = background ?? "";
+ _rfb.qualityLevel = qualityLevel ?? 6;
+ _rfb.compressionLevel = compressionLevel ?? 2;
+ setRfb(_rfb);
+
+ eventListeners.current.connect = _onConnect;
+ eventListeners.current.disconnect = _onDisconnect;
+ eventListeners.current.credentialsrequired = _onCredentialsRequired;
+ eventListeners.current.securityfailure = onSecurityFailure;
+ eventListeners.current.clipboard = onClipboard;
+ eventListeners.current.bell = onBell;
+ eventListeners.current.desktopname = _onDesktopName;
+ eventListeners.current.capabilities = onCapabilities;
+ const eventKeys = Object.keys(eventListeners.current) as (keyof typeof Events)[];
+
+ for (let i = 0; i < eventKeys.length; i++) {
+ const event = eventKeys[i];
if (eventListeners.current[event]) {
// biome-ignore lint: lint/suspicious/noExplicitAny
- _rfb.addEventListener(event as keyof NoVncEvents, eventListeners.current[event] as any)
+ _rfb.addEventListener(event as keyof NoVncEvents, eventListeners.current[event] as any);
}
- })
+ }
- setConnected(true)
+ setConnected(true);
}
} catch (error) {
- logger.error(error as string)
+ logger.error(error as string);
}
- }
+ };
const sendCredentials = (credentials: NoVncCredentials) => {
- const rfb = getRfb()
- rfb?.sendCredentials(credentials)
- }
+ const rfb = getRfb();
+ rfb?.sendCredentials(credentials);
+ };
const sendKey = (keysym: number, code: string, down?: boolean) => {
- const rfb = getRfb()
- rfb?.sendKey(keysym, code, down)
- }
+ const rfb = getRfb();
+ rfb?.sendKey(keysym, code, down);
+ };
const sendCtrlAltDel = () => {
- const rfb = getRfb()
- rfb?.sendCtrlAltDel()
- }
+ const rfb = getRfb();
+ rfb?.sendCtrlAltDel();
+ };
const focus = () => {
- const rfb = getRfb()
- rfb?.focus()
- }
+ const rfb = getRfb();
+ rfb?.focus();
+ };
const blur = () => {
- const rfb = getRfb()
- rfb?.blur()
- }
+ const rfb = getRfb();
+ rfb?.blur();
+ };
const machineShutdown = () => {
- const rfb = getRfb()
- rfb?.machineShutdown()
- }
+ const rfb = getRfb();
+ rfb?.machineShutdown();
+ };
const machineReboot = () => {
- const rfb = getRfb()
- rfb?.machineReboot()
- }
+ const rfb = getRfb();
+ rfb?.machineReboot();
+ };
const machineReset = () => {
- const rfb = getRfb()
- rfb?.machineReset()
- }
+ const rfb = getRfb();
+ rfb?.machineReset();
+ };
const clipboardPaste = (text: string) => {
- const rfb = getRfb()
- rfb?.clipboardPasteFrom(text)
- }
+ const rfb = getRfb();
+ rfb?.clipboardPasteFrom(text);
+ };
useImperativeHandle(ref, () => ({
connect,
@@ -313,31 +319,31 @@ const VncScreen = forwardRef(
clipboardPaste,
rfb: rfb.current,
eventListeners: eventListeners.current,
- }))
+ }));
// biome-ignore lint: correctness/useExhaustiveDependencies
useEffect(() => {
- if (autoConnect) connect()
+ if (autoConnect) connect();
- return disconnect
- }, [])
+ return disconnect;
+ }, []);
const handleClick = () => {
- const rfb = getRfb()
+ const rfb = getRfb();
- if (rfb) rfb.focus()
- }
+ if (rfb) rfb.focus();
+ };
const handleMouseEnter = () => {
- if (document.activeElement && document.activeElement instanceof HTMLElement) document.activeElement.blur()
+ if (document.activeElement && document.activeElement instanceof HTMLElement) document.activeElement.blur();
- handleClick()
- }
+ handleClick();
+ };
const handleMouseLeave = () => {
- const rfb = getRfb()
+ const rfb = getRfb();
- if (rfb) rfb.blur()
- }
+ if (rfb) rfb.blur();
+ };
return (
<>
{loading || !url ? loader ?? Loading...
: null}
@@ -351,8 +357,8 @@ const VncScreen = forwardRef(
/>
) : null}
>
- )
+ );
},
-)
-VncScreen.displayName = "VncScreen"
-export default VncScreen
+);
+VncScreen.displayName = "VncScreen";
+export default VncScreen;
diff --git a/src/lib/auth.ts b/src/lib/auth.ts
index c831f8b..62a10f8 100644
--- a/src/lib/auth.ts
+++ b/src/lib/auth.ts
@@ -1,6 +1,6 @@
-import { db, user } from "@/lib/drizzle/db"
-import { type NextAuthOptions, getServerSession } from "next-auth"
-import Auth0 from "next-auth/providers/auth0"
+import { db, user } from "@/lib/drizzle/db";
+import { type NextAuthOptions, getServerSession } from "next-auth";
+import Auth0 from "next-auth/providers/auth0";
const authConfig: NextAuthOptions = {
pages: {
@@ -11,11 +11,11 @@ const authConfig: NextAuthOptions = {
},
callbacks: {
async signIn({ profile }) {
- const { email, name, sub: id } = profile || {}
+ const { email, name, sub: id } = profile || {};
if (email && id) {
- await db.insert(user).values({ id, email, name }).onConflictDoUpdate({ target: user.id, set: { name, email } })
+ await db.insert(user).values({ id, email, name }).onConflictDoUpdate({ target: user.id, set: { name, email } });
}
- return true
+ return true;
},
},
providers: [
@@ -25,6 +25,6 @@ const authConfig: NextAuthOptions = {
issuer: process.env.AUTH0_ISSUER,
}),
],
-}
-const getAuthSession = () => getServerSession(authConfig)
-export { authConfig, getAuthSession }
+};
+const getAuthSession = () => getServerSession(authConfig);
+export { authConfig, getAuthSession };
diff --git a/src/lib/docker.ts b/src/lib/docker.ts
index 0fabc3c..3d6c5d3 100644
--- a/src/lib/docker.ts
+++ b/src/lib/docker.ts
@@ -1,25 +1,25 @@
-import Dockerode, { type DockerOptions } from "dockerode"
+import Dockerode, { type DockerOptions } from "dockerode";
const docker = new Dockerode(
((): DockerOptions => {
- const connectionType = process.env.DOCKER_TYPE
+ const connectionType = process.env.DOCKER_TYPE;
switch (connectionType) {
case "socket": {
return {
socketPath: process.env.DOCKER_SOCKET,
- }
+ };
}
case "http": {
return {
port: process.env.DOCKER_PORT,
protocol: "http",
host: process.env.DOCKER_HOST,
- }
+ };
}
default: {
- throw new Error("Invalid connection type")
+ throw new Error("Invalid connection type");
}
}
})(),
-)
-export default docker
+);
+export default docker;
diff --git a/src/lib/drizzle/db.ts b/src/lib/drizzle/db.ts
index 9212c73..fd06ba0 100644
--- a/src/lib/drizzle/db.ts
+++ b/src/lib/drizzle/db.ts
@@ -1,7 +1,7 @@
-import { drizzle } from "drizzle-orm/postgres-js"
-import postgres from "postgres"
-import * as schema from "./schema"
+import { drizzle } from "drizzle-orm/postgres-js";
+import postgres from "postgres";
+import * as schema from "./schema";
-export const client = postgres(process.env.DATABASE_URL as string)
-export const db = drizzle(client, { schema })
-export * from "./schema"
+export const client = postgres(process.env.DATABASE_URL as string);
+export const db = drizzle(client, { schema });
+export * from "./schema";
diff --git a/src/lib/drizzle/schema.ts b/src/lib/drizzle/schema.ts
index f045862..bac07f2 100644
--- a/src/lib/drizzle/schema.ts
+++ b/src/lib/drizzle/schema.ts
@@ -1,5 +1,5 @@
-import { relations } from "drizzle-orm"
-import { bigint, boolean, integer, pgTable, text, uniqueIndex } from "drizzle-orm/pg-core"
+import { relations } from "drizzle-orm";
+import { bigint, boolean, integer, pgTable, text, uniqueIndex } from "drizzle-orm/pg-core";
export const user = pgTable(
"User",
@@ -12,13 +12,13 @@ export const user = pgTable(
(table) => {
return {
emailKey: uniqueIndex("User_email_key").on(table.email),
- }
+ };
},
-)
-export type SelectUser = typeof user.$inferSelect
+);
+export type SelectUser = typeof user.$inferSelect;
export const userRelations = relations(user, ({ many }) => ({
session: many(session),
-}))
+}));
export const image = pgTable(
"Image",
{
@@ -31,12 +31,12 @@ export const image = pgTable(
(table) => {
return {
dockerImageKey: uniqueIndex("Image_dockerImage_key").on(table.dockerImage),
- }
+ };
},
-)
+);
export const imageRelations = relations(image, ({ many }) => ({
session: many(session),
-}))
+}));
export const session = pgTable("Session", {
id: text("id").primaryKey().notNull(),
dockerImage: text("dockerImage").notNull(),
@@ -47,7 +47,7 @@ export const session = pgTable("Session", {
.references(() => user.id),
vncPort: integer("vncPort").notNull(),
imagePreview: text("imagePreview"),
-})
+});
export const sessionRelations = relations(session, ({ one }) => ({
user: one(user, {
fields: [session.userId],
@@ -57,4 +57,4 @@ export const sessionRelations = relations(session, ({ one }) => ({
fields: [session.dockerImage],
references: [image.dockerImage],
}),
-}))
+}));
diff --git a/src/lib/drizzle/seed.ts b/src/lib/drizzle/seed.ts
index dcfdbf8..430423b 100644
--- a/src/lib/drizzle/seed.ts
+++ b/src/lib/drizzle/seed.ts
@@ -1,14 +1,14 @@
-import "dotenv/config"
-import { drizzle } from "drizzle-orm/node-postgres"
-import pg from "pg"
-import * as schema from "./schema"
-const { image } = schema
+import "dotenv/config";
+import { drizzle } from "drizzle-orm/node-postgres";
+import pg from "pg";
+import * as schema from "./schema";
+const { image } = schema;
const connection = new pg.Client({
connectionString: process.env.DATABASE_URL as string,
-})
-const db = drizzle(connection, { schema })
-;(async () => {
- await connection.connect()
+});
+const db = drizzle(connection, { schema });
+(async () => {
+ await connection.connect();
const insertion = await db
.insert(image)
.values([
@@ -19,9 +19,9 @@ const db = drizzle(connection, { schema })
icon: "/images/workspaces/debian.svg",
},
])
- .returning()
- console.log(insertion)
- console.log("Seeded image")
- connection.end()
- process.exit()
-})()
+ .returning();
+ console.log(insertion);
+ console.log("Seeded image");
+ connection.end();
+ process.exit();
+})();
diff --git a/src/lib/migrate.ts b/src/lib/migrate.ts
index 924f780..311f794 100644
--- a/src/lib/migrate.ts
+++ b/src/lib/migrate.ts
@@ -1,6 +1,6 @@
-import "dotenv/config"
-import { migrate } from "drizzle-orm/postgres-js/migrator"
-import { client, db } from "./drizzle/db"
+import "dotenv/config";
+import { migrate } from "drizzle-orm/postgres-js/migrator";
+import { client, db } from "./drizzle/db";
-await migrate(db, { migrationsFolder: `${process.cwd()}/src/lib/drizzle` })
-await client.end()
+await migrate(db, { migrationsFolder: `${process.cwd()}/src/lib/drizzle` });
+await client.end();
diff --git a/src/lib/util/get-session.ts b/src/lib/util/get-session.ts
index 1ca1ef6..cf3334c 100644
--- a/src/lib/util/get-session.ts
+++ b/src/lib/util/get-session.ts
@@ -1,9 +1,9 @@
-import { db, session, user } from "@/lib/drizzle/db"
-import { and, eq } from "drizzle-orm"
-import type { Session } from "next-auth"
+import { db, session, user } from "@/lib/drizzle/db";
+import { and, eq } from "drizzle-orm";
+import type { Session } from "next-auth";
async function getSession(containerId: string, userSession: Session) {
- if (!userSession || !userSession.user) throw new Error("User not found")
+ if (!userSession || !userSession.user) throw new Error("User not found");
const { userId } = (
await db
.select({
@@ -11,15 +11,15 @@ async function getSession(containerId: string, userSession: Session) {
})
.from(user)
.where(eq(user.email, userSession.user.email as string))
- )[0]
+ )[0];
const containerSession = (
await db
.select()
.from(session)
.where(and(eq(session.id, containerId), eq(session.userId, userId)))
- )[0]
- if (!containerSession) return null
- return containerSession
+ )[0];
+ if (!containerSession) return null;
+ return containerSession;
}
-export { getSession }
+export { getSession };
diff --git a/src/lib/util/session.ts b/src/lib/util/session.ts
index 5cb04c8..d9157fd 100644
--- a/src/lib/util/session.ts
+++ b/src/lib/util/session.ts
@@ -1,22 +1,22 @@
-import docker from "@/lib/docker"
-import { db, session, user } from "@/lib/drizzle/db"
-import type Dockerode from "dockerode"
-import { eq } from "drizzle-orm"
-import type { Session } from "next-auth"
-import { getSession } from "./get-session"
+import docker from "@/lib/docker";
+import { db, session, user } from "@/lib/drizzle/db";
+import type Dockerode from "dockerode";
+import { eq } from "drizzle-orm";
+import type { Session } from "next-auth";
+import { getSession } from "./get-session";
async function createSession(Image: string, userSession: Session) {
- console.log(`Creating session with image ${Image}`)
+ console.log(`Creating session with image ${Image}`);
try {
- if (!userSession || !userSession.user) throw new Error("User not found")
- if (!process.env.DOCKER_PORT_RANGE) throw new Error("Docker port range not set")
- const portsRange = process.env.DOCKER_PORT_RANGE.split("-").map(Number)
- let vncPort: number = Math.floor(Math.random() * (portsRange[1] - portsRange[0] + 1)) + portsRange[0]
+ if (!userSession || !userSession.user) throw new Error("User not found");
+ if (!process.env.DOCKER_PORT_RANGE) throw new Error("Docker port range not set");
+ const portsRange = process.env.DOCKER_PORT_RANGE.split("-").map(Number);
+ let vncPort: number = Math.floor(Math.random() * (portsRange[1] - portsRange[0] + 1)) + portsRange[0];
const portInUse = await docker
.listContainers({ all: true })
- .then((containers) => containers.flatMap((container) => container.Ports?.map((port) => port.PublicPort)))
+ .then((containers) => containers.flatMap((container) => container.Ports?.map((port) => port.PublicPort)));
while (portInUse.includes(vncPort)) {
- vncPort = Math.floor(Math.random() * (portsRange[1] - portsRange[0] + 1)) + portsRange[0]
+ vncPort = Math.floor(Math.random() * (portsRange[1] - portsRange[0] + 1)) + portsRange[0];
}
const container = await docker
.createContainer({
@@ -29,12 +29,12 @@ async function createSession(Image: string, userSession: Session) {
},
})
.catch((error) => {
- throw new Error(`Container·not·created:${error}`)
- })
+ throw new Error(`Container·not·created:${error}`);
+ });
await container.start().catch(() => {
- container.remove({ force: true })
- throw new Error("Container not started")
- })
+ container.remove({ force: true });
+ throw new Error("Container not started");
+ });
const { userId } = (
await db
.select({
@@ -42,7 +42,7 @@ async function createSession(Image: string, userSession: Session) {
})
.from(user)
.where(eq(user.email, userSession.user.email as string))
- )[0]
+ )[0];
return await db
.insert(session)
.values({
@@ -53,32 +53,32 @@ async function createSession(Image: string, userSession: Session) {
createdAt: Date.now(),
expiresAt: Date.now() + 1000 * 60 * 60 * 24,
})
- .returning()
+ .returning();
} catch (error) {
- console.error(error)
+ console.error(error);
}
}
async function manageSession(containerId: string, action: keyof Dockerode.Container, userSession: Session) {
- if (!userSession || !userSession.user) throw new Error("User not found")
- const { id } = (await getSession(containerId, userSession)) || {}
- if (!id) throw new Error("Session not found")
+ if (!userSession || !userSession.user) throw new Error("User not found");
+ const { id } = (await getSession(containerId, userSession)) || {};
+ if (!id) throw new Error("Session not found");
try {
- const container = docker.getContainer(id)
- await container[action]()
+ const container = docker.getContainer(id);
+ await container[action]();
} catch (error) {
- console.error(error)
+ console.error(error);
}
}
async function deleteSession(containerId: string, userSession: Session) {
- if (!userSession || !userSession.user) throw new Error("User not found")
- const { id } = (await getSession(containerId, userSession)) || {}
- if (!id) throw new Error("Session not found")
+ if (!userSession || !userSession.user) throw new Error("User not found");
+ const { id } = (await getSession(containerId, userSession)) || {};
+ if (!id) throw new Error("Session not found");
try {
- const container = docker.getContainer(id)
- await container.remove({ force: true })
- await db.delete(session).where(eq(session.id, id))
+ const container = docker.getContainer(id);
+ await container.remove({ force: true });
+ await db.delete(session).where(eq(session.id, id));
} catch (error) {
- console.error(error)
+ console.error(error);
}
}
-export { createSession, deleteSession, manageSession }
+export { createSession, deleteSession, manageSession };
diff --git a/src/lib/utils.ts b/src/lib/utils.ts
index 9a7122c..afc6737 100644
--- a/src/lib/utils.ts
+++ b/src/lib/utils.ts
@@ -1,6 +1,7 @@
-import { clsx, type ClassValue } from "clsx"
-import { twMerge } from "tailwind-merge"
+import { clsx, type ClassValue } from "clsx";
+import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
- return twMerge(clsx(inputs))
+ return twMerge(clsx(inputs));
}
+export const fetcher = (url: string) => fetch(url).then((res) => res.json());
diff --git a/src/middleware.ts b/src/middleware.ts
index b69812e..2b7f1dd 100644
--- a/src/middleware.ts
+++ b/src/middleware.ts
@@ -1,4 +1,4 @@
-import { withAuth } from "next-auth/middleware"
+import { withAuth } from "next-auth/middleware";
export default withAuth({
pages: {
@@ -7,7 +7,7 @@ export default withAuth({
signOut: "/auth/logout",
error: "/auth/error",
},
-})
+});
export const config = {
matcher: ["/((?!_next/static|_next/image|icon.svg|api/vnc).*)"],
-}
+};
diff --git a/tailwind.config.ts b/tailwind.config.ts
index 01e3a92..a0cf43e 100644
--- a/tailwind.config.ts
+++ b/tailwind.config.ts
@@ -1,4 +1,4 @@
-import type { Config } from "tailwindcss"
+import type { Config } from "tailwindcss";
const config = {
darkMode: ["class"],
@@ -67,9 +67,12 @@ const config = {
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
},
+ fontFamily: {
+ mono: ["var(--mono)"],
+ },
},
},
plugins: [require("tailwindcss-animate"), require("tailwindcss-dotted-background")],
-} satisfies Config
+} satisfies Config;
-export default config
+export default config;