Skip to content

Commit

Permalink
feat: stable release!!!
Browse files Browse the repository at this point in the history
* Finish admin dash (mostly)
* use PPR
* add user management stuff
* remove .then
and other things that idk of
  • Loading branch information
IncognitoTGT committed May 29, 2024
1 parent 37edef6 commit d4617d0
Show file tree
Hide file tree
Showing 33 changed files with 1,263 additions and 1,003 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,6 @@ next-env.d.ts
.tmp
docker/
.assets

# IDE
.idea/
3 changes: 1 addition & 2 deletions next.config.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// @ts-check
import NextBundleAnalyzer from "@next/bundle-analyzer";
import { execSync } from "node:child_process";
const withBundleAnalyzer = NextBundleAnalyzer({ enabled: process.env.ANALYZE === "true" });
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
Expand Down Expand Up @@ -51,4 +50,4 @@ const nextConfig = {
},
};

export default withBundleAnalyzer(nextConfig);
export default NextBundleAnalyzer({ enabled: process.env.ANALYZE === "true" })(nextConfig);
12 changes: 7 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "stardust",
"version": "0.7-beta",
"version": "0.8-rc",
"private": true,
"type": "module",
"scripts": {
Expand Down Expand Up @@ -43,29 +43,31 @@
"dockerode": "^4.0.2",
"dotenv": "^16.4.5",
"drizzle-orm": "^0.30.4",
"drizzle-zod": "^0.5.1",
"lucide-react": "^0.358.0",
"next": "15.0.0-rc.0",
"next-auth": "5.0.0-beta.18",
"next-themes": "^0.3.0",
"node-loader": "^2.0.0",
"postgres": "^3.4.4",
"react": "19.0.0-rc-4c2e457c7c-20240522",
"react-dom": "19.0.0-rc-4c2e457c7c-20240522",
"react": "19.0.0-rc-6f23540c7d-20240528",
"react-dom": "19.0.0-rc-6f23540c7d-20240528",
"server-only": "^0.0.1",
"sharp": "^0.33.3",
"sonner": "^1.4.41",
"swr": "^2.2.5",
"tailwind-merge": "^2.2.2",
"tailwindcss-animate": "^1.0.7",
"tsx": "^4.9.1",
"ws": "^8.17.0"
"ws": "^8.17.0",
"zod": "^3.23.8"
},
"devDependencies": {
"@biomejs/biome": "1.7.0",
"@types/node": "^20.11.30",
"@types/novnc__novnc": "^1.3.5",
"@types/pg": "^8.11.4",
"@types/react": "^18.3.2",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"autoprefixer": "^10.4.19",
"drizzle-kit": "^0.21.1",
Expand Down
914 changes: 465 additions & 449 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

87 changes: 42 additions & 45 deletions server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ import { WebSocketServer } from "ws";
const port = Number.parseInt(process.env.PORT as string) || 3000;
const dev = process.env.NODE_ENV !== "production";
if (process.argv.includes("--turbo")) {
process.env.TURBOPACK = "1";

process.env.TURBOPACK = "1";
}
const server = createServer();
const app = next({ dev, port, httpServer: server, hostname: process.env.HOSTNAME });
Expand All @@ -20,55 +19,53 @@ const nextRequest = app.getRequestHandler();
const nextUpgrade = app.getUpgradeHandler();
const websockify = new WebSocketServer({ noServer: true });
websockify.on("connection", async (ws, req) => {
try {
const id = req.url?.split("/")[2];
if (!id) {
ws.close(1008, "Missing ID");
return;
}
const container = docker.getContainer(id);
const ip = await container
.inspect()
.then((data) => data.NetworkSettings.Networks[data.HostConfig.NetworkMode as string].IPAddress);
const socket = new Socket();
socket.connect(5901, ip);
ws.on("message", (message: Uint8Array) => {
socket.write(message);
});
ws.on("close", (code, reason) => {
consola.info(
`✨ Stardust: Connection closed with code ${code} and ${reason ? `reason ${reason.toString()}` : "no reason"}`,
);
socket.end()
});
try {
const id = req.url?.split("/")[2];
if (!id) {
ws.close(1008, "Missing ID");
return;
}
const container = await docker.getContainer(id).inspect();
const ip = container.NetworkSettings.Networks[container.HostConfig.NetworkMode as string].IPAddress;
const socket = new Socket();
socket.connect(5901, ip);
ws.on("message", (message: Uint8Array) => {
socket.write(message);
});
ws.on("close", (code, reason) => {
consola.info(
`✨ Stardust: Connection closed with code ${code} and ${reason ? `reason ${reason.toString()}` : "no reason"}`,
);
socket.end();
});

socket.on("data", (data) => {
ws.send(data);
});
socket.on("data", (data) => {
ws.send(data);
});

socket.on("error", (err) => {
consola.warn(`✨ Stardust: ${err.message}`);
ws.close();
});
socket.on("error", (err) => {
consola.warn(`✨ Stardust: ${err.message}`);
ws.close();
});

socket.on("close", () => {
ws.close();
});
} catch (error) {
ws.close(1008, "Server error");
consola.error(`✨ Stardust: ${(error as Error).message}`);
}
socket.on("close", () => {
ws.close();
});
} catch (error) {
ws.close(1008, "Server error");
consola.error(`✨ Stardust: ${(error as Error).message}`);
}
});
server.on("request", nextRequest);
server.on("upgrade", async (req, socket, head) => {
if (req.url?.includes("websockify")) {
websockify.handleUpgrade(req, socket, head, (ws) => {
websockify.emit("connection", ws, req, websockify);
});
} else {
nextUpgrade(req, socket, head);
}
if (req.url?.includes("websockify")) {
websockify.handleUpgrade(req, socket, head, (ws) => {
websockify.emit("connection", ws, req, websockify);
});
} else {
nextUpgrade(req, socket, head);
}
});
server.listen(port, () => {
consola.success(`✨ Stardust: Server listening on ${port}`);
consola.success(`✨ Stardust: Server listening on ${port}`);
});
56 changes: 56 additions & 0 deletions src/actions/image.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
"use server";

import docker from "@/lib/docker";
import { db } from "@/lib/drizzle/db";
import { image, insertImageSchema } from "@/lib/drizzle/schema";
import { revalidatePath } from "next/cache";

export async function addImage(data: FormData) {
const validatedFields = insertImageSchema.safeParse({
dockerImage: data.get("dockerImage"),
friendlyName: data.get("friendlyName"),
category: data
.get("category")
?.toString()
.split(",")
.map((cat) => cat.trim()),
icon: data.get("icon"),
pulled: true,
});
if (!validatedFields.success) {
return {
errors: validatedFields.error.flatten().fieldErrors,
};
}
await new Promise((resolve, reject) =>
docker.pull(validatedFields.data.dockerImage, (_err: Error, stream: NodeJS.ReadableStream) => {
docker.modem.followProgress(stream, (err, res) => {
if (err) {
reject(err);
} else {
resolve(res);
}
});
}),
);
await db
.insert(image)
.values({
category: [validatedFields.data.category as string],
dockerImage: validatedFields.data.dockerImage,
friendlyName: validatedFields.data.friendlyName,
icon: validatedFields.data.icon,
pulled: validatedFields.data.pulled,
})
.onConflictDoUpdate({
target: image.dockerImage,
set: {
category: [validatedFields.data.category as string],
friendlyName: validatedFields.data.friendlyName,
icon: validatedFields.data.icon,
pulled: validatedFields.data.pulled,
},
});
revalidatePath("/admin/images");
return { success: true };
}
44 changes: 44 additions & 0 deletions src/actions/user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"use server";

import { auth } from "@/lib/auth";
import { db, session, user } from "@/lib/drizzle/db";
import { deleteSession } from "@/lib/session";
import { and, eq } from "drizzle-orm";
import { revalidatePath } from "next/cache";

export async function deleteUser(userId: string) {
const userSession = await auth();
const { sessions, currentUser } = await db.transaction(async (tx) => {
const sessions = await tx.select().from(session).where(eq(session.userId, userId));
const currentUser = await tx.query.user.findFirst({
where: (users, { eq }) => and(eq(users.email, userSession?.user?.email as string), eq(user.id, userId)),
});
return { sessions, currentUser };
});
if (currentUser?.isAdmin) {
throw new Error("Cannot delete the current user");
}
await Promise.all(sessions.map((session) => deleteSession(session.id)));
await db.delete(user).where(eq(user.id, userId));
revalidatePath("/admin/users");
return { success: true };
}
export async function deleteUserSessions(userId: string) {
const sessions = await db.select().from(session).where(eq(session.userId, userId));
await Promise.all(sessions.map((session) => deleteSession(session.id)));
revalidatePath("/admin/users");

return { success: true };
}
export async function changeUserAdminStatus(userId: string, isAdmin: boolean) {
const userSession = await auth();
const currentUser = await db.query.user.findFirst({
where: (users, { eq }) => and(eq(users.email, userSession?.user?.email as string), eq(user.id, userId)),
});
if (currentUser?.isAdmin) {
throw new Error("Cannot change the admin status of the current user");
}
const [update] = await db.update(user).set({ isAdmin }).where(eq(user.id, userId)).returning();
revalidatePath("/admin/users");
return { success: true, admin: update.isAdmin };
}
45 changes: 41 additions & 4 deletions src/app/(main)/admin/images/page.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,59 @@
import { db } from "@/lib/drizzle/db";
import { columns } from "./columns";
import { DataTable } from "@/components/ui/data-table";
import { StyledSubmit } from "@/components/submit-button";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import type { Metadata } from "next";
import { addImage } from "@/actions/image";
export const metadata: Metadata = {
title: "Images",
};
export default async function AdminPage() {
const sessions = await db.query.image.findMany({
const data = await db.query.image.findMany({
with: {
session: true,
},
});
return (
<div className="flex h-full flex-col">
<h1 className="ml-10 py-6 text-3xl font-bold">Images</h1>
<section className="flex justify-center items-start w-full h-full">
<DataTable columns={columns} data={sessions} />
<h1 className="py-6 text-3xl font-bold">Images</h1>
<section className="-ml-8">
<DataTable data={data} columns={columns} />
</section>
<div className="flex justify-start items-center">
<Dialog>
<DialogTrigger asChild>
<Button className="ml-2">Add Image</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Add Image</DialogTitle>
<DialogDescription>The image will automatically save after you click the button.</DialogDescription>
</DialogHeader>
<form action={addImage} className="flex flex-col gap-2 w-full">
<Label htmlFor="name">Name</Label>
<Input id="name" placeholder="Name" name="friendlyName" minLength={2} required />
<Label htmlFor="cat">Category (comma seperated)</Label>
<Input id="cat" placeholder="Category" name="category" />
<Label htmlFor="img">Docker pull URL</Label>
<Input id="img" placeholder="ghcr.io/spaceness/xxxx" name="dockerImage" required />
<Label htmlFor="icon">Icon</Label>
<Input id="icon" placeholder="Icon URL" name="icon" required />
<StyledSubmit>Submit</StyledSubmit>
</form>
</DialogContent>
</Dialog>
</div>
</div>
);
}
2 changes: 1 addition & 1 deletion src/app/(main)/admin/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export default async function AdminLayout({ children }: { children: React.ReactN
return (
<>
<AdminSidebar />
<div className="ml-[4.5rem] h-full">{children}</div>
<div className="ml-48 h-full">{children}</div>
</>
);
}
Loading

0 comments on commit d4617d0

Please sign in to comment.