From a59246b1efbed752f301017309cb79a69c4b5e6e Mon Sep 17 00:00:00 2001 From: James Murdza Date: Sun, 1 Dec 2024 11:14:25 -0800 Subject: [PATCH] feat: correctly show whether a project has been deployed --- backend/server/src/DokkuClient.ts | 27 ++++++++++-- backend/server/src/Sandbox.ts | 26 ++++++++++- backend/server/src/index.ts | 2 + frontend/components/editor/navbar/deploy.tsx | 45 ++++++++++++++------ frontend/context/TerminalContext.tsx | 27 ++++++++++++ 5 files changed, 110 insertions(+), 17 deletions(-) diff --git a/backend/server/src/DokkuClient.ts b/backend/server/src/DokkuClient.ts index 38c559d..cefc80b 100644 --- a/backend/server/src/DokkuClient.ts +++ b/backend/server/src/DokkuClient.ts @@ -31,9 +31,30 @@ export class DokkuClient extends SSHSocketClient { // List all deployed Dokku apps async listApps(): Promise { - const response = await this.sendCommand("apps:list") - // Split the output by newline and remove the header - return response.output.split("\n").slice(1) + const response = await this.sendCommand("--quiet apps:list") + return response.output.split("\n") + } + + // Get the creation timestamp of an app + async getAppCreatedAt(appName: string): Promise { + const response = await this.sendCommand( + `apps:report --app-created-at ${appName}` + ) + const createdAt = parseInt(response.output.trim(), 10) + + if (isNaN(createdAt)) { + throw new Error( + `Failed to retrieve creation timestamp for app ${appName}` + ) + } + + return createdAt + } + + // Check if an app exists + async appExists(appName: string): Promise { + const response = await this.sendCommand(`apps:exists ${appName}`) + return response.output.includes("App") === false } } diff --git a/backend/server/src/Sandbox.ts b/backend/server/src/Sandbox.ts index 085135d..88e5870 100644 --- a/backend/server/src/Sandbox.ts +++ b/backend/server/src/Sandbox.ts @@ -156,6 +156,28 @@ export class Sandbox { return { success: true, apps: await this.dokkuClient.listApps() } } + // Handle getting app creation timestamp + const handleGetAppCreatedAt: SocketHandler = async ({ appName }) => { + if (!this.dokkuClient) + throw new Error( + "Failed to retrieve app creation timestamp: No Dokku client" + ) + return { + success: true, + createdAt: await this.dokkuClient.getAppCreatedAt(appName), + } + } + + // Handle checking if an app exists + const handleAppExists: SocketHandler = async ({ appName }) => { + if (!this.dokkuClient) + throw new Error("Failed to check app existence: No Dokku client") + return { + success: true, + exists: await this.dokkuClient.appExists(appName), + } + } + // Handle deploying code const handleDeploy: SocketHandler = async (_: any) => { if (!this.gitClient) throw Error("No git client") @@ -253,7 +275,9 @@ export class Sandbox { getFolder: handleGetFolder, saveFile: handleSaveFile, moveFile: handleMoveFile, - list: handleListApps, + listApps: handleListApps, + getAppCreatedAt: handleGetAppCreatedAt, + getAppExists: handleAppExists, deploy: handleDeploy, createFile: handleCreateFile, createFolder: handleCreateFolder, diff --git a/backend/server/src/index.ts b/backend/server/src/index.ts index 873ab8c..2c8d0a7 100644 --- a/backend/server/src/index.ts +++ b/backend/server/src/index.ts @@ -146,6 +146,8 @@ io.on("connection", async (socket) => { ) }) + socket.emit("ready") + // Handle disconnection event socket.on("disconnect", async () => { try { diff --git a/frontend/components/editor/navbar/deploy.tsx b/frontend/components/editor/navbar/deploy.tsx index 5e42b75..f8db036 100644 --- a/frontend/components/editor/navbar/deploy.tsx +++ b/frontend/components/editor/navbar/deploy.tsx @@ -9,7 +9,7 @@ import { import { useTerminal } from "@/context/TerminalContext" import { Sandbox, User } from "@/lib/types" import { Globe } from "lucide-react" -import { useState } from "react" +import { useEffect, useState } from "react" export default function DeployButtonModal({ userData, @@ -18,8 +18,19 @@ export default function DeployButtonModal({ userData: User data: Sandbox }) { - const { deploy } = useTerminal() + const { deploy, getAppExists } = useTerminal() const [isDeploying, setIsDeploying] = useState(false) + const [isDeployed, setIsDeployed] = useState(false) + + useEffect(() => { + const checkDeployment = async () => { + if (getAppExists) { + const exists = await getAppExists(data.id) + setIsDeployed(exists) + } + } + checkDeployment() + }, [data.id, getAppExists]) const handleDeploy = () => { if (isDeploying) { @@ -30,6 +41,7 @@ export default function DeployButtonModal({ setIsDeploying(true) deploy(() => { setIsDeploying(false) + setIsDeployed(true) }) } } @@ -52,8 +64,9 @@ export default function DeployButtonModal({ } domain={`${data.id}.gitwit.app`} - timestamp="Deployed 1h ago" + timestamp="Deployed 1m ago" user={userData.name} + isDeployed={isDeployed} /> @@ -74,27 +87,33 @@ function DeploymentOption({ domain, timestamp, user, + isDeployed, }: { icon: React.ReactNode domain: string timestamp: string user: string + isDeployed: boolean }) { return (
{icon}
- - {domain} - + {isDeployed ? ( + + {domain} + + ) : ( + {domain} + )}

- {timestamp} • {user} + {isDeployed ? `${timestamp} • ${user}` : "Never deployed"}

) diff --git a/frontend/context/TerminalContext.tsx b/frontend/context/TerminalContext.tsx index 1dd00c2..74a929c 100644 --- a/frontend/context/TerminalContext.tsx +++ b/frontend/context/TerminalContext.tsx @@ -20,6 +20,7 @@ interface TerminalContextType { createNewTerminal: (command?: string) => Promise closeTerminal: (id: string) => void deploy: (callback: () => void) => void + getAppExists: ((appName: string) => Promise) | null } const TerminalContext = createContext( @@ -35,6 +36,19 @@ export const TerminalProvider: React.FC<{ children: React.ReactNode }> = ({ >([]) const [activeTerminalId, setActiveTerminalId] = useState("") const [creatingTerminal, setCreatingTerminal] = useState(false) + const [isSocketReady, setIsSocketReady] = useState(false) + + // Listen for the "ready" signal from the socket + React.useEffect(() => { + if (socket) { + socket.on("ready", () => { + setIsSocketReady(true) + }) + } + return () => { + if (socket) socket.off("ready") + } + }, [socket]) const createNewTerminal = async (command?: string): Promise => { if (!socket) return @@ -78,6 +92,18 @@ export const TerminalProvider: React.FC<{ children: React.ReactNode }> = ({ }) } + const getAppExists = async (appName: string): Promise => { + console.log("Is there a socket: " + !!socket) + if (!socket) { + console.error("Couldn't check if app exists: No socket") + return false + } + const response: { exists: boolean } = await new Promise((resolve) => + socket.emit("getAppExists", { appName }, resolve) + ) + return response.exists + } + const value = { terminals, setTerminals, @@ -88,6 +114,7 @@ export const TerminalProvider: React.FC<{ children: React.ReactNode }> = ({ createNewTerminal, closeTerminal, deploy, + getAppExists: isSocketReady ? getAppExists : null, } return (