diff --git a/index.html b/index.html
index 09c76e3..903ad29 100644
--- a/index.html
+++ b/index.html
@@ -4,7 +4,7 @@
-
Code containers
+ DLinux
diff --git a/src/components/ContainerInfo.tsx b/src/components/ContainerInfo.tsx
new file mode 100644
index 0000000..808f06c
--- /dev/null
+++ b/src/components/ContainerInfo.tsx
@@ -0,0 +1,25 @@
+import { useEffect, useState } from "react";
+import { Typography } from "@mui/material";
+import { InfoData } from "../types";
+
+export default function ContainerInfo() {
+ const [info, setInfo] = useState();
+
+ useEffect(() => {
+ const updateInfo = async () => {
+ const stats = await fetch(`https://api.ssh.surf/info`, {
+ headers: { "x-ssh-auth": localStorage.getItem("key") },
+ });
+
+ const { data } = await stats.json();
+ setInfo(data);
+ };
+ updateInfo();
+ }, []);
+
+ if (!info) {
+ return "Fetching container info...";
+ }
+
+ return (Hostname: {info.name});
+}
diff --git a/src/components/ContainerStats.tsx b/src/components/ContainerStats.tsx
deleted file mode 100644
index 7926148..0000000
--- a/src/components/ContainerStats.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import { useEffect, useState } from "react";
-import { ContainerStats as ContainerStatsType } from "dockerode";
-import { Typography } from "@mui/material";
-
-export default function ContainerStats({ id }: { id: string }) {
- const [stats, setStats] = useState();
-
- useEffect(() => {
- const sse = new EventSource(`/api/containers/${id}/stats`);
-
- sse.onmessage = ({ data }) => {
- const parsed = JSON.parse(data);
- setStats(parsed);
- };
-
- return () => {
- sse.close();
- };
- }, []);
-
- if (!stats) {
- return "Fetching container stats...";
- }
-
- const CPUPercentage =
- ((stats.cpu_stats.cpu_usage.total_usage -
- stats.precpu_stats.cpu_usage.total_usage) /
- (stats.cpu_stats.system_cpu_usage -
- stats.precpu_stats.system_cpu_usage)) *
- stats.cpu_stats.online_cpus *
- 100;
-
- return (
- CPU Usage: {(CPUPercentage || 0).toFixed(2) + "%"}
- );
-}
diff --git a/src/components/Login.tsx b/src/components/Login.tsx
new file mode 100644
index 0000000..8d39ed6
--- /dev/null
+++ b/src/components/Login.tsx
@@ -0,0 +1,40 @@
+import { Button, Paper, TextField } from "@mui/material";
+import { FormEvent } from "react";
+
+export default function Login() {
+ async function onSubmit(e: FormEvent) {
+ e.preventDefault();
+
+ const formData = new FormData(e.target as HTMLFormElement);
+ const data = Object.fromEntries(formData);
+
+ const hello = await fetch(`https://api.ssh.surf/hello`, {
+ headers: { "x-ssh-auth": data.key },
+ });
+
+ const json = await hello.json();
+
+ if (json.message.startsWith("Hello")) {
+ localStorage.setItem("key", data.key.toString());
+ location.reload();
+ }
+ }
+
+ return (
+
+
+
+
+ );
+}
diff --git a/src/components/WebTerminal.tsx b/src/components/WebTerminal.tsx
index 2b59551..af85c52 100644
--- a/src/components/WebTerminal.tsx
+++ b/src/components/WebTerminal.tsx
@@ -4,59 +4,61 @@ import { AttachAddon } from "@xterm/addon-attach";
import { FitAddon } from "@xterm/addon-fit";
import "@xterm/xterm/css/xterm.css";
import { useLoaderData } from "react-router";
-import { Container } from "../types";
+import { InfoData } from "../types";
export default function WebTerminal() {
- const container = useLoaderData() as Container;
- const terminalRef = useRef(null);
+ let container = useLoaderData();
+ container = container.data as InfoData;
- useEffect(() => {
- document.title = `Terminal: ${container.name}`;
+ const terminalRef = useRef(null);
- if (terminalRef.current) {
- const terminal = new Terminal({ rows: 67 });
+ useEffect(() => {
+ document.title = `Terminal: ${container.name}`;
- const socket = new WebSocket(
- `ws://127.0.0.1:3000/containers/${container.id}/terminal`,
- );
+ if (terminalRef.current) {
+ const terminal = new Terminal({ rows: 67 });
- const fitAddon = new FitAddon();
- terminal.loadAddon(fitAddon);
+ const socket = new WebSocket(
+ `ws://127.0.0.1:3000/containers/${container.id}/terminal`,
+ );
- terminal.open(terminalRef.current);
- fitAddon.fit();
+ const fitAddon = new FitAddon();
+ terminal.loadAddon(fitAddon);
- terminal.write("Connecting to the container\r\n");
+ terminal.open(terminalRef.current);
+ fitAddon.fit();
- socket.onopen = () => {
- const attachAddon = new AttachAddon(socket);
- terminal.loadAddon(attachAddon);
+ terminal.write("Connecting to the container\r\n");
- terminal.clear();
- terminal.focus();
+ socket.onopen = () => {
+ const attachAddon = new AttachAddon(socket);
+ terminal.loadAddon(attachAddon);
- socket.send(
- JSON.stringify({
- rows: terminal.rows,
- cols: terminal.cols,
- }),
- );
- };
+ terminal.clear();
+ terminal.focus();
- window.addEventListener("resize", () => {
- fitAddon.fit();
- });
+ socket.send(
+ JSON.stringify({
+ rows: terminal.rows,
+ cols: terminal.cols,
+ }),
+ );
+ };
- terminal.onResize(({ rows, cols }) => {
- socket.send(JSON.stringify({ rows, cols }));
- });
+ window.addEventListener("resize", () => {
+ fitAddon.fit();
+ });
- return () => {
- socket.close();
- terminal.dispose();
- };
- }
- }, []);
+ terminal.onResize(({ rows, cols }) => {
+ socket.send(JSON.stringify({ rows, cols }));
+ });
- return ;
+ return () => {
+ socket.close();
+ terminal.dispose();
+ };
+ }
+ }, []);
+
+ return ;
}
diff --git a/src/main.tsx b/src/main.tsx
index 6903389..f4ddaf4 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -3,41 +3,40 @@ import ReactDOM from "react-dom/client";
import App from "./pages/App.tsx";
import "./index.css";
import { createBrowserRouter, RouterProvider } from "react-router";
-import Containers, {
- Loader as ContainersLoader,
-} from "./pages/containers/index.tsx";
-import ContainerPage, {
- Loader as ContainerDataLoader,
-} from "./pages/containers/[name]/index.tsx";
-import TerminalPage from "./pages/containers/[name]/terminal.tsx";
+import ContainerPage from "./pages/container/index.tsx";
+import TerminalPage from "./pages/container/terminal.tsx";
+
+async function loader() {
+ const info = await fetch(`https://api.ssh.surf/info`, {
+ headers: { "x-ssh-auth": localStorage.getItem("key") },
+ });
+
+ const data = await info.json();
+ return data;
+}
const router = createBrowserRouter([
- {
- path: "/",
- element: ,
- errorElement: ,
- children: [
- {
- path: "/containers",
- element: ,
- loader: ContainersLoader,
- },
- {
- path: "/containers/:name",
- element: ,
- loader: ContainerDataLoader,
- },
- {
- path: "/containers/:name/terminal",
- element: ,
- loader: ContainerDataLoader,
- },
- ],
- },
+ {
+ path: "/",
+ element: ,
+ errorElement: ,
+ children: [
+ {
+ path: "/container",
+ element: ,
+ loader,
+ },
+ {
+ path: "/container/terminal",
+ element: ,
+ loader,
+ },
+ ],
+ },
]);
ReactDOM.createRoot(document.getElementById("root")!).render(
-
-
- ,
+
+
+ ,
);
diff --git a/src/pages/App.tsx b/src/pages/App.tsx
index 2756db2..7b7cc3e 100644
--- a/src/pages/App.tsx
+++ b/src/pages/App.tsx
@@ -3,103 +3,105 @@ import "@fontsource/roboto/400.css";
import "@fontsource/roboto/500.css";
import "@fontsource/roboto/700.css";
import {
- AppBar,
- Box,
- Drawer,
- Icon,
- IconButton,
- List,
- ListItem,
- ListItemButton,
- ListItemIcon,
- ListItemText,
- Toolbar,
- Typography,
+ AppBar,
+ Box,
+ Drawer,
+ Icon,
+ IconButton,
+ List,
+ ListItem,
+ ListItemButton,
+ ListItemIcon,
+ ListItemText,
+ Toolbar,
+ Typography,
} from "@mui/material";
import * as Icons from "@mui/icons-material";
import { useState } from "react";
import { Link, Outlet } from "react-router";
import ErrorPage from "./Error";
+import Login from "../components/Login";
interface Item {
- title: string;
- icon: keyof typeof Icons;
- href: string;
+ title: string;
+ icon: keyof typeof Icons;
+ href: string;
}
const sidebarItems: Item[] = [
- { title: "Containers", icon: "Storage", href: "/containers" },
+ { title: "Info", icon: "Info", href: "/container" },
+ { title: "Terminal", icon: "Terminal", href: "/container/terminal" },
];
const drawerWidth = 240;
function App({ error }: { error?: boolean }) {
- const [isOpen, setOpen] = useState(false);
+ const [isOpen, setOpen] = useState(false);
- return (
- <>
- theme.zIndex.drawer + 1 }}
- >
-
- setOpen(!isOpen)}
- size="large"
- color="inherit"
- sx={{ mr: 2 }}
- >
-
-
-
- Code containers
-
-
-
-
- theme.transitions.create("width", {
- easing: theme.transitions.easing.sharp,
- duration: theme.transitions.duration.enteringScreen,
- }),
- },
- }}
- >
-
-
- {sidebarItems.map((item) => (
-
-
-
-
-
-
-
-
- ))}
-
-
-
-
- {error && }
-
-
- >
- );
+ return (
+ <>
+ theme.zIndex.drawer + 1 }}
+ >
+
+ setOpen(!isOpen)}
+ size="large"
+ color="inherit"
+ sx={{ mr: 2 }}
+ >
+
+
+
+ DLinux
+
+
+
+
+ theme.transitions.create("width", {
+ easing: theme.transitions.easing.sharp,
+ duration: theme.transitions.duration.enteringScreen,
+ }),
+ },
+ }}
+ >
+
+
+ {sidebarItems.map((item) => (
+
+
+
+
+
+
+
+
+ ))}
+
+
+
+
+ {error && }
+ {localStorage.getItem("key") ? : }
+
+ >
+ );
}
export default App;
diff --git a/src/pages/container/index.tsx b/src/pages/container/index.tsx
new file mode 100644
index 0000000..1acb497
--- /dev/null
+++ b/src/pages/container/index.tsx
@@ -0,0 +1,60 @@
+import { useLoaderData, useRevalidator } from "react-router";
+import { InfoData } from "../../types";
+import { Button, ButtonGroup, Paper, Typography } from "@mui/material";
+import { useState } from "react";
+import ContainerInfo from "../../components/ContainerInfo";
+
+export default function ContainerPage() {
+ let container = useLoaderData();
+ if (!container.data) {
+ return "Container not found";
+ }
+
+ container = container.data as InfoData;
+
+ const revalidator = useRevalidator();
+ const [isPowerStateLocked, setPowerStateLocked] = useState();
+
+ return (
+
+
+
+ Managing: {container.name}
+
+
+
+
+
+
+
+
+ );
+
+ async function switchPowerState(state: string) {
+ setPowerStateLocked(true);
+
+ const res = await fetch(`/api/containers/${container.name}/${state}`, {
+ method: "PUT",
+ });
+
+ if (res.ok) {
+ setPowerStateLocked(false);
+ revalidator.revalidate();
+ }
+ }
+}
diff --git a/src/pages/containers/[name]/terminal.tsx b/src/pages/container/terminal.tsx
similarity index 63%
rename from src/pages/containers/[name]/terminal.tsx
rename to src/pages/container/terminal.tsx
index 4c36e93..2767903 100644
--- a/src/pages/containers/[name]/terminal.tsx
+++ b/src/pages/container/terminal.tsx
@@ -1,17 +1,15 @@
import { useLoaderData } from "react-router";
-import { Container } from "../../../types";
+import { Container } from "../../types";
import { Paper, Typography } from "@mui/material";
-import WebTerminal from "../../../components/WebTerminal";
+import WebTerminal from "../../components/WebTerminal";
-interface Params {
- name: string;
-}
+export async function Loader() {
+ const info = await fetch(`https://api.ssh.surf/info`, {
+ headers: { "x-ssh-auth": localStorage["key"] },
+ });
-export async function Loader({ params }: { params: Params }) {
- const container = await fetch(`/api/containers/${params.name}`);
-
- const data = await container.json();
- return data;
+ const data = await info.json();
+ return data;
}
export default function TerminalPage() {
diff --git a/src/pages/containers/[name]/index.tsx b/src/pages/containers/[name]/index.tsx
deleted file mode 100644
index 9bacede..0000000
--- a/src/pages/containers/[name]/index.tsx
+++ /dev/null
@@ -1,73 +0,0 @@
-import { useLoaderData, useRevalidator } from "react-router";
-import { Container } from "../../../types";
-import { Button, ButtonGroup, Paper, Typography } from "@mui/material";
-import { useState } from "react";
-import ContainerStats from "../../../components/ContainerStats";
-
-interface Params {
- name: string;
-}
-
-export async function Loader({ params }: { params: Params }) {
- const container = await fetch(`/api/containers/${params.name}`);
-
- const data = await container.json();
- return data;
-}
-
-export default function ContainerPage() {
- const container = useLoaderData() as Container & { statusCode: number };
-
- if (container.statusCode === 404) {
- return "Container not found";
- }
-
- const revalidator = useRevalidator();
- const [isPowerStateLocked, setPowerStateLocked] = useState();
-
- return (
-
-
-
- Managing: {container.name}
-
-
-
-
-
-
-
-
-
- );
-
- async function switchPowerState(state: string) {
- setPowerStateLocked(true);
-
- const res = await fetch(`/api/containers/${container.name}/${state}`, {
- method: "PUT",
- });
-
- if (res.ok) {
- setPowerStateLocked(false);
- revalidator.revalidate();
- }
- }
-}
diff --git a/src/pages/containers/index.tsx b/src/pages/containers/index.tsx
deleted file mode 100644
index b7e43e7..0000000
--- a/src/pages/containers/index.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-import { useLoaderData } from "react-router";
-import { Container } from "../../types";
-import {
- Button,
- Card,
- CardActions,
- CardContent,
- CardHeader,
- Grid2 as Grid,
-} from "@mui/material";
-
-export async function Loader() {
- const containers = await fetch("/api/containers");
-
- const data = await containers.json();
- return data;
-}
-
-export default function Containers() {
- const containers = useLoaderData() as Container[];
-
- return (
- <>
-
- {containers.map((container) => (
-
-
-
- Image: {container.image}
-
- Status: {container.status}
-
-
-
-
-
-
- ))}
-
- >
- );
-}
diff --git a/src/types.ts b/src/types.ts
index 0781da7..4e3786f 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -1,7 +1,29 @@
-export interface Container {
- id: string;
- name: string;
- image: string;
- status: string;
- ip: string;
+export interface Info {
+ success: boolean;
+ data: InfoData;
+}
+
+export interface InfoData {
+ name: string;
+ IPAddress: string;
+ MacAddress: string;
+ memory: string;
+ cpus: string;
+ restartPolicy: { Name: string; MaximumRetryCount: number };
+ restarts: number;
+ state: {
+ Status: string;
+ Running: boolean;
+ Paused: boolean;
+ Restarting: boolean;
+ OOMKilled: boolean;
+ Dead: boolean;
+ Pid: number;
+ ExitCode: number;
+ Error: string;
+ StartedAt: string;
+ FinishedAt: string;
+ };
+ created: string;
+ image: string;
}