feat: correctly show whether a project has been deployed
This commit is contained in:
parent
1630a5a9cd
commit
a59246b1ef
@ -31,9 +31,30 @@ export class DokkuClient extends SSHSocketClient {
|
||||
|
||||
// List all deployed Dokku apps
|
||||
async listApps(): Promise<string[]> {
|
||||
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<number> {
|
||||
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<boolean> {
|
||||
const response = await this.sendCommand(`apps:exists ${appName}`)
|
||||
return response.output.includes("App") === false
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -146,6 +146,8 @@ io.on("connection", async (socket) => {
|
||||
)
|
||||
})
|
||||
|
||||
socket.emit("ready")
|
||||
|
||||
// Handle disconnection event
|
||||
socket.on("disconnect", async () => {
|
||||
try {
|
||||
|
@ -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({
|
||||
<DeploymentOption
|
||||
icon={<Globe className="text-gray-500 w-5 h-5" />}
|
||||
domain={`${data.id}.gitwit.app`}
|
||||
timestamp="Deployed 1h ago"
|
||||
timestamp="Deployed 1m ago"
|
||||
user={userData.name}
|
||||
isDeployed={isDeployed}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
@ -61,7 +74,7 @@ export default function DeployButtonModal({
|
||||
className="mt-4 w-full bg-[#0a0a0a] text-white hover:bg-[#262626]"
|
||||
onClick={handleDeploy}
|
||||
>
|
||||
{isDeploying ? "Deploying..." : "Update"}
|
||||
{isDeploying ? "Deploying..." : isDeployed ? "Update" : "Deploy"}
|
||||
</Button>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
@ -74,27 +87,33 @@ function DeploymentOption({
|
||||
domain,
|
||||
timestamp,
|
||||
user,
|
||||
isDeployed,
|
||||
}: {
|
||||
icon: React.ReactNode
|
||||
domain: string
|
||||
timestamp: string
|
||||
user: string
|
||||
isDeployed: boolean
|
||||
}) {
|
||||
return (
|
||||
<div className="flex flex-col gap-2 w-full text-left p-2 rounded-md border border-gray-700 bg-gray-900">
|
||||
<div className="flex items-start gap-2 relative">
|
||||
<div className="flex-shrink-0">{icon}</div>
|
||||
<a
|
||||
href={`https://${domain}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="font-semibold text-gray-300 hover:underline"
|
||||
>
|
||||
{domain}
|
||||
</a>
|
||||
{isDeployed ? (
|
||||
<a
|
||||
href={`https://${domain}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="font-semibold text-gray-300 hover:underline"
|
||||
>
|
||||
{domain}
|
||||
</a>
|
||||
) : (
|
||||
<span className="font-semibold text-gray-300">{domain}</span>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-sm text-gray-400 mt-0 ml-7">
|
||||
{timestamp} • {user}
|
||||
{isDeployed ? `${timestamp} • ${user}` : "Never deployed"}
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
|
@ -20,6 +20,7 @@ interface TerminalContextType {
|
||||
createNewTerminal: (command?: string) => Promise<void>
|
||||
closeTerminal: (id: string) => void
|
||||
deploy: (callback: () => void) => void
|
||||
getAppExists: ((appName: string) => Promise<boolean>) | null
|
||||
}
|
||||
|
||||
const TerminalContext = createContext<TerminalContextType | undefined>(
|
||||
@ -35,6 +36,19 @@ export const TerminalProvider: React.FC<{ children: React.ReactNode }> = ({
|
||||
>([])
|
||||
const [activeTerminalId, setActiveTerminalId] = useState<string>("")
|
||||
const [creatingTerminal, setCreatingTerminal] = useState<boolean>(false)
|
||||
const [isSocketReady, setIsSocketReady] = useState<boolean>(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<void> => {
|
||||
if (!socket) return
|
||||
@ -78,6 +92,18 @@ export const TerminalProvider: React.FC<{ children: React.ReactNode }> = ({
|
||||
})
|
||||
}
|
||||
|
||||
const getAppExists = async (appName: string): Promise<boolean> => {
|
||||
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 (
|
||||
|
Loading…
x
Reference in New Issue
Block a user