Merge pull request #1510 from Alm0stEthical/canary

Fix: Consistent Component Styling and Server URL
This commit is contained in:
Mauricio Siu
2025-03-16 19:09:26 -06:00
committed by GitHub
14 changed files with 2423 additions and 2400 deletions

View File

@@ -10,14 +10,14 @@ import {
TooltipProvider, TooltipProvider,
TooltipTrigger, TooltipTrigger,
} from "@/components/ui/tooltip"; } from "@/components/ui/tooltip";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import { import {
Ban, Ban,
CheckCircle2, CheckCircle2,
Hammer, Hammer,
HelpCircle,
RefreshCcw, RefreshCcw,
Rocket,
Terminal, Terminal,
} from "lucide-react"; } from "lucide-react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
@@ -33,7 +33,7 @@ export const ShowGeneralApplication = ({ applicationId }: Props) => {
{ {
applicationId, applicationId,
}, },
{ enabled: !!applicationId }, { enabled: !!applicationId }
); );
const { mutateAsync: update } = api.application.update.useMutation(); const { mutateAsync: update } = api.application.update.useMutation();
const { mutateAsync: start, isLoading: isStarting } = const { mutateAsync: start, isLoading: isStarting } =
@@ -55,7 +55,7 @@ export const ShowGeneralApplication = ({ applicationId }: Props) => {
<CardTitle className="text-xl">Deploy Settings</CardTitle> <CardTitle className="text-xl">Deploy Settings</CardTitle>
</CardHeader> </CardHeader>
<CardContent className="flex flex-row gap-4 flex-wrap"> <CardContent className="flex flex-row gap-4 flex-wrap">
<TooltipProvider delayDuration={0}> <TooltipProvider delayDuration={0} disableHoverableContent={false}>
<DialogAction <DialogAction
title="Deploy Application" title="Deploy Application"
description="Are you sure you want to deploy this application?" description="Are you sure you want to deploy this application?"
@@ -68,7 +68,7 @@ export const ShowGeneralApplication = ({ applicationId }: Props) => {
toast.success("Application deployed successfully"); toast.success("Application deployed successfully");
refetch(); refetch();
router.push( router.push(
`/dashboard/project/${data?.projectId}/services/application/${applicationId}?tab=deployments`, `/dashboard/project/${data?.projectId}/services/application/${applicationId}?tab=deployments`
); );
}) })
.catch(() => { .catch(() => {
@@ -76,15 +76,16 @@ export const ShowGeneralApplication = ({ applicationId }: Props) => {
}); });
}} }}
> >
<Tooltip>
<TooltipTrigger asChild>
<Button <Button
variant="default" variant="default"
isLoading={data?.applicationStatus === "running"} isLoading={data?.applicationStatus === "running"}
className="flex items-center gap-1.5" className="flex items-center gap-1.5 group focus-visible:ring-2 focus-visible:ring-offset-2"
> >
<Rocket className="size-4 mr-1" />
Deploy Deploy
<Tooltip> </Button>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
</TooltipTrigger> </TooltipTrigger>
<TooltipPrimitive.Portal> <TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]"> <TooltipContent sideOffset={5} className="z-[60]">
@@ -94,7 +95,6 @@ export const ShowGeneralApplication = ({ applicationId }: Props) => {
</TooltipContent> </TooltipContent>
</TooltipPrimitive.Portal> </TooltipPrimitive.Portal>
</Tooltip> </Tooltip>
</Button>
</DialogAction> </DialogAction>
<DialogAction <DialogAction
title="Reload Application" title="Reload Application"
@@ -114,10 +114,26 @@ export const ShowGeneralApplication = ({ applicationId }: Props) => {
}); });
}} }}
> >
<Button variant="secondary" isLoading={isReloading}> <Tooltip>
<TooltipTrigger asChild>
<Button
variant="secondary"
isLoading={isReloading}
className="flex items-center gap-1.5 group focus-visible:ring-2 focus-visible:ring-offset-2"
>
<RefreshCcw className="size-4 mr-1" />
Reload Reload
<RefreshCcw className="size-4" />
</Button> </Button>
</TooltipTrigger>
<TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]">
<p>
Reload the application when you change configuration or
environment variables
</p>
</TooltipContent>
</TooltipPrimitive.Portal>
</Tooltip>
</DialogAction> </DialogAction>
<DialogAction <DialogAction
title="Rebuild Application" title="Rebuild Application"
@@ -136,27 +152,25 @@ export const ShowGeneralApplication = ({ applicationId }: Props) => {
}); });
}} }}
> >
<Tooltip>
<TooltipTrigger asChild>
<Button <Button
variant="secondary" variant="secondary"
isLoading={data?.applicationStatus === "running"} isLoading={data?.applicationStatus === "running"}
className="flex items-center gap-1.5" className="flex items-center gap-1.5 group focus-visible:ring-2 focus-visible:ring-offset-2"
> >
<Hammer className="size-4 mr-1" />
Rebuild Rebuild
<Hammer className="size-4" /> </Button>
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
</TooltipTrigger> </TooltipTrigger>
<TooltipPrimitive.Portal> <TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]"> <TooltipContent sideOffset={5} className="z-[60]">
<p> <p>
Only rebuilds the application without downloading new Only rebuilds the application without downloading new code
code
</p> </p>
</TooltipContent> </TooltipContent>
</TooltipPrimitive.Portal> </TooltipPrimitive.Portal>
</Tooltip> </Tooltip>
</Button>
</DialogAction> </DialogAction>
{data?.applicationStatus === "idle" ? ( {data?.applicationStatus === "idle" ? (
@@ -177,16 +191,16 @@ export const ShowGeneralApplication = ({ applicationId }: Props) => {
}); });
}} }}
> >
<Tooltip>
<TooltipTrigger asChild>
<Button <Button
variant="secondary" variant="secondary"
isLoading={isStarting} isLoading={isStarting}
className="flex items-center gap-1.5" className="flex items-center gap-1.5 group focus-visible:ring-2 focus-visible:ring-offset-2"
> >
<CheckCircle2 className="size-4 mr-1" />
Start Start
<CheckCircle2 className="size-4" /> </Button>
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
</TooltipTrigger> </TooltipTrigger>
<TooltipPrimitive.Portal> <TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]"> <TooltipContent sideOffset={5} className="z-[60]">
@@ -197,7 +211,6 @@ export const ShowGeneralApplication = ({ applicationId }: Props) => {
</TooltipContent> </TooltipContent>
</TooltipPrimitive.Portal> </TooltipPrimitive.Portal>
</Tooltip> </Tooltip>
</Button>
</DialogAction> </DialogAction>
) : ( ) : (
<DialogAction <DialogAction
@@ -216,16 +229,16 @@ export const ShowGeneralApplication = ({ applicationId }: Props) => {
}); });
}} }}
> >
<Tooltip>
<TooltipTrigger asChild>
<Button <Button
variant="destructive" variant="destructive"
isLoading={isStopping} isLoading={isStopping}
className="flex items-center gap-1.5" className="flex items-center gap-1.5 group focus-visible:ring-2 focus-visible:ring-offset-2"
> >
<Ban className="size-4 mr-1" />
Stop Stop
<Ban className="size-4" /> </Button>
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
</TooltipTrigger> </TooltipTrigger>
<TooltipPrimitive.Portal> <TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]"> <TooltipContent sideOffset={5} className="z-[60]">
@@ -233,7 +246,6 @@ export const ShowGeneralApplication = ({ applicationId }: Props) => {
</TooltipContent> </TooltipContent>
</TooltipPrimitive.Portal> </TooltipPrimitive.Portal>
</Tooltip> </Tooltip>
</Button>
</DialogAction> </DialogAction>
)} )}
</TooltipProvider> </TooltipProvider>
@@ -241,15 +253,18 @@ export const ShowGeneralApplication = ({ applicationId }: Props) => {
appName={data?.appName || ""} appName={data?.appName || ""}
serverId={data?.serverId || ""} serverId={data?.serverId || ""}
> >
<Button variant="outline"> <Button
<Terminal /> variant="outline"
className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
>
<Terminal className="size-4 mr-1" />
Open Terminal Open Terminal
</Button> </Button>
</DockerTerminalModal> </DockerTerminalModal>
<div className="flex flex-row items-center gap-2 rounded-md px-4 py-2 border"> <div className="flex flex-row items-center gap-2 rounded-md px-4 py-2 border">
<span className="text-sm font-medium">Autodeploy</span> <span className="text-sm font-medium">Autodeploy</span>
<Switch <Switch
aria-label="Toggle italic" aria-label="Toggle autodeploy"
checked={data?.autoDeploy || false} checked={data?.autoDeploy || false}
onCheckedChange={async (enabled) => { onCheckedChange={async (enabled) => {
await update({ await update({
@@ -264,14 +279,14 @@ export const ShowGeneralApplication = ({ applicationId }: Props) => {
toast.error("Error updating Auto Deploy"); toast.error("Error updating Auto Deploy");
}); });
}} }}
className="flex flex-row gap-2 items-center" className="flex flex-row gap-2 items-center data-[state=checked]:bg-primary"
/> />
</div> </div>
<div className="flex flex-row items-center gap-2 rounded-md px-4 py-2 border"> <div className="flex flex-row items-center gap-2 rounded-md px-4 py-2 border">
<span className="text-sm font-medium">Clean Cache</span> <span className="text-sm font-medium">Clean Cache</span>
<Switch <Switch
aria-label="Toggle italic" aria-label="Toggle clean cache"
checked={data?.cleanCache || false} checked={data?.cleanCache || false}
onCheckedChange={async (enabled) => { onCheckedChange={async (enabled) => {
await update({ await update({
@@ -286,7 +301,7 @@ export const ShowGeneralApplication = ({ applicationId }: Props) => {
toast.error("Error updating Clean Cache"); toast.error("Error updating Clean Cache");
}); });
}} }}
className="flex flex-row gap-2 items-center" className="flex flex-row gap-2 items-center data-[state=checked]:bg-primary"
/> />
</div> </div>
</CardContent> </CardContent>

View File

@@ -7,9 +7,9 @@ import {
TooltipProvider, TooltipProvider,
TooltipTrigger, TooltipTrigger,
} from "@/components/ui/tooltip"; } from "@/components/ui/tooltip";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import { Ban, CheckCircle2, Hammer, HelpCircle, Terminal } from "lucide-react"; import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import { Ban, CheckCircle2, RefreshCcw, Rocket, Terminal } from "lucide-react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { toast } from "sonner"; import { toast } from "sonner";
import { DockerTerminalModal } from "../../settings/web-server/docker-terminal-modal"; import { DockerTerminalModal } from "../../settings/web-server/docker-terminal-modal";
@@ -23,7 +23,7 @@ export const ComposeActions = ({ composeId }: Props) => {
{ {
composeId, composeId,
}, },
{ enabled: !!composeId }, { enabled: !!composeId }
); );
const { mutateAsync: update } = api.compose.update.useMutation(); const { mutateAsync: update } = api.compose.update.useMutation();
const { mutateAsync: deploy } = api.compose.deploy.useMutation(); const { mutateAsync: deploy } = api.compose.deploy.useMutation();
@@ -34,7 +34,7 @@ export const ComposeActions = ({ composeId }: Props) => {
api.compose.stop.useMutation(); api.compose.stop.useMutation();
return ( return (
<div className="flex flex-row gap-4 w-full flex-wrap "> <div className="flex flex-row gap-4 w-full flex-wrap ">
<TooltipProvider delayDuration={0}> <TooltipProvider delayDuration={0} disableHoverableContent={false}>
<DialogAction <DialogAction
title="Deploy Compose" title="Deploy Compose"
description="Are you sure you want to deploy this compose?" description="Are you sure you want to deploy this compose?"
@@ -47,7 +47,7 @@ export const ComposeActions = ({ composeId }: Props) => {
toast.success("Compose deployed successfully"); toast.success("Compose deployed successfully");
refetch(); refetch();
router.push( router.push(
`/dashboard/project/${data?.project.projectId}/services/compose/${composeId}?tab=deployments`, `/dashboard/project/${data?.project.projectId}/services/compose/${composeId}?tab=deployments`
); );
}) })
.catch(() => { .catch(() => {
@@ -55,15 +55,16 @@ export const ComposeActions = ({ composeId }: Props) => {
}); });
}} }}
> >
<Tooltip>
<TooltipTrigger asChild>
<Button <Button
variant="default" variant="default"
isLoading={data?.composeStatus === "running"} isLoading={data?.composeStatus === "running"}
className="flex items-center gap-1.5" className="flex items-center gap-1.5 group focus-visible:ring-2 focus-visible:ring-offset-2"
> >
<Rocket className="size-4 mr-1" />
Deploy Deploy
<Tooltip> </Button>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
</TooltipTrigger> </TooltipTrigger>
<TooltipPrimitive.Portal> <TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]"> <TooltipContent sideOffset={5} className="z-[60]">
@@ -71,43 +72,44 @@ export const ComposeActions = ({ composeId }: Props) => {
</TooltipContent> </TooltipContent>
</TooltipPrimitive.Portal> </TooltipPrimitive.Portal>
</Tooltip> </Tooltip>
</Button>
</DialogAction> </DialogAction>
<DialogAction <DialogAction
title="Rebuild Compose" title="Reload Compose"
description="Are you sure you want to rebuild this compose?" description="Are you sure you want to reload this compose?"
type="default" type="default"
onClick={async () => { onClick={async () => {
await redeploy({ await redeploy({
composeId: composeId, composeId: composeId,
}) })
.then(() => { .then(() => {
toast.success("Compose rebuilt successfully"); toast.success("Compose reloaded successfully");
refetch(); refetch();
}) })
.catch(() => { .catch(() => {
toast.error("Error rebuilding compose"); toast.error("Error reloading compose");
}); });
}} }}
> >
<Tooltip>
<TooltipTrigger asChild>
<Button <Button
variant="secondary" variant="secondary"
isLoading={data?.composeStatus === "running"} isLoading={data?.composeStatus === "running"}
className="flex items-center gap-1.5" className="flex items-center gap-1.5 group focus-visible:ring-2 focus-visible:ring-offset-2"
> >
Rebuild <RefreshCcw className="size-4 mr-1" />
<Hammer className="size-4" /> Reload
<Tooltip> </Button>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
</TooltipTrigger> </TooltipTrigger>
<TooltipPrimitive.Portal> <TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]"> <TooltipContent sideOffset={5} className="z-[60]">
<p>Only rebuilds the compose without downloading new code</p> <p>
Reload the compose when you change configuration or
environment variables
</p>
</TooltipContent> </TooltipContent>
</TooltipPrimitive.Portal> </TooltipPrimitive.Portal>
</Tooltip> </Tooltip>
</Button>
</DialogAction> </DialogAction>
{data?.composeType === "docker-compose" && {data?.composeType === "docker-compose" &&
data?.composeStatus === "idle" ? ( data?.composeStatus === "idle" ? (
@@ -128,16 +130,16 @@ export const ComposeActions = ({ composeId }: Props) => {
}); });
}} }}
> >
<Tooltip>
<TooltipTrigger asChild>
<Button <Button
variant="secondary" variant="secondary"
isLoading={isStarting} isLoading={isStarting}
className="flex items-center gap-1.5" className="flex items-center gap-1.5 group focus-visible:ring-2 focus-visible:ring-offset-2"
> >
<CheckCircle2 className="size-4 mr-1" />
Start Start
<CheckCircle2 className="size-4" /> </Button>
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
</TooltipTrigger> </TooltipTrigger>
<TooltipPrimitive.Portal> <TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]"> <TooltipContent sideOffset={5} className="z-[60]">
@@ -147,7 +149,6 @@ export const ComposeActions = ({ composeId }: Props) => {
</TooltipContent> </TooltipContent>
</TooltipPrimitive.Portal> </TooltipPrimitive.Portal>
</Tooltip> </Tooltip>
</Button>
</DialogAction> </DialogAction>
) : ( ) : (
<DialogAction <DialogAction
@@ -166,16 +167,16 @@ export const ComposeActions = ({ composeId }: Props) => {
}); });
}} }}
> >
<Tooltip>
<TooltipTrigger asChild>
<Button <Button
variant="destructive" variant="destructive"
isLoading={isStopping} isLoading={isStopping}
className="flex items-center gap-1.5" className="flex items-center gap-1.5 group focus-visible:ring-2 focus-visible:ring-offset-2"
> >
<Ban className="size-4 mr-1" />
Stop Stop
<Ban className="size-4" /> </Button>
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
</TooltipTrigger> </TooltipTrigger>
<TooltipPrimitive.Portal> <TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]"> <TooltipContent sideOffset={5} className="z-[60]">
@@ -183,7 +184,6 @@ export const ComposeActions = ({ composeId }: Props) => {
</TooltipContent> </TooltipContent>
</TooltipPrimitive.Portal> </TooltipPrimitive.Portal>
</Tooltip> </Tooltip>
</Button>
</DialogAction> </DialogAction>
)} )}
</TooltipProvider> </TooltipProvider>
@@ -191,15 +191,18 @@ export const ComposeActions = ({ composeId }: Props) => {
appName={data?.appName || ""} appName={data?.appName || ""}
serverId={data?.serverId || ""} serverId={data?.serverId || ""}
> >
<Button variant="outline"> <Button
<Terminal /> variant="outline"
className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
>
<Terminal className="size-4 mr-1" />
Open Terminal Open Terminal
</Button> </Button>
</DockerTerminalModal> </DockerTerminalModal>
<div className="flex flex-row items-center gap-2 rounded-md px-4 py-2 border"> <div className="flex flex-row items-center gap-2 rounded-md px-4 py-2 border">
<span className="text-sm font-medium">Autodeploy</span> <span className="text-sm font-medium">Autodeploy</span>
<Switch <Switch
aria-label="Toggle italic" aria-label="Toggle autodeploy"
checked={data?.autoDeploy || false} checked={data?.autoDeploy || false}
onCheckedChange={async (enabled) => { onCheckedChange={async (enabled) => {
await update({ await update({
@@ -214,7 +217,7 @@ export const ComposeActions = ({ composeId }: Props) => {
toast.error("Error updating Auto Deploy"); toast.error("Error updating Auto Deploy");
}); });
}} }}
className="flex flex-row gap-2 items-center" className="flex flex-row gap-2 items-center data-[state=checked]:bg-primary"
/> />
</div> </div>
</div> </div>

View File

@@ -1,6 +1,6 @@
import { AlertBlock } from "@/components/shared/alert-block";
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input"; import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { AlertBlock } from "@/components/shared/alert-block";
import { import {
Card, Card,
CardContent, CardContent,
@@ -20,11 +20,11 @@ import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import Link from "next/link";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { toast } from "sonner"; import { toast } from "sonner";
import { z } from "zod"; import { z } from "zod";
import Link from "next/link";
const DockerProviderSchema = z.object({ const DockerProviderSchema = z.object({
externalPort: z.preprocess((a) => { externalPort: z.preprocess((a) => {
@@ -33,11 +33,7 @@ const DockerProviderSchema = z.object({
return Number.isNaN(parsed) ? null : parsed; return Number.isNaN(parsed) ? null : parsed;
} }
return null; return null;
}, z }, z.number().gte(0, "Range must be 0 - 65535").lte(65535, "Range must be 0 - 65535").nullable()),
.number()
.gte(0, "Range must be 0 - 65535")
.lte(65535, "Range must be 0 - 65535")
.nullable()),
}); });
type DockerProvider = z.infer<typeof DockerProviderSchema>; type DockerProvider = z.infer<typeof DockerProviderSchema>;
@@ -111,7 +107,10 @@ export const ShowExternalMariadbCredentials = ({ mariadbId }: Props) => {
{!getIp && ( {!getIp && (
<AlertBlock type="warning"> <AlertBlock type="warning">
You need to set an IP address in your{" "} You need to set an IP address in your{" "}
<Link href="/dashboard/settings" className="text-primary"> <Link
href="/dashboard/settings/server"
className="text-primary"
>
{data?.serverId {data?.serverId
? "Remote Servers -> Server -> Edit Server -> Update IP Address" ? "Remote Servers -> Server -> Edit Server -> Update IP Address"
: "Web Server -> Server -> Update Server IP"} : "Web Server -> Server -> Update Server IP"}

View File

@@ -8,15 +8,9 @@ import {
TooltipProvider, TooltipProvider,
TooltipTrigger, TooltipTrigger,
} from "@/components/ui/tooltip"; } from "@/components/ui/tooltip";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import { import * as TooltipPrimitive from "@radix-ui/react-tooltip";
Ban, import { Ban, CheckCircle2, RefreshCcw, Rocket, Terminal } from "lucide-react";
CheckCircle2,
HelpCircle,
RefreshCcw,
Terminal,
} from "lucide-react";
import { useState } from "react"; import { useState } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import { type LogLine, parseLogs } from "../../docker/logs/utils"; import { type LogLine, parseLogs } from "../../docker/logs/utils";
@@ -31,7 +25,7 @@ export const ShowGeneralMariadb = ({ mariadbId }: Props) => {
{ {
mariadbId, mariadbId,
}, },
{ enabled: !!mariadbId }, { enabled: !!mariadbId }
); );
const { mutateAsync: reload, isLoading: isReloading } = const { mutateAsync: reload, isLoading: isReloading } =
@@ -67,7 +61,7 @@ export const ShowGeneralMariadb = ({ mariadbId }: Props) => {
console.error("Deployment logs error:", error); console.error("Deployment logs error:", error);
setIsDeploying(false); setIsDeploying(false);
}, },
}, }
); );
return ( return (
@@ -89,15 +83,16 @@ export const ShowGeneralMariadb = ({ mariadbId }: Props) => {
refetch(); refetch();
}} }}
> >
<Tooltip>
<TooltipTrigger asChild>
<Button <Button
variant="default" variant="default"
isLoading={data?.applicationStatus === "running"} isLoading={data?.applicationStatus === "running"}
className="flex items-center gap-1.5" className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
> >
<Rocket className="size-4 mr-1" />
Deploy Deploy
<Tooltip> </Button>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
</TooltipTrigger> </TooltipTrigger>
<TooltipPrimitive.Portal> <TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]"> <TooltipContent sideOffset={5} className="z-[60]">
@@ -105,7 +100,6 @@ export const ShowGeneralMariadb = ({ mariadbId }: Props) => {
</TooltipContent> </TooltipContent>
</TooltipPrimitive.Portal> </TooltipPrimitive.Portal>
</Tooltip> </Tooltip>
</Button>
</DialogAction> </DialogAction>
<DialogAction <DialogAction
title="Reload Mariadb" title="Reload Mariadb"
@@ -125,16 +119,16 @@ export const ShowGeneralMariadb = ({ mariadbId }: Props) => {
}); });
}} }}
> >
<Tooltip>
<TooltipTrigger asChild>
<Button <Button
variant="secondary" variant="secondary"
isLoading={isReloading} isLoading={isReloading}
className="flex items-center gap-1.5" className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
> >
<RefreshCcw className="size-4 mr-1" />
Reload Reload
<RefreshCcw className="size-4" /> </Button>
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
</TooltipTrigger> </TooltipTrigger>
<TooltipPrimitive.Portal> <TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]"> <TooltipContent sideOffset={5} className="z-[60]">
@@ -142,7 +136,6 @@ export const ShowGeneralMariadb = ({ mariadbId }: Props) => {
</TooltipContent> </TooltipContent>
</TooltipPrimitive.Portal> </TooltipPrimitive.Portal>
</Tooltip> </Tooltip>
</Button>
</DialogAction> </DialogAction>
{data?.applicationStatus === "idle" ? ( {data?.applicationStatus === "idle" ? (
<DialogAction <DialogAction
@@ -162,16 +155,16 @@ export const ShowGeneralMariadb = ({ mariadbId }: Props) => {
}); });
}} }}
> >
<Tooltip>
<TooltipTrigger asChild>
<Button <Button
variant="secondary" variant="secondary"
isLoading={isStarting} isLoading={isStarting}
className="flex items-center gap-1.5" className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
> >
<CheckCircle2 className="size-4 mr-1" />
Start Start
<CheckCircle2 className="size-4" /> </Button>
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
</TooltipTrigger> </TooltipTrigger>
<TooltipPrimitive.Portal> <TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]"> <TooltipContent sideOffset={5} className="z-[60]">
@@ -182,7 +175,6 @@ export const ShowGeneralMariadb = ({ mariadbId }: Props) => {
</TooltipContent> </TooltipContent>
</TooltipPrimitive.Portal> </TooltipPrimitive.Portal>
</Tooltip> </Tooltip>
</Button>
</DialogAction> </DialogAction>
) : ( ) : (
<DialogAction <DialogAction
@@ -201,16 +193,16 @@ export const ShowGeneralMariadb = ({ mariadbId }: Props) => {
}); });
}} }}
> >
<Tooltip>
<TooltipTrigger asChild>
<Button <Button
variant="destructive" variant="destructive"
isLoading={isStopping} isLoading={isStopping}
className="flex items-center gap-1.5" className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
> >
<Ban className="size-4 mr-1" />
Stop Stop
<Ban className="size-4" /> </Button>
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
</TooltipTrigger> </TooltipTrigger>
<TooltipPrimitive.Portal> <TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]"> <TooltipContent sideOffset={5} className="z-[60]">
@@ -218,7 +210,6 @@ export const ShowGeneralMariadb = ({ mariadbId }: Props) => {
</TooltipContent> </TooltipContent>
</TooltipPrimitive.Portal> </TooltipPrimitive.Portal>
</Tooltip> </Tooltip>
</Button>
</DialogAction> </DialogAction>
)} )}
</TooltipProvider> </TooltipProvider>
@@ -226,10 +217,22 @@ export const ShowGeneralMariadb = ({ mariadbId }: Props) => {
appName={data?.appName || ""} appName={data?.appName || ""}
serverId={data?.serverId || ""} serverId={data?.serverId || ""}
> >
<Button variant="outline"> <Tooltip>
<Terminal /> <TooltipTrigger asChild>
<Button
variant="outline"
className="flex items-center gap-2 focus-visible:ring-2 focus-visible:ring-offset-2"
>
<Terminal className="size-4" />
Open Terminal Open Terminal
</Button> </Button>
</TooltipTrigger>
<TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]">
<p>Open a terminal to the MariaDB container</p>
</TooltipContent>
</TooltipPrimitive.Portal>
</Tooltip>
</DockerTerminalModal> </DockerTerminalModal>
</CardContent> </CardContent>
</Card> </Card>

View File

@@ -1,6 +1,6 @@
import { AlertBlock } from "@/components/shared/alert-block";
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input"; import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { AlertBlock } from "@/components/shared/alert-block";
import { import {
Card, Card,
CardContent, CardContent,
@@ -20,11 +20,11 @@ import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import Link from "next/link";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { toast } from "sonner"; import { toast } from "sonner";
import { z } from "zod"; import { z } from "zod";
import Link from "next/link";
const DockerProviderSchema = z.object({ const DockerProviderSchema = z.object({
externalPort: z.preprocess((a) => { externalPort: z.preprocess((a) => {
@@ -33,11 +33,7 @@ const DockerProviderSchema = z.object({
return Number.isNaN(parsed) ? null : parsed; return Number.isNaN(parsed) ? null : parsed;
} }
return null; return null;
}, z }, z.number().gte(0, "Range must be 0 - 65535").lte(65535, "Range must be 0 - 65535").nullable()),
.number()
.gte(0, "Range must be 0 - 65535")
.lte(65535, "Range must be 0 - 65535")
.nullable()),
}); });
type DockerProvider = z.infer<typeof DockerProviderSchema>; type DockerProvider = z.infer<typeof DockerProviderSchema>;
@@ -111,7 +107,10 @@ export const ShowExternalMongoCredentials = ({ mongoId }: Props) => {
{!getIp && ( {!getIp && (
<AlertBlock type="warning"> <AlertBlock type="warning">
You need to set an IP address in your{" "} You need to set an IP address in your{" "}
<Link href="/dashboard/settings" className="text-primary"> <Link
href="/dashboard/settings/server"
className="text-primary"
>
{data?.serverId {data?.serverId
? "Remote Servers -> Server -> Edit Server -> Update IP Address" ? "Remote Servers -> Server -> Edit Server -> Update IP Address"
: "Web Server -> Server -> Update Server IP"} : "Web Server -> Server -> Update Server IP"}

View File

@@ -8,15 +8,9 @@ import {
TooltipProvider, TooltipProvider,
TooltipTrigger, TooltipTrigger,
} from "@/components/ui/tooltip"; } from "@/components/ui/tooltip";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import { import * as TooltipPrimitive from "@radix-ui/react-tooltip";
Ban, import { Ban, CheckCircle2, RefreshCcw, Rocket, Terminal } from "lucide-react";
CheckCircle2,
HelpCircle,
RefreshCcw,
Terminal,
} from "lucide-react";
import { useState } from "react"; import { useState } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import { type LogLine, parseLogs } from "../../docker/logs/utils"; import { type LogLine, parseLogs } from "../../docker/logs/utils";
@@ -30,7 +24,7 @@ export const ShowGeneralMongo = ({ mongoId }: Props) => {
{ {
mongoId, mongoId,
}, },
{ enabled: !!mongoId }, { enabled: !!mongoId }
); );
const { mutateAsync: reload, isLoading: isReloading } = const { mutateAsync: reload, isLoading: isReloading } =
@@ -67,7 +61,7 @@ export const ShowGeneralMongo = ({ mongoId }: Props) => {
console.error("Deployment logs error:", error); console.error("Deployment logs error:", error);
setIsDeploying(false); setIsDeploying(false);
}, },
}, }
); );
return ( return (
<> <>
@@ -88,15 +82,16 @@ export const ShowGeneralMongo = ({ mongoId }: Props) => {
refetch(); refetch();
}} }}
> >
<Tooltip>
<TooltipTrigger asChild>
<Button <Button
variant="default" variant="default"
isLoading={data?.applicationStatus === "running"} isLoading={data?.applicationStatus === "running"}
className="flex items-center gap-1.5" className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
> >
<Rocket className="size-4 mr-1" />
Deploy Deploy
<Tooltip> </Button>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
</TooltipTrigger> </TooltipTrigger>
<TooltipPrimitive.Portal> <TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]"> <TooltipContent sideOffset={5} className="z-[60]">
@@ -104,7 +99,6 @@ export const ShowGeneralMongo = ({ mongoId }: Props) => {
</TooltipContent> </TooltipContent>
</TooltipPrimitive.Portal> </TooltipPrimitive.Portal>
</Tooltip> </Tooltip>
</Button>
</DialogAction> </DialogAction>
<DialogAction <DialogAction
title="Reload Mongo" title="Reload Mongo"
@@ -124,16 +118,16 @@ export const ShowGeneralMongo = ({ mongoId }: Props) => {
}); });
}} }}
> >
<Tooltip>
<TooltipTrigger asChild>
<Button <Button
variant="secondary" variant="secondary"
isLoading={isReloading} isLoading={isReloading}
className="flex items-center gap-1.5" className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
> >
<RefreshCcw className="size-4 mr-1" />
Reload Reload
<RefreshCcw className="size-4" /> </Button>
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
</TooltipTrigger> </TooltipTrigger>
<TooltipPrimitive.Portal> <TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]"> <TooltipContent sideOffset={5} className="z-[60]">
@@ -141,7 +135,6 @@ export const ShowGeneralMongo = ({ mongoId }: Props) => {
</TooltipContent> </TooltipContent>
</TooltipPrimitive.Portal> </TooltipPrimitive.Portal>
</Tooltip> </Tooltip>
</Button>
</DialogAction> </DialogAction>
{data?.applicationStatus === "idle" ? ( {data?.applicationStatus === "idle" ? (
<DialogAction <DialogAction
@@ -161,16 +154,16 @@ export const ShowGeneralMongo = ({ mongoId }: Props) => {
}); });
}} }}
> >
<Tooltip>
<TooltipTrigger asChild>
<Button <Button
variant="secondary" variant="secondary"
isLoading={isStarting} isLoading={isStarting}
className="flex items-center gap-1.5" className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
> >
<CheckCircle2 className="size-4 mr-1" />
Start Start
<CheckCircle2 className="size-4" /> </Button>
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
</TooltipTrigger> </TooltipTrigger>
<TooltipPrimitive.Portal> <TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]"> <TooltipContent sideOffset={5} className="z-[60]">
@@ -181,7 +174,6 @@ export const ShowGeneralMongo = ({ mongoId }: Props) => {
</TooltipContent> </TooltipContent>
</TooltipPrimitive.Portal> </TooltipPrimitive.Portal>
</Tooltip> </Tooltip>
</Button>
</DialogAction> </DialogAction>
) : ( ) : (
<DialogAction <DialogAction
@@ -200,16 +192,16 @@ export const ShowGeneralMongo = ({ mongoId }: Props) => {
}); });
}} }}
> >
<Tooltip>
<TooltipTrigger asChild>
<Button <Button
variant="destructive" variant="destructive"
isLoading={isStopping} isLoading={isStopping}
className="flex items-center gap-1.5" className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
> >
<Ban className="size-4 mr-1" />
Stop Stop
<Ban className="size-4" /> </Button>
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
</TooltipTrigger> </TooltipTrigger>
<TooltipPrimitive.Portal> <TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]"> <TooltipContent sideOffset={5} className="z-[60]">
@@ -217,7 +209,6 @@ export const ShowGeneralMongo = ({ mongoId }: Props) => {
</TooltipContent> </TooltipContent>
</TooltipPrimitive.Portal> </TooltipPrimitive.Portal>
</Tooltip> </Tooltip>
</Button>
</DialogAction> </DialogAction>
)} )}
</TooltipProvider> </TooltipProvider>
@@ -225,10 +216,22 @@ export const ShowGeneralMongo = ({ mongoId }: Props) => {
appName={data?.appName || ""} appName={data?.appName || ""}
serverId={data?.serverId || ""} serverId={data?.serverId || ""}
> >
<Button variant="outline"> <Tooltip>
<Terminal /> <TooltipTrigger asChild>
<Button
variant="outline"
className="flex items-center gap-2 focus-visible:ring-2 focus-visible:ring-offset-2"
>
<Terminal className="size-4" />
Open Terminal Open Terminal
</Button> </Button>
</TooltipTrigger>
<TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]">
<p>Open a terminal to the MongoDB container</p>
</TooltipContent>
</TooltipPrimitive.Portal>
</Tooltip>
</DockerTerminalModal> </DockerTerminalModal>
</CardContent> </CardContent>
</Card> </Card>

View File

@@ -1,6 +1,6 @@
import { AlertBlock } from "@/components/shared/alert-block";
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input"; import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { AlertBlock } from "@/components/shared/alert-block";
import { import {
Card, Card,
CardContent, CardContent,
@@ -20,11 +20,11 @@ import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import Link from "next/link";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { toast } from "sonner"; import { toast } from "sonner";
import { z } from "zod"; import { z } from "zod";
import Link from "next/link";
const DockerProviderSchema = z.object({ const DockerProviderSchema = z.object({
externalPort: z.preprocess((a) => { externalPort: z.preprocess((a) => {
@@ -33,11 +33,7 @@ const DockerProviderSchema = z.object({
return Number.isNaN(parsed) ? null : parsed; return Number.isNaN(parsed) ? null : parsed;
} }
return null; return null;
}, z }, z.number().gte(0, "Range must be 0 - 65535").lte(65535, "Range must be 0 - 65535").nullable()),
.number()
.gte(0, "Range must be 0 - 65535")
.lte(65535, "Range must be 0 - 65535")
.nullable()),
}); });
type DockerProvider = z.infer<typeof DockerProviderSchema>; type DockerProvider = z.infer<typeof DockerProviderSchema>;
@@ -111,7 +107,10 @@ export const ShowExternalMysqlCredentials = ({ mysqlId }: Props) => {
{!getIp && ( {!getIp && (
<AlertBlock type="warning"> <AlertBlock type="warning">
You need to set an IP address in your{" "} You need to set an IP address in your{" "}
<Link href="/dashboard/settings" className="text-primary"> <Link
href="/dashboard/settings/server"
className="text-primary"
>
{data?.serverId {data?.serverId
? "Remote Servers -> Server -> Edit Server -> Update IP Address" ? "Remote Servers -> Server -> Edit Server -> Update IP Address"
: "Web Server -> Server -> Update Server IP"} : "Web Server -> Server -> Update Server IP"}

View File

@@ -8,15 +8,9 @@ import {
TooltipProvider, TooltipProvider,
TooltipTrigger, TooltipTrigger,
} from "@/components/ui/tooltip"; } from "@/components/ui/tooltip";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import { import * as TooltipPrimitive from "@radix-ui/react-tooltip";
Ban, import { Ban, CheckCircle2, RefreshCcw, Rocket, Terminal } from "lucide-react";
CheckCircle2,
HelpCircle,
RefreshCcw,
Terminal,
} from "lucide-react";
import { useState } from "react"; import { useState } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import { type LogLine, parseLogs } from "../../docker/logs/utils"; import { type LogLine, parseLogs } from "../../docker/logs/utils";
@@ -30,7 +24,7 @@ export const ShowGeneralMysql = ({ mysqlId }: Props) => {
{ {
mysqlId, mysqlId,
}, },
{ enabled: !!mysqlId }, { enabled: !!mysqlId }
); );
const { mutateAsync: reload, isLoading: isReloading } = const { mutateAsync: reload, isLoading: isReloading } =
@@ -65,7 +59,7 @@ export const ShowGeneralMysql = ({ mysqlId }: Props) => {
console.error("Deployment logs error:", error); console.error("Deployment logs error:", error);
setIsDeploying(false); setIsDeploying(false);
}, },
}, }
); );
return ( return (
<> <>
@@ -86,15 +80,16 @@ export const ShowGeneralMysql = ({ mysqlId }: Props) => {
refetch(); refetch();
}} }}
> >
<Tooltip>
<TooltipTrigger asChild>
<Button <Button
variant="default" variant="default"
isLoading={data?.applicationStatus === "running"} isLoading={data?.applicationStatus === "running"}
className="flex items-center gap-1.5" className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
> >
<Rocket className="size-4 mr-1" />
Deploy Deploy
<Tooltip> </Button>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
</TooltipTrigger> </TooltipTrigger>
<TooltipPrimitive.Portal> <TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]"> <TooltipContent sideOffset={5} className="z-[60]">
@@ -102,7 +97,6 @@ export const ShowGeneralMysql = ({ mysqlId }: Props) => {
</TooltipContent> </TooltipContent>
</TooltipPrimitive.Portal> </TooltipPrimitive.Portal>
</Tooltip> </Tooltip>
</Button>
</DialogAction> </DialogAction>
<DialogAction <DialogAction
title="Reload Mysql" title="Reload Mysql"
@@ -122,16 +116,16 @@ export const ShowGeneralMysql = ({ mysqlId }: Props) => {
}); });
}} }}
> >
<Tooltip>
<TooltipTrigger asChild>
<Button <Button
variant="secondary" variant="secondary"
isLoading={isReloading} isLoading={isReloading}
className="flex items-center gap-1.5" className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
> >
<RefreshCcw className="size-4 mr-1" />
Reload Reload
<RefreshCcw className="size-4" /> </Button>
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
</TooltipTrigger> </TooltipTrigger>
<TooltipPrimitive.Portal> <TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]"> <TooltipContent sideOffset={5} className="z-[60]">
@@ -139,7 +133,6 @@ export const ShowGeneralMysql = ({ mysqlId }: Props) => {
</TooltipContent> </TooltipContent>
</TooltipPrimitive.Portal> </TooltipPrimitive.Portal>
</Tooltip> </Tooltip>
</Button>
</DialogAction> </DialogAction>
{data?.applicationStatus === "idle" ? ( {data?.applicationStatus === "idle" ? (
<DialogAction <DialogAction
@@ -159,16 +152,16 @@ export const ShowGeneralMysql = ({ mysqlId }: Props) => {
}); });
}} }}
> >
<Tooltip>
<TooltipTrigger asChild>
<Button <Button
variant="secondary" variant="secondary"
isLoading={isStarting} isLoading={isStarting}
className="flex items-center gap-1.5" className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
> >
<CheckCircle2 className="size-4 mr-1" />
Start Start
<CheckCircle2 className="size-4" /> </Button>
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
</TooltipTrigger> </TooltipTrigger>
<TooltipPrimitive.Portal> <TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]"> <TooltipContent sideOffset={5} className="z-[60]">
@@ -179,7 +172,6 @@ export const ShowGeneralMysql = ({ mysqlId }: Props) => {
</TooltipContent> </TooltipContent>
</TooltipPrimitive.Portal> </TooltipPrimitive.Portal>
</Tooltip> </Tooltip>
</Button>
</DialogAction> </DialogAction>
) : ( ) : (
<DialogAction <DialogAction
@@ -198,16 +190,16 @@ export const ShowGeneralMysql = ({ mysqlId }: Props) => {
}); });
}} }}
> >
<Tooltip>
<TooltipTrigger asChild>
<Button <Button
variant="destructive" variant="destructive"
isLoading={isStopping} isLoading={isStopping}
className="flex items-center gap-1.5" className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
> >
<Ban className="size-4 mr-1" />
Stop Stop
<Ban className="size-4" /> </Button>
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
</TooltipTrigger> </TooltipTrigger>
<TooltipPrimitive.Portal> <TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]"> <TooltipContent sideOffset={5} className="z-[60]">
@@ -215,7 +207,6 @@ export const ShowGeneralMysql = ({ mysqlId }: Props) => {
</TooltipContent> </TooltipContent>
</TooltipPrimitive.Portal> </TooltipPrimitive.Portal>
</Tooltip> </Tooltip>
</Button>
</DialogAction> </DialogAction>
)} )}
</TooltipProvider> </TooltipProvider>
@@ -223,10 +214,22 @@ export const ShowGeneralMysql = ({ mysqlId }: Props) => {
appName={data?.appName || ""} appName={data?.appName || ""}
serverId={data?.serverId || ""} serverId={data?.serverId || ""}
> >
<Button variant="outline"> <Tooltip>
<Terminal /> <TooltipTrigger asChild>
<Button
variant="outline"
className="flex items-center gap-2 focus-visible:ring-2 focus-visible:ring-offset-2"
>
<Terminal className="size-4" />
Open Terminal Open Terminal
</Button> </Button>
</TooltipTrigger>
<TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]">
<p>Open a terminal to the MySQL container</p>
</TooltipContent>
</TooltipPrimitive.Portal>
</Tooltip>
</DockerTerminalModal> </DockerTerminalModal>
</CardContent> </CardContent>
</Card> </Card>

View File

@@ -1,6 +1,6 @@
import { AlertBlock } from "@/components/shared/alert-block";
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input"; import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { AlertBlock } from "@/components/shared/alert-block";
import { import {
Card, Card,
CardContent, CardContent,
@@ -20,11 +20,11 @@ import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import Link from "next/link";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { toast } from "sonner"; import { toast } from "sonner";
import { z } from "zod"; import { z } from "zod";
import Link from "next/link";
const DockerProviderSchema = z.object({ const DockerProviderSchema = z.object({
externalPort: z.preprocess((a) => { externalPort: z.preprocess((a) => {
@@ -33,11 +33,7 @@ const DockerProviderSchema = z.object({
return Number.isNaN(parsed) ? null : parsed; return Number.isNaN(parsed) ? null : parsed;
} }
return null; return null;
}, z }, z.number().gte(0, "Range must be 0 - 65535").lte(65535, "Range must be 0 - 65535").nullable()),
.number()
.gte(0, "Range must be 0 - 65535")
.lte(65535, "Range must be 0 - 65535")
.nullable()),
}); });
type DockerProvider = z.infer<typeof DockerProviderSchema>; type DockerProvider = z.infer<typeof DockerProviderSchema>;
@@ -113,7 +109,10 @@ export const ShowExternalPostgresCredentials = ({ postgresId }: Props) => {
{!getIp && ( {!getIp && (
<AlertBlock type="warning"> <AlertBlock type="warning">
You need to set an IP address in your{" "} You need to set an IP address in your{" "}
<Link href="/dashboard/settings" className="text-primary"> <Link
href="/dashboard/settings/server"
className="text-primary"
>
{data?.serverId {data?.serverId
? "Remote Servers -> Server -> Edit Server -> Update IP Address" ? "Remote Servers -> Server -> Edit Server -> Update IP Address"
: "Web Server -> Server -> Update Server IP"} : "Web Server -> Server -> Update Server IP"}

View File

@@ -8,15 +8,9 @@ import {
TooltipProvider, TooltipProvider,
TooltipTrigger, TooltipTrigger,
} from "@/components/ui/tooltip"; } from "@/components/ui/tooltip";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import { import * as TooltipPrimitive from "@radix-ui/react-tooltip";
Ban, import { Ban, CheckCircle2, RefreshCcw, Rocket, Terminal } from "lucide-react";
CheckCircle2,
HelpCircle,
RefreshCcw,
Terminal,
} from "lucide-react";
import { useState } from "react"; import { useState } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import { type LogLine, parseLogs } from "../../docker/logs/utils"; import { type LogLine, parseLogs } from "../../docker/logs/utils";
@@ -31,7 +25,7 @@ export const ShowGeneralPostgres = ({ postgresId }: Props) => {
{ {
postgresId: postgresId, postgresId: postgresId,
}, },
{ enabled: !!postgresId }, { enabled: !!postgresId }
); );
const { mutateAsync: reload, isLoading: isReloading } = const { mutateAsync: reload, isLoading: isReloading } =
@@ -67,7 +61,7 @@ export const ShowGeneralPostgres = ({ postgresId }: Props) => {
console.error("Deployment logs error:", error); console.error("Deployment logs error:", error);
setIsDeploying(false); setIsDeploying(false);
}, },
}, }
); );
return ( return (
@@ -78,7 +72,7 @@ export const ShowGeneralPostgres = ({ postgresId }: Props) => {
<CardTitle className="text-xl">Deploy Settings</CardTitle> <CardTitle className="text-xl">Deploy Settings</CardTitle>
</CardHeader> </CardHeader>
<CardContent className="flex flex-row gap-4 flex-wrap"> <CardContent className="flex flex-row gap-4 flex-wrap">
<TooltipProvider delayDuration={0}> <TooltipProvider disableHoverableContent={false}>
<DialogAction <DialogAction
title="Deploy Postgres" title="Deploy Postgres"
description="Are you sure you want to deploy this postgres?" description="Are you sure you want to deploy this postgres?"
@@ -89,15 +83,16 @@ export const ShowGeneralPostgres = ({ postgresId }: Props) => {
refetch(); refetch();
}} }}
> >
<Tooltip>
<TooltipTrigger asChild>
<Button <Button
variant="default" variant="default"
isLoading={data?.applicationStatus === "running"} isLoading={data?.applicationStatus === "running"}
className="flex items-center gap-1.5" className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
> >
<Rocket className="size-4 mr-1" />
Deploy Deploy
<Tooltip> </Button>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
</TooltipTrigger> </TooltipTrigger>
<TooltipPrimitive.Portal> <TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]"> <TooltipContent sideOffset={5} className="z-[60]">
@@ -105,7 +100,6 @@ export const ShowGeneralPostgres = ({ postgresId }: Props) => {
</TooltipContent> </TooltipContent>
</TooltipPrimitive.Portal> </TooltipPrimitive.Portal>
</Tooltip> </Tooltip>
</Button>
</DialogAction> </DialogAction>
<DialogAction <DialogAction
title="Reload Postgres" title="Reload Postgres"
@@ -125,24 +119,26 @@ export const ShowGeneralPostgres = ({ postgresId }: Props) => {
}); });
}} }}
> >
<Tooltip>
<TooltipTrigger asChild>
<Button <Button
variant="secondary" variant="secondary"
isLoading={isReloading} isLoading={isReloading}
className="flex items-center gap-1.5" className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
> >
<RefreshCcw className="size-4 mr-1" />
Reload Reload
<RefreshCcw className="size-4" /> </Button>
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
</TooltipTrigger> </TooltipTrigger>
<TooltipPrimitive.Portal> <TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]"> <TooltipContent sideOffset={5} className="z-[60]">
<p>Restart the PostgreSQL service without rebuilding</p> <p>
Reload the PostgreSQL when you change configuration or
environment variables
</p>
</TooltipContent> </TooltipContent>
</TooltipPrimitive.Portal> </TooltipPrimitive.Portal>
</Tooltip> </Tooltip>
</Button>
</DialogAction> </DialogAction>
{data?.applicationStatus === "idle" ? ( {data?.applicationStatus === "idle" ? (
<DialogAction <DialogAction
@@ -162,16 +158,16 @@ export const ShowGeneralPostgres = ({ postgresId }: Props) => {
}); });
}} }}
> >
<Tooltip>
<TooltipTrigger asChild>
<Button <Button
variant="secondary" variant="secondary"
isLoading={isStarting} isLoading={isStarting}
className="flex items-center gap-1.5" className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
> >
<CheckCircle2 className="size-4 mr-1" />
Start Start
<CheckCircle2 className="size-4" /> </Button>
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
</TooltipTrigger> </TooltipTrigger>
<TooltipPrimitive.Portal> <TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]"> <TooltipContent sideOffset={5} className="z-[60]">
@@ -182,7 +178,6 @@ export const ShowGeneralPostgres = ({ postgresId }: Props) => {
</TooltipContent> </TooltipContent>
</TooltipPrimitive.Portal> </TooltipPrimitive.Portal>
</Tooltip> </Tooltip>
</Button>
</DialogAction> </DialogAction>
) : ( ) : (
<DialogAction <DialogAction
@@ -201,16 +196,16 @@ export const ShowGeneralPostgres = ({ postgresId }: Props) => {
}); });
}} }}
> >
<Tooltip>
<TooltipTrigger asChild>
<Button <Button
variant="destructive" variant="destructive"
isLoading={isStopping} isLoading={isStopping}
className="flex items-center gap-1.5" className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
> >
<Ban className="size-4 mr-1" />
Stop Stop
<Ban className="size-4" /> </Button>
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
</TooltipTrigger> </TooltipTrigger>
<TooltipPrimitive.Portal> <TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]"> <TooltipContent sideOffset={5} className="z-[60]">
@@ -218,7 +213,6 @@ export const ShowGeneralPostgres = ({ postgresId }: Props) => {
</TooltipContent> </TooltipContent>
</TooltipPrimitive.Portal> </TooltipPrimitive.Portal>
</Tooltip> </Tooltip>
</Button>
</DialogAction> </DialogAction>
)} )}
</TooltipProvider> </TooltipProvider>
@@ -226,8 +220,11 @@ export const ShowGeneralPostgres = ({ postgresId }: Props) => {
appName={data?.appName || ""} appName={data?.appName || ""}
serverId={data?.serverId || ""} serverId={data?.serverId || ""}
> >
<Button variant="outline"> <Button
<Terminal /> variant="outline"
className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
>
<Terminal className="size-4 mr-1" />
Open Terminal Open Terminal
</Button> </Button>
</DockerTerminalModal> </DockerTerminalModal>

View File

@@ -21,7 +21,7 @@ import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { PenBoxIcon } from "lucide-react"; import { PenBox } from "lucide-react";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { toast } from "sonner"; import { toast } from "sonner";
@@ -51,7 +51,7 @@ export const UpdatePostgres = ({ postgresId }: Props) => {
}, },
{ {
enabled: !!postgresId, enabled: !!postgresId,
}, }
); );
const form = useForm<UpdatePostgres>({ const form = useForm<UpdatePostgres>({
defaultValues: { defaultValues: {
@@ -94,9 +94,9 @@ export const UpdatePostgres = ({ postgresId }: Props) => {
<Button <Button
variant="ghost" variant="ghost"
size="icon" size="icon"
className="group hover:bg-blue-500/10 " className="group hover:bg-blue-500/10 focus-visible:ring-2 focus-visible:ring-offset-2"
> >
<PenBoxIcon className="size-3.5 text-primary group-hover:text-blue-500" /> <PenBox className="size-3.5 text-primary group-hover:text-blue-500" />
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg"> <DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
@@ -151,6 +151,7 @@ export const UpdatePostgres = ({ postgresId }: Props) => {
isLoading={isLoading} isLoading={isLoading}
form="hook-form-update-postgres" form="hook-form-update-postgres"
type="submit" type="submit"
className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
> >
Update Update
</Button> </Button>

View File

@@ -1,6 +1,6 @@
import { AlertBlock } from "@/components/shared/alert-block";
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input"; import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { AlertBlock } from "@/components/shared/alert-block";
import { import {
Card, Card,
CardContent, CardContent,
@@ -20,11 +20,11 @@ import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import Link from "next/link";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { toast } from "sonner"; import { toast } from "sonner";
import { z } from "zod"; import { z } from "zod";
import Link from "next/link";
const DockerProviderSchema = z.object({ const DockerProviderSchema = z.object({
externalPort: z.preprocess((a) => { externalPort: z.preprocess((a) => {
@@ -33,11 +33,7 @@ const DockerProviderSchema = z.object({
return Number.isNaN(parsed) ? null : parsed; return Number.isNaN(parsed) ? null : parsed;
} }
return null; return null;
}, z }, z.number().gte(0, "Range must be 0 - 65535").lte(65535, "Range must be 0 - 65535").nullable()),
.number()
.gte(0, "Range must be 0 - 65535")
.lte(65535, "Range must be 0 - 65535")
.nullable()),
}); });
type DockerProvider = z.infer<typeof DockerProviderSchema>; type DockerProvider = z.infer<typeof DockerProviderSchema>;
@@ -105,7 +101,10 @@ export const ShowExternalRedisCredentials = ({ redisId }: Props) => {
{!getIp && ( {!getIp && (
<AlertBlock type="warning"> <AlertBlock type="warning">
You need to set an IP address in your{" "} You need to set an IP address in your{" "}
<Link href="/dashboard/settings" className="text-primary"> <Link
href="/dashboard/settings/server"
className="text-primary"
>
{data?.serverId {data?.serverId
? "Remote Servers -> Server -> Edit Server -> Update IP Address" ? "Remote Servers -> Server -> Edit Server -> Update IP Address"
: "Web Server -> Server -> Update Server IP"} : "Web Server -> Server -> Update Server IP"}

View File

@@ -8,15 +8,9 @@ import {
TooltipProvider, TooltipProvider,
TooltipTrigger, TooltipTrigger,
} from "@/components/ui/tooltip"; } from "@/components/ui/tooltip";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import { import * as TooltipPrimitive from "@radix-ui/react-tooltip";
Ban, import { Ban, CheckCircle2, RefreshCcw, Rocket, Terminal } from "lucide-react";
CheckCircle2,
HelpCircle,
RefreshCcw,
Terminal,
} from "lucide-react";
import { useState } from "react"; import { useState } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import { type LogLine, parseLogs } from "../../docker/logs/utils"; import { type LogLine, parseLogs } from "../../docker/logs/utils";
@@ -31,7 +25,7 @@ export const ShowGeneralRedis = ({ redisId }: Props) => {
{ {
redisId, redisId,
}, },
{ enabled: !!redisId }, { enabled: !!redisId }
); );
const { mutateAsync: reload, isLoading: isReloading } = const { mutateAsync: reload, isLoading: isReloading } =
@@ -66,7 +60,7 @@ export const ShowGeneralRedis = ({ redisId }: Props) => {
console.error("Deployment logs error:", error); console.error("Deployment logs error:", error);
setIsDeploying(false); setIsDeploying(false);
}, },
}, }
); );
return ( return (
@@ -88,15 +82,16 @@ export const ShowGeneralRedis = ({ redisId }: Props) => {
refetch(); refetch();
}} }}
> >
<Tooltip>
<TooltipTrigger asChild>
<Button <Button
variant="default" variant="default"
isLoading={data?.applicationStatus === "running"} isLoading={data?.applicationStatus === "running"}
className="flex items-center gap-1.5" className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
> >
<Rocket className="size-4 mr-1" />
Deploy Deploy
<Tooltip> </Button>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
</TooltipTrigger> </TooltipTrigger>
<TooltipPrimitive.Portal> <TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]"> <TooltipContent sideOffset={5} className="z-[60]">
@@ -104,7 +99,6 @@ export const ShowGeneralRedis = ({ redisId }: Props) => {
</TooltipContent> </TooltipContent>
</TooltipPrimitive.Portal> </TooltipPrimitive.Portal>
</Tooltip> </Tooltip>
</Button>
</DialogAction> </DialogAction>
<DialogAction <DialogAction
title="Reload Redis" title="Reload Redis"
@@ -124,16 +118,16 @@ export const ShowGeneralRedis = ({ redisId }: Props) => {
}); });
}} }}
> >
<Tooltip>
<TooltipTrigger asChild>
<Button <Button
variant="secondary" variant="secondary"
isLoading={isReloading} isLoading={isReloading}
className="flex items-center gap-1.5" className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
> >
<RefreshCcw className="size-4 mr-1" />
Reload Reload
<RefreshCcw className="size-4" /> </Button>
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
</TooltipTrigger> </TooltipTrigger>
<TooltipPrimitive.Portal> <TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]"> <TooltipContent sideOffset={5} className="z-[60]">
@@ -141,7 +135,6 @@ export const ShowGeneralRedis = ({ redisId }: Props) => {
</TooltipContent> </TooltipContent>
</TooltipPrimitive.Portal> </TooltipPrimitive.Portal>
</Tooltip> </Tooltip>
</Button>
</DialogAction> </DialogAction>
{data?.applicationStatus === "idle" ? ( {data?.applicationStatus === "idle" ? (
<DialogAction <DialogAction
@@ -161,16 +154,16 @@ export const ShowGeneralRedis = ({ redisId }: Props) => {
}); });
}} }}
> >
<Tooltip>
<TooltipTrigger asChild>
<Button <Button
variant="secondary" variant="secondary"
isLoading={isStarting} isLoading={isStarting}
className="flex items-center gap-1.5" className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
> >
<CheckCircle2 className="size-4 mr-1" />
Start Start
<CheckCircle2 className="size-4" /> </Button>
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
</TooltipTrigger> </TooltipTrigger>
<TooltipPrimitive.Portal> <TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]"> <TooltipContent sideOffset={5} className="z-[60]">
@@ -181,7 +174,6 @@ export const ShowGeneralRedis = ({ redisId }: Props) => {
</TooltipContent> </TooltipContent>
</TooltipPrimitive.Portal> </TooltipPrimitive.Portal>
</Tooltip> </Tooltip>
</Button>
</DialogAction> </DialogAction>
) : ( ) : (
<DialogAction <DialogAction
@@ -200,16 +192,16 @@ export const ShowGeneralRedis = ({ redisId }: Props) => {
}); });
}} }}
> >
<Tooltip>
<TooltipTrigger asChild>
<Button <Button
variant="destructive" variant="destructive"
isLoading={isStopping} isLoading={isStopping}
className="flex items-center gap-1.5" className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
> >
<Ban className="size-4 mr-1" />
Stop Stop
<Ban className="size-4" /> </Button>
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
</TooltipTrigger> </TooltipTrigger>
<TooltipPrimitive.Portal> <TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]"> <TooltipContent sideOffset={5} className="z-[60]">
@@ -217,7 +209,6 @@ export const ShowGeneralRedis = ({ redisId }: Props) => {
</TooltipContent> </TooltipContent>
</TooltipPrimitive.Portal> </TooltipPrimitive.Portal>
</Tooltip> </Tooltip>
</Button>
</DialogAction> </DialogAction>
)} )}
</TooltipProvider> </TooltipProvider>
@@ -225,10 +216,22 @@ export const ShowGeneralRedis = ({ redisId }: Props) => {
appName={data?.appName || ""} appName={data?.appName || ""}
serverId={data?.serverId || ""} serverId={data?.serverId || ""}
> >
<Button variant="outline"> <Tooltip>
<Terminal /> <TooltipTrigger asChild>
<Button
variant="outline"
className="flex items-center gap-2 focus-visible:ring-2 focus-visible:ring-offset-2"
>
<Terminal className="size-4" />
Open Terminal Open Terminal
</Button> </Button>
</TooltipTrigger>
<TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]">
<p>Open a terminal to the Redis container</p>
</TooltipContent>
</TooltipPrimitive.Portal>
</Tooltip>
</DockerTerminalModal> </DockerTerminalModal>
</CardContent> </CardContent>
</Card> </Card>