diff --git a/package.json b/package.json index c4aab68..a304898 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ }, "dependencies": { "@marsidev/react-turnstile": "^0.7.2", + "@monaco-editor/react": "^4.6.0", "@next/bundle-analyzer": "15.0.1", "@novnc/novnc": "^1.5.0", "@paralleldrive/cuid2": "^2.2.2", @@ -44,6 +45,7 @@ "drizzle-zod": "^0.5.1", "input-otp": "^1.2.4", "lucide-react": "^0.358.0", + "monaco-editor": "^0.52.0", "next": "15.0.3-canary.5", "next-auth": "5.0.0-beta.24", "next-themes": "^0.3.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e4c8a25..0938ab0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,6 +15,9 @@ importers: '@marsidev/react-turnstile': specifier: ^0.7.2 version: 0.7.2(react-dom@19.0.0-rc-7c8e5e7a-20241101(react@19.0.0-rc-7c8e5e7a-20241101))(react@19.0.0-rc-7c8e5e7a-20241101) + '@monaco-editor/react': + specifier: ^4.6.0 + version: 4.6.0(monaco-editor@0.52.0)(react-dom@19.0.0-rc-7c8e5e7a-20241101(react@19.0.0-rc-7c8e5e7a-20241101))(react@19.0.0-rc-7c8e5e7a-20241101) '@next/bundle-analyzer': specifier: 15.0.1 version: 15.0.1 @@ -102,6 +105,9 @@ importers: lucide-react: specifier: ^0.358.0 version: 0.358.0(react@19.0.0-rc-7c8e5e7a-20241101) + monaco-editor: + specifier: ^0.52.0 + version: 0.52.0 next: specifier: 15.0.3-canary.5 version: 15.0.3-canary.5(babel-plugin-react-compiler@19.0.0-beta-6fc168f-20241025)(react-dom@19.0.0-rc-7c8e5e7a-20241101(react@19.0.0-rc-7c8e5e7a-20241101))(react@19.0.0-rc-7c8e5e7a-20241101) @@ -866,6 +872,18 @@ packages: react: '>=16.8.0' react-dom: '>=16.8.0' + '@monaco-editor/loader@1.4.0': + resolution: {integrity: sha512-00ioBig0x642hytVspPl7DbQyaSWRaolYie/UFNjoTdvoKPzo6xrXLhTk9ixgIKcLH5b5vDOjVNiGyY+uDCUlg==} + peerDependencies: + monaco-editor: '>= 0.21.0 < 1' + + '@monaco-editor/react@4.6.0': + resolution: {integrity: sha512-RFkU9/i7cN2bsq/iTkurMWOEErmYcY6JiQI3Jn+WeR/FGISH8JbHERjpS9oRuSOPvDMJI0Z8nJeKkbOs9sBYQw==} + peerDependencies: + monaco-editor: '>= 0.25.0 < 1' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + '@next/bundle-analyzer@15.0.1': resolution: {integrity: sha512-i/nCRBGBEkESPDpXJc+6SPLFDItnvTTJSaxiOvuNqHmQjQognRl3BANkKb3nWYy0V5rgzygxu++X349Z4dhs4Q==} @@ -2202,6 +2220,9 @@ packages: mkdirp-classic@0.5.3: resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + monaco-editor@0.52.0: + resolution: {integrity: sha512-OeWhNpABLCeTqubfqLMXGsqf6OmPU6pHM85kF3dhy6kq5hnhuVS1p3VrEW/XhWHc71P2tHyS5JFySD8mgs1crw==} + mrmime@2.0.0: resolution: {integrity: sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==} engines: {node: '>=10'} @@ -2590,6 +2611,9 @@ packages: resolution: {integrity: sha512-C0PHgX4h6lBxYx7hcXwu3QWdh4tg6tZZsTfXcdvc5caW/EMxaB4H9dWsl7qk+F7LAW762hp8VbXOX7x4xUYvEw==} engines: {node: '>=10.16.0'} + state-local@1.0.7: + resolution: {integrity: sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==} + streamsearch@1.1.0: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} @@ -3280,6 +3304,18 @@ snapshots: react: 19.0.0-rc-7c8e5e7a-20241101 react-dom: 19.0.0-rc-7c8e5e7a-20241101(react@19.0.0-rc-7c8e5e7a-20241101) + '@monaco-editor/loader@1.4.0(monaco-editor@0.52.0)': + dependencies: + monaco-editor: 0.52.0 + state-local: 1.0.7 + + '@monaco-editor/react@4.6.0(monaco-editor@0.52.0)(react-dom@19.0.0-rc-7c8e5e7a-20241101(react@19.0.0-rc-7c8e5e7a-20241101))(react@19.0.0-rc-7c8e5e7a-20241101)': + dependencies: + '@monaco-editor/loader': 1.4.0(monaco-editor@0.52.0) + monaco-editor: 0.52.0 + react: 19.0.0-rc-7c8e5e7a-20241101 + react-dom: 19.0.0-rc-7c8e5e7a-20241101(react@19.0.0-rc-7c8e5e7a-20241101) + '@next/bundle-analyzer@15.0.1': dependencies: webpack-bundle-analyzer: 4.10.1 @@ -4568,6 +4604,8 @@ snapshots: mkdirp-classic@0.5.3: {} + monaco-editor@0.52.0: {} + mrmime@2.0.0: {} ms@2.1.2: {} @@ -4924,6 +4962,8 @@ snapshots: cpu-features: 0.0.10 nan: 2.20.0 + state-local@1.0.7: {} + streamsearch@1.1.0: {} string-width@4.2.3: diff --git a/src/app/(main)/admin/config/editor.tsx b/src/app/(main)/admin/config/editor.tsx new file mode 100644 index 0000000..92e373a --- /dev/null +++ b/src/app/(main)/admin/config/editor.tsx @@ -0,0 +1,13 @@ +"use client"; +import Editor from "@monaco-editor/react"; +export default function ConfigEditor({ current }: { current: string }) { + return ( + + ); +} diff --git a/src/app/(main)/admin/config/page.tsx b/src/app/(main)/admin/config/page.tsx new file mode 100644 index 0000000..e835e2c --- /dev/null +++ b/src/app/(main)/admin/config/page.tsx @@ -0,0 +1,16 @@ +import { readFile } from "node:fs"; +import ConfigEditor from "./editor"; + +export default async function Page() { + const configFile = await new Promise((res, rej) => + readFile(`${process.cwd()}/.config/config.json`, (err, data) => { + if (err) rej(err); + res(data); + }), + ); + return ( +
+ +
+ ); +} diff --git a/src/app/(main)/admin/layout.tsx b/src/app/(main)/admin/layout.tsx index 6b3a9a8..21c3005 100644 --- a/src/app/(main)/admin/layout.tsx +++ b/src/app/(main)/admin/layout.tsx @@ -26,3 +26,4 @@ export default async function AdminLayout({ children }: { children: React.ReactN ); } +export const dynamic = "force-dynamic"; diff --git a/src/app/view/[slug]/page.tsx b/src/app/view/[slug]/page.tsx index 49fa6bd..f7985e4 100644 --- a/src/app/view/[slug]/page.tsx +++ b/src/app/view/[slug]/page.tsx @@ -19,7 +19,7 @@ import { Button } from "@/components/ui/button"; import { Label } from "@/components/ui/label"; import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion"; -import { Card } from "@/components/ui/card"; +import { Card, CardContent, CardHeader, CardTitle } 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"; @@ -32,8 +32,12 @@ import { AlertCircle, Camera, Clipboard, + ClipboardCopy, Download, File, + Files, + HardDriveDownload, + HardDriveUpload, Info, LogOut, Maximize, @@ -43,10 +47,8 @@ import { RotateCw, ScreenShareOff, Settings, - Sparkles, Square, TrashIcon, - Upload, } from "lucide-react"; import dynamic from "next/dynamic"; import Link from "next/link"; @@ -55,12 +57,25 @@ import { use, useEffect, useRef, useState } from "react"; import { toast } from "sonner"; import useSWR from "swr"; type ScalingValues = "remote" | "local" | "none"; -const Loading = ({ text }: { text: string }) => ( - - -

{text}

-
-); +import { Loader2 } from "lucide-react"; + +function Loading({ text }: { text: string }) { + return ( +
+ +

{text}

+
+ ); +} +function ConnectionAlert({ text, error }: { text: string; error?: boolean }) { + const Comp = error ? AlertCircle : Info; + return ( +
+ +

{text}

+
+ ); +} const VncScreen = dynamic(() => import("@/components/vnc-screen"), { loading: () => , }); @@ -136,12 +151,16 @@ export default function View(props: { params: Promise<{ slug: string }> }) { useEffect(() => { const interval = setInterval(() => { if (workingClipboard && document.hasFocus()) { - navigator.clipboard.readText().then((text) => { - if (text !== clipboard) { - setClipboard(text); - vncRef.current?.rfb.clipboardPasteFrom(text); - } - }); + vncRef?.current?.rfb.focus(); + navigator.clipboard + .readText() + .then((text) => { + if (text !== clipboard) { + setClipboard(text); + vncRef.current?.rfb.clipboardPasteFrom(text); + } + }) + .catch(() => setWorkingClipboard(false)); } }, 2000); return () => clearInterval(interval); @@ -190,235 +209,228 @@ export default function View(props: { params: Promise<{ slug: string }> }) { filesMutate(); }} > - + - Control Panel - - Manage your session with these controls - + Control Panel -
-
- - +
+ + - - - - - - + + + + + + - - - Confirm session deletion - - Are you sure you want to delete this session? This action is irreversible. - - - - Cancel - { - vncRef.current?.rfb?.disconnect(); - toast.promise( - async () => { - await deleteSession(params.slug); - router.push("/"); - }, - { - loading: "Deleting session...", - success: "Session deleted", - error: "Failed to delete session", - }, - ); - }} - > - - - - - - -
- - - -
- -
- Clipboard -
- -