mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
Merge branch 'canary' into 57-dokploy-api-or-cli
This commit is contained in:
@@ -183,8 +183,7 @@ export const AddPort = ({
|
||||
<SelectValue placeholder="Select a protocol" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent defaultValue={"none"}>
|
||||
<SelectItem value={"none"}>None</SelectItem>
|
||||
<SelectContent>
|
||||
<SelectItem value={"tcp"}>TCP</SelectItem>
|
||||
<SelectItem value={"udp"}>UDP</SelectItem>
|
||||
</SelectContent>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
@@ -19,8 +19,9 @@ import {
|
||||
} from "@/components/ui/form";
|
||||
import { api } from "@/utils/api";
|
||||
import { toast } from "sonner";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { Toggle } from "@/components/ui/toggle";
|
||||
import { CodeEditor } from "@/components/shared/code-editor";
|
||||
import { EyeIcon, EyeOffIcon } from "lucide-react";
|
||||
|
||||
const addEnvironmentSchema = z.object({
|
||||
environment: z.string(),
|
||||
@@ -33,6 +34,7 @@ interface Props {
|
||||
}
|
||||
|
||||
export const ShowEnvironment = ({ applicationId }: Props) => {
|
||||
const [isEnvVisible, setIsEnvVisible] = useState(true);
|
||||
const { mutateAsync, isLoading } =
|
||||
api.application.saveEnvironment.useMutation();
|
||||
|
||||
@@ -72,15 +74,50 @@ export const ShowEnvironment = ({ applicationId }: Props) => {
|
||||
toast.error("Error to add environment");
|
||||
});
|
||||
};
|
||||
useEffect(() => {
|
||||
if (isEnvVisible) {
|
||||
if (data?.env) {
|
||||
const maskedLines = data.env
|
||||
.split("\n")
|
||||
.map((line) => "*".repeat(line.length))
|
||||
.join("\n");
|
||||
form.reset({
|
||||
environment: maskedLines,
|
||||
});
|
||||
} else {
|
||||
form.reset({
|
||||
environment: "",
|
||||
});
|
||||
}
|
||||
} else {
|
||||
form.reset({
|
||||
environment: data?.env || "",
|
||||
});
|
||||
}
|
||||
}, [form.reset, data, form, isEnvVisible]);
|
||||
|
||||
return (
|
||||
<div className="flex w-full flex-col gap-5 ">
|
||||
<Card className="bg-background">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-xl">Environment Settings</CardTitle>
|
||||
<CardDescription>
|
||||
You can add environment variables to your resource.
|
||||
</CardDescription>
|
||||
<CardHeader className="flex flex-row w-full items-center justify-between">
|
||||
<div>
|
||||
<CardTitle className="text-xl">Environment Settings</CardTitle>
|
||||
<CardDescription>
|
||||
You can add environment variables to your resource.
|
||||
</CardDescription>
|
||||
</div>
|
||||
|
||||
<Toggle
|
||||
aria-label="Toggle bold"
|
||||
pressed={isEnvVisible}
|
||||
onPressedChange={setIsEnvVisible}
|
||||
>
|
||||
{isEnvVisible ? (
|
||||
<EyeOffIcon className="h-4 w-4 text-muted-foreground" />
|
||||
) : (
|
||||
<EyeIcon className="h-4 w-4 text-muted-foreground" />
|
||||
)}
|
||||
</Toggle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Form {...form}>
|
||||
@@ -97,6 +134,7 @@ export const ShowEnvironment = ({ applicationId }: Props) => {
|
||||
<FormControl>
|
||||
<CodeEditor
|
||||
language="properties"
|
||||
disabled={isEnvVisible}
|
||||
placeholder={`NODE_ENV=production
|
||||
PORT=3000
|
||||
`}
|
||||
@@ -111,7 +149,12 @@ PORT=3000
|
||||
/>
|
||||
|
||||
<div className="flex flex-row justify-end">
|
||||
<Button isLoading={isLoading} className="w-fit" type="submit">
|
||||
<Button
|
||||
disabled={isEnvVisible}
|
||||
isLoading={isLoading}
|
||||
className="w-fit"
|
||||
type="submit"
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
} from "@/components/ui/alert-dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { api } from "@/utils/api";
|
||||
import { useEffect, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
interface Props {
|
||||
@@ -51,18 +52,14 @@ export const DeployApplication = ({ applicationId }: Props) => {
|
||||
applicationId,
|
||||
})
|
||||
.then(async () => {
|
||||
toast.success("Application Deploying....");
|
||||
toast.success("Deploying Application....");
|
||||
|
||||
await refetch();
|
||||
await deploy({
|
||||
applicationId,
|
||||
})
|
||||
.then(() => {
|
||||
toast.success("Application Deployed Succesfully");
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error to deploy Application");
|
||||
});
|
||||
}).catch(() => {
|
||||
toast.error("Error to deploy Application");
|
||||
});
|
||||
|
||||
await refetch();
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
@@ -20,6 +20,8 @@ import {
|
||||
import { api } from "@/utils/api";
|
||||
import { toast } from "sonner";
|
||||
import { CodeEditor } from "@/components/shared/code-editor";
|
||||
import { Toggle } from "@/components/ui/toggle";
|
||||
import { EyeIcon, EyeOffIcon } from "lucide-react";
|
||||
|
||||
const addEnvironmentSchema = z.object({
|
||||
environment: z.string(),
|
||||
@@ -32,6 +34,7 @@ interface Props {
|
||||
}
|
||||
|
||||
export const ShowEnvironmentCompose = ({ composeId }: Props) => {
|
||||
const [isEnvVisible, setIsEnvVisible] = useState(true);
|
||||
const { mutateAsync, isLoading } = api.compose.update.useMutation();
|
||||
|
||||
const { data, refetch } = api.compose.one.useQuery(
|
||||
@@ -71,14 +74,50 @@ export const ShowEnvironmentCompose = ({ composeId }: Props) => {
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (isEnvVisible) {
|
||||
if (data?.env) {
|
||||
const maskedLines = data.env
|
||||
.split("\n")
|
||||
.map((line) => "*".repeat(line.length))
|
||||
.join("\n");
|
||||
form.reset({
|
||||
environment: maskedLines,
|
||||
});
|
||||
} else {
|
||||
form.reset({
|
||||
environment: "",
|
||||
});
|
||||
}
|
||||
} else {
|
||||
form.reset({
|
||||
environment: data?.env || "",
|
||||
});
|
||||
}
|
||||
}, [form.reset, data, form, isEnvVisible]);
|
||||
|
||||
return (
|
||||
<div className="flex w-full flex-col gap-5 ">
|
||||
<Card className="bg-background">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-xl">Environment Settings</CardTitle>
|
||||
<CardDescription>
|
||||
You can add environment variables to your resource.
|
||||
</CardDescription>
|
||||
<CardHeader className="flex flex-row w-full items-center justify-between">
|
||||
<div>
|
||||
<CardTitle className="text-xl">Environment Settings</CardTitle>
|
||||
<CardDescription>
|
||||
You can add environment variables to your resource.
|
||||
</CardDescription>
|
||||
</div>
|
||||
|
||||
<Toggle
|
||||
aria-label="Toggle bold"
|
||||
pressed={isEnvVisible}
|
||||
onPressedChange={setIsEnvVisible}
|
||||
>
|
||||
{isEnvVisible ? (
|
||||
<EyeOffIcon className="h-4 w-4 text-muted-foreground" />
|
||||
) : (
|
||||
<EyeIcon className="h-4 w-4 text-muted-foreground" />
|
||||
)}
|
||||
</Toggle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Form {...form}>
|
||||
@@ -95,6 +134,7 @@ export const ShowEnvironmentCompose = ({ composeId }: Props) => {
|
||||
<FormControl>
|
||||
<CodeEditor
|
||||
language="properties"
|
||||
disabled={isEnvVisible}
|
||||
placeholder={`NODE_ENV=production
|
||||
PORT=3000
|
||||
`}
|
||||
@@ -109,7 +149,12 @@ PORT=3000
|
||||
/>
|
||||
|
||||
<div className="flex flex-row justify-end">
|
||||
<Button isLoading={isLoading} className="w-fit" type="submit">
|
||||
<Button
|
||||
disabled={isEnvVisible}
|
||||
isLoading={isLoading}
|
||||
className="w-fit"
|
||||
type="submit"
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -49,18 +49,14 @@ export const DeployCompose = ({ composeId }: Props) => {
|
||||
composeStatus: "running",
|
||||
})
|
||||
.then(async () => {
|
||||
toast.success("Compose Deploying....");
|
||||
toast.success("Deploying Compose....");
|
||||
|
||||
await refetch();
|
||||
await deploy({
|
||||
composeId,
|
||||
})
|
||||
.then(() => {
|
||||
toast.success("Compose Deployed Succesfully");
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error to deploy Compose");
|
||||
});
|
||||
}).catch(() => {
|
||||
toast.error("Error to deploy Compose");
|
||||
});
|
||||
|
||||
await refetch();
|
||||
})
|
||||
|
||||
@@ -50,17 +50,13 @@ export const DeployMariadb = ({ mariadbId }: Props) => {
|
||||
applicationStatus: "running",
|
||||
})
|
||||
.then(async () => {
|
||||
toast.success("Database Deploying....");
|
||||
toast.success("Deploying Database....");
|
||||
await refetch();
|
||||
await deploy({
|
||||
mariadbId,
|
||||
})
|
||||
.then(() => {
|
||||
toast.success("Database Deployed Succesfully");
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error to deploy Database");
|
||||
});
|
||||
}).catch(() => {
|
||||
toast.error("Error to deploy Database");
|
||||
});
|
||||
await refetch();
|
||||
})
|
||||
.catch((e) => {
|
||||
|
||||
@@ -22,6 +22,7 @@ import React, { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
|
||||
|
||||
const DockerProviderSchema = z.object({
|
||||
externalPort: z.preprocess((a) => {
|
||||
@@ -136,7 +137,7 @@ export const ShowExternalMariadbCredentials = ({ mariadbId }: Props) => {
|
||||
<div className="flex flex-col gap-3">
|
||||
{/* jdbc:mariadb://5.161.59.207:3306/pixel-calculate?user=mariadb&password=HdVXfq6hM7W7F1 */}
|
||||
<Label>External Host</Label>
|
||||
<Input disabled value={connectionUrl} />
|
||||
<ToggleVisibilityInput value={connectionUrl} disabled />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { api } from "@/utils/api";
|
||||
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
|
||||
|
||||
interface Props {
|
||||
mariadbId: string;
|
||||
@@ -29,20 +30,18 @@ export const ShowInternalMariadbCredentials = ({ mariadbId }: Props) => {
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label>Password</Label>
|
||||
<div className="flex flex-row gap-4">
|
||||
<Input
|
||||
<ToggleVisibilityInput
|
||||
disabled
|
||||
value={data?.databasePassword}
|
||||
type="password"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label>Root Password</Label>
|
||||
<div className="flex flex-row gap-4">
|
||||
<Input
|
||||
<ToggleVisibilityInput
|
||||
disabled
|
||||
value={data?.databaseRootPassword}
|
||||
type="password"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -58,7 +57,7 @@ export const ShowInternalMariadbCredentials = ({ mariadbId }: Props) => {
|
||||
|
||||
<div className="flex flex-col gap-2 md:col-span-2">
|
||||
<Label>Internal Connection URL </Label>
|
||||
<Input
|
||||
<ToggleVisibilityInput
|
||||
disabled
|
||||
value={`mariadb://${data?.databaseUser}:${data?.databasePassword}@${data?.appName}:3306/${data?.databaseName}`}
|
||||
/>
|
||||
|
||||
@@ -50,17 +50,13 @@ export const DeployMongo = ({ mongoId }: Props) => {
|
||||
applicationStatus: "running",
|
||||
})
|
||||
.then(async () => {
|
||||
toast.success("Database Deploying....");
|
||||
toast.success("Deploying Database....");
|
||||
await refetch();
|
||||
await deploy({
|
||||
mongoId,
|
||||
})
|
||||
.then(() => {
|
||||
toast.success("Database Deployed Succesfully");
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error to deploy Database");
|
||||
});
|
||||
}).catch(() => {
|
||||
toast.error("Error to deploy Database");
|
||||
});
|
||||
await refetch();
|
||||
})
|
||||
.catch((e) => {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
@@ -136,7 +137,7 @@ export const ShowExternalMongoCredentials = ({ mongoId }: Props) => {
|
||||
<div className="grid w-full gap-8">
|
||||
<div className="flex flex-col gap-3">
|
||||
<Label>External Host</Label>
|
||||
<Input disabled value={connectionUrl} />
|
||||
<ToggleVisibilityInput value={connectionUrl} disabled />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { api } from "@/utils/api";
|
||||
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
|
||||
|
||||
interface Props {
|
||||
mongoId: string;
|
||||
@@ -26,10 +27,9 @@ export const ShowInternalMongoCredentials = ({ mongoId }: Props) => {
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label>Password</Label>
|
||||
<div className="flex flex-row gap-4">
|
||||
<Input
|
||||
<ToggleVisibilityInput
|
||||
disabled
|
||||
value={data?.databasePassword}
|
||||
type="password"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -46,7 +46,7 @@ export const ShowInternalMongoCredentials = ({ mongoId }: Props) => {
|
||||
|
||||
<div className="flex flex-col gap-2 md:col-span-2">
|
||||
<Label>Internal Connection URL </Label>
|
||||
<Input
|
||||
<ToggleVisibilityInput
|
||||
disabled
|
||||
value={`mongodb://${data?.databaseUser}:${data?.databasePassword}@${data?.appName}:27017`}
|
||||
/>
|
||||
|
||||
@@ -50,17 +50,13 @@ export const DeployMysql = ({ mysqlId }: Props) => {
|
||||
applicationStatus: "running",
|
||||
})
|
||||
.then(async () => {
|
||||
toast.success("Database Deploying....");
|
||||
toast.success("Deploying Database....");
|
||||
await refetch();
|
||||
await deploy({
|
||||
mysqlId,
|
||||
})
|
||||
.then(() => {
|
||||
toast.success("Database Deployed Succesfully");
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error to deploy Database");
|
||||
});
|
||||
}).catch(() => {
|
||||
toast.error("Error to deploy Database");
|
||||
});
|
||||
await refetch();
|
||||
})
|
||||
.catch((e) => {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
@@ -136,7 +137,7 @@ export const ShowExternalMysqlCredentials = ({ mysqlId }: Props) => {
|
||||
<div className="grid w-full gap-8">
|
||||
<div className="flex flex-col gap-3">
|
||||
<Label>External Host</Label>
|
||||
<Input disabled value={connectionUrl} />
|
||||
<ToggleVisibilityInput disabled value={connectionUrl} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { api } from "@/utils/api";
|
||||
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
|
||||
|
||||
interface Props {
|
||||
mysqlId: string;
|
||||
@@ -29,20 +30,18 @@ export const ShowInternalMysqlCredentials = ({ mysqlId }: Props) => {
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label>Password</Label>
|
||||
<div className="flex flex-row gap-4">
|
||||
<Input
|
||||
<ToggleVisibilityInput
|
||||
disabled
|
||||
value={data?.databasePassword}
|
||||
type="password"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label>Root Password</Label>
|
||||
<div className="flex flex-row gap-4">
|
||||
<Input
|
||||
<ToggleVisibilityInput
|
||||
disabled
|
||||
value={data?.databaseRootPassword}
|
||||
type="password"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -58,7 +57,7 @@ export const ShowInternalMysqlCredentials = ({ mysqlId }: Props) => {
|
||||
|
||||
<div className="flex flex-col gap-2 md:col-span-2">
|
||||
<Label>Internal Connection URL </Label>
|
||||
<Input
|
||||
<ToggleVisibilityInput
|
||||
disabled
|
||||
value={`mysql://${data?.databaseUser}:${data?.databasePassword}@${data?.appName}:3306/${data?.databaseName}`}
|
||||
/>
|
||||
|
||||
@@ -50,17 +50,13 @@ export const DeployPostgres = ({ postgresId }: Props) => {
|
||||
applicationStatus: "running",
|
||||
})
|
||||
.then(async () => {
|
||||
toast.success("Database Deploying....");
|
||||
toast.success("Deploying Database....");
|
||||
await refetch();
|
||||
await deploy({
|
||||
postgresId,
|
||||
})
|
||||
.then(() => {
|
||||
toast.success("Database Deployed Succesfully");
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error to deploy Database");
|
||||
});
|
||||
}).catch(() => {
|
||||
toast.error("Error to deploy Database");
|
||||
});
|
||||
await refetch();
|
||||
})
|
||||
.catch((e) => {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
@@ -137,7 +138,7 @@ export const ShowExternalPostgresCredentials = ({ postgresId }: Props) => {
|
||||
<div className="grid w-full gap-8">
|
||||
<div className="flex flex-col gap-3">
|
||||
<Label>External Host</Label>
|
||||
<Input disabled value={connectionUrl} />
|
||||
<ToggleVisibilityInput value={connectionUrl} disabled />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { api } from "@/utils/api";
|
||||
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
|
||||
|
||||
interface Props {
|
||||
postgresId: string;
|
||||
@@ -29,10 +30,9 @@ export const ShowInternalPostgresCredentials = ({ postgresId }: Props) => {
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label>Password</Label>
|
||||
<div className="flex flex-row gap-4">
|
||||
<Input
|
||||
disabled
|
||||
<ToggleVisibilityInput
|
||||
value={data?.databasePassword}
|
||||
type="password"
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -48,7 +48,7 @@ export const ShowInternalPostgresCredentials = ({ postgresId }: Props) => {
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label>Internal Connection URL </Label>
|
||||
<Input
|
||||
<ToggleVisibilityInput
|
||||
disabled
|
||||
value={`postgresql://${data?.databaseUser}:${data?.databasePassword}@${data?.appName}:5432/${data?.databaseName}`}
|
||||
/>
|
||||
|
||||
@@ -22,16 +22,26 @@ import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { api } from "@/utils/api";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { Folder } from "lucide-react";
|
||||
import { useEffect } from "react";
|
||||
import { useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { slugify } from "@/lib/slug";
|
||||
|
||||
const AddTemplateSchema = z.object({
|
||||
name: z.string().min(1, {
|
||||
message: "Name is required",
|
||||
}),
|
||||
appName: z
|
||||
.string()
|
||||
.min(1, {
|
||||
message: "App name is required",
|
||||
})
|
||||
.regex(/^[a-z](?!.*--)([a-z0-9-]*[a-z])?$/, {
|
||||
message:
|
||||
"App name supports letters, numbers, '-' and can only start and end letters, and does not support continuous '-'",
|
||||
}),
|
||||
description: z.string().optional(),
|
||||
});
|
||||
|
||||
@@ -39,10 +49,13 @@ type AddTemplate = z.infer<typeof AddTemplateSchema>;
|
||||
|
||||
interface Props {
|
||||
projectId: string;
|
||||
projectName?: string;
|
||||
}
|
||||
|
||||
export const AddApplication = ({ projectId }: Props) => {
|
||||
export const AddApplication = ({ projectId, projectName }: Props) => {
|
||||
const utils = api.useUtils();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const slug = slugify(projectName);
|
||||
|
||||
const { mutateAsync, isLoading, error, isError } =
|
||||
api.application.create.useMutation();
|
||||
@@ -50,34 +63,34 @@ export const AddApplication = ({ projectId }: Props) => {
|
||||
const form = useForm<AddTemplate>({
|
||||
defaultValues: {
|
||||
name: "",
|
||||
appName: `${slug}-`,
|
||||
description: "",
|
||||
},
|
||||
resolver: zodResolver(AddTemplateSchema),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
form.reset();
|
||||
}, [form, form.reset, form.formState.isSubmitSuccessful]);
|
||||
|
||||
const onSubmit = async (data: AddTemplate) => {
|
||||
await mutateAsync({
|
||||
name: data.name,
|
||||
appName: data.appName,
|
||||
description: data.description,
|
||||
projectId,
|
||||
})
|
||||
.then(async () => {
|
||||
toast.success("Service Created");
|
||||
form.reset();
|
||||
setVisible(false);
|
||||
await utils.project.one.invalidate({
|
||||
projectId,
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
.catch((e) => {
|
||||
toast.error("Error to create the service");
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog>
|
||||
<Dialog open={visible} onOpenChange={setVisible}>
|
||||
<DialogTrigger className="w-full">
|
||||
<DropdownMenuItem
|
||||
className="w-full cursor-pointer space-x-3"
|
||||
@@ -95,29 +108,46 @@ export const AddApplication = ({ projectId }: Props) => {
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
|
||||
|
||||
<Form {...form}>
|
||||
<form
|
||||
id="hook-form"
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="grid w-full gap-4"
|
||||
>
|
||||
<div className="flex flex-col gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Frontend" {...field} />
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="Frontend"
|
||||
{...field}
|
||||
onChange={(e) => {
|
||||
const val = e.target.value?.trim() || "";
|
||||
form.setValue("appName", `${slug}-${val}`);
|
||||
field.onChange(val);
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="appName"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>AppName</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="my-app" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="description"
|
||||
@@ -136,47 +166,6 @@ export const AddApplication = ({ projectId }: Props) => {
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* <FormField
|
||||
control={form.control}
|
||||
name="buildType"
|
||||
render={({ field }) => (
|
||||
<FormItem className="space-y-3">
|
||||
<FormLabel>Build Type</FormLabel>
|
||||
<FormControl>
|
||||
<RadioGroup
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={field.value}
|
||||
className="flex flex-col space-y-1"
|
||||
>
|
||||
<FormItem className="flex items-center space-x-3 space-y-0">
|
||||
<FormControl>
|
||||
<RadioGroupItem value="dockerfile" />
|
||||
</FormControl>
|
||||
<FormLabel className="font-normal">
|
||||
Dockerfile
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
<FormItem className="flex items-center space-x-3 space-y-0">
|
||||
<FormControl>
|
||||
<RadioGroupItem value="nixpacks" />
|
||||
</FormControl>
|
||||
<FormLabel className="font-normal">Nixpacks</FormLabel>
|
||||
</FormItem>
|
||||
<FormItem className="flex items-center space-x-3 space-y-0">
|
||||
<FormControl>
|
||||
<RadioGroupItem value="heroku_buildpacks" />
|
||||
</FormControl>
|
||||
<FormLabel className="font-normal">
|
||||
Heroku Buildpacks
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/> */}
|
||||
</form>
|
||||
|
||||
<DialogFooter>
|
||||
|
||||
@@ -34,12 +34,22 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { slugify } from "@/lib/slug";
|
||||
|
||||
const AddComposeSchema = z.object({
|
||||
composeType: z.enum(["docker-compose", "stack"]).optional(),
|
||||
name: z.string().min(1, {
|
||||
message: "Name is required",
|
||||
}),
|
||||
appName: z
|
||||
.string()
|
||||
.min(1, {
|
||||
message: "App name is required",
|
||||
})
|
||||
.regex(/^[a-z](?!.*--)([a-z0-9-]*[a-z])?$/, {
|
||||
message:
|
||||
"App name supports letters, numbers, '-' and can only start and end letters, and does not support continuous '-'",
|
||||
}),
|
||||
description: z.string().optional(),
|
||||
});
|
||||
|
||||
@@ -47,11 +57,12 @@ type AddCompose = z.infer<typeof AddComposeSchema>;
|
||||
|
||||
interface Props {
|
||||
projectId: string;
|
||||
projectName?: string;
|
||||
}
|
||||
|
||||
export const AddCompose = ({ projectId }: Props) => {
|
||||
export const AddCompose = ({ projectId, projectName }: Props) => {
|
||||
const utils = api.useUtils();
|
||||
|
||||
const slug = slugify(projectName);
|
||||
const { mutateAsync, isLoading, error, isError } =
|
||||
api.compose.create.useMutation();
|
||||
|
||||
@@ -60,6 +71,7 @@ export const AddCompose = ({ projectId }: Props) => {
|
||||
name: "",
|
||||
description: "",
|
||||
composeType: "docker-compose",
|
||||
appName: `${slug}-`,
|
||||
},
|
||||
resolver: zodResolver(AddComposeSchema),
|
||||
});
|
||||
@@ -74,6 +86,7 @@ export const AddCompose = ({ projectId }: Props) => {
|
||||
description: data.description,
|
||||
projectId,
|
||||
composeType: data.composeType,
|
||||
appName: data.appName,
|
||||
})
|
||||
.then(async () => {
|
||||
toast.success("Compose Created");
|
||||
@@ -120,14 +133,34 @@ export const AddCompose = ({ projectId }: Props) => {
|
||||
<FormItem>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Frontend" {...field} />
|
||||
<Input
|
||||
placeholder="Frontend"
|
||||
{...field}
|
||||
onChange={(e) => {
|
||||
const val = e.target.value?.trim() || "";
|
||||
form.setValue("appName", `${slug}-${val}`);
|
||||
field.onChange(val);
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="appName"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>AppName</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="my-app" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="composeType"
|
||||
|
||||
@@ -27,10 +27,11 @@ import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { slugify } from "@/lib/slug";
|
||||
import { api } from "@/utils/api";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { Database } from "lucide-react";
|
||||
import { useEffect } from "react";
|
||||
import { Database, AlertTriangle } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
@@ -58,6 +59,15 @@ const databasesUserDefaultPlaceholder: Record<
|
||||
|
||||
const baseDatabaseSchema = z.object({
|
||||
name: z.string().min(1, "Name required"),
|
||||
appName: z
|
||||
.string()
|
||||
.min(1, {
|
||||
message: "App name is required",
|
||||
})
|
||||
.regex(/^[a-z](?!.*--)([a-z0-9-]*[a-z])?$/, {
|
||||
message:
|
||||
"App name supports letters, numbers, '-' and can only start and end letters, and does not support continuous '-'",
|
||||
}),
|
||||
databasePassword: z.string(),
|
||||
dockerImage: z.string(),
|
||||
description: z.string().nullable(),
|
||||
@@ -101,30 +111,52 @@ const mySchema = z.discriminatedUnion("type", [
|
||||
.merge(baseDatabaseSchema),
|
||||
]);
|
||||
|
||||
const databasesMap = {
|
||||
postgres: {
|
||||
icon: <PostgresqlIcon />,
|
||||
label: "PostgreSQL",
|
||||
},
|
||||
mongo: {
|
||||
icon: <MongodbIcon />,
|
||||
label: "MongoDB",
|
||||
},
|
||||
mariadb: {
|
||||
icon: <MariadbIcon />,
|
||||
label: "MariaDB",
|
||||
},
|
||||
mysql: {
|
||||
icon: <MysqlIcon />,
|
||||
label: "MySQL",
|
||||
},
|
||||
redis: {
|
||||
icon: <RedisIcon />,
|
||||
label: "Redis",
|
||||
},
|
||||
};
|
||||
|
||||
type AddDatabase = z.infer<typeof mySchema>;
|
||||
|
||||
interface Props {
|
||||
projectId: string;
|
||||
projectName?: string;
|
||||
}
|
||||
|
||||
export const AddDatabase = ({ projectId }: Props) => {
|
||||
export const AddDatabase = ({ projectId, projectName }: Props) => {
|
||||
const utils = api.useUtils();
|
||||
|
||||
const { mutateAsync: createPostgresql } = api.postgres.create.useMutation();
|
||||
|
||||
const { mutateAsync: createMongo } = api.mongo.create.useMutation();
|
||||
|
||||
const { mutateAsync: createRedis } = api.redis.create.useMutation();
|
||||
|
||||
const { mutateAsync: createMariadb } = api.mariadb.create.useMutation();
|
||||
|
||||
const { mutateAsync: createMysql } = api.mysql.create.useMutation();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const slug = slugify(projectName);
|
||||
const postgresMutation = api.postgres.create.useMutation();
|
||||
const mongoMutation = api.mongo.create.useMutation();
|
||||
const redisMutation = api.redis.create.useMutation();
|
||||
const mariadbMutation = api.mariadb.create.useMutation();
|
||||
const mysqlMutation = api.mysql.create.useMutation();
|
||||
|
||||
const form = useForm<AddDatabase>({
|
||||
defaultValues: {
|
||||
type: "postgres",
|
||||
dockerImage: "",
|
||||
name: "",
|
||||
appName: `${slug}-`,
|
||||
databasePassword: "",
|
||||
description: "",
|
||||
databaseName: "",
|
||||
@@ -133,76 +165,65 @@ export const AddDatabase = ({ projectId }: Props) => {
|
||||
resolver: zodResolver(mySchema),
|
||||
});
|
||||
const type = form.watch("type");
|
||||
|
||||
useEffect(() => {
|
||||
form.reset({
|
||||
type: "postgres",
|
||||
dockerImage: "",
|
||||
name: "",
|
||||
databasePassword: "",
|
||||
description: "",
|
||||
databaseName: "",
|
||||
databaseUser: "",
|
||||
});
|
||||
}, [form, form.reset, form.formState.isSubmitSuccessful]);
|
||||
const activeMutation = {
|
||||
postgres: postgresMutation,
|
||||
mongo: mongoMutation,
|
||||
redis: redisMutation,
|
||||
mariadb: mariadbMutation,
|
||||
mysql: mysqlMutation,
|
||||
};
|
||||
|
||||
const onSubmit = async (data: AddDatabase) => {
|
||||
const defaultDockerImage =
|
||||
data.dockerImage || dockerImageDefaultPlaceholder[data.type];
|
||||
|
||||
let promise: Promise<unknown> | null = null;
|
||||
const commonParams = {
|
||||
name: data.name,
|
||||
appName: data.appName,
|
||||
dockerImage: defaultDockerImage,
|
||||
projectId,
|
||||
description: data.description,
|
||||
};
|
||||
|
||||
if (data.type === "postgres") {
|
||||
promise = createPostgresql({
|
||||
name: data.name,
|
||||
dockerImage: defaultDockerImage,
|
||||
promise = postgresMutation.mutateAsync({
|
||||
...commonParams,
|
||||
databasePassword: data.databasePassword,
|
||||
databaseName: data.databaseName,
|
||||
databaseUser:
|
||||
data.databaseUser || databasesUserDefaultPlaceholder[data.type],
|
||||
projectId,
|
||||
description: data.description,
|
||||
});
|
||||
} else if (data.type === "mongo") {
|
||||
promise = createMongo({
|
||||
name: data.name,
|
||||
dockerImage: defaultDockerImage,
|
||||
promise = mongoMutation.mutateAsync({
|
||||
...commonParams,
|
||||
databasePassword: data.databasePassword,
|
||||
databaseUser:
|
||||
data.databaseUser || databasesUserDefaultPlaceholder[data.type],
|
||||
projectId,
|
||||
description: data.description,
|
||||
});
|
||||
} else if (data.type === "redis") {
|
||||
promise = createRedis({
|
||||
name: data.name,
|
||||
dockerImage: defaultDockerImage,
|
||||
promise = redisMutation.mutateAsync({
|
||||
...commonParams,
|
||||
databasePassword: data.databasePassword,
|
||||
projectId,
|
||||
description: data.description,
|
||||
});
|
||||
} else if (data.type === "mariadb") {
|
||||
promise = createMariadb({
|
||||
name: data.name,
|
||||
dockerImage: defaultDockerImage,
|
||||
promise = mariadbMutation.mutateAsync({
|
||||
...commonParams,
|
||||
databasePassword: data.databasePassword,
|
||||
projectId,
|
||||
databaseRootPassword: data.databaseRootPassword,
|
||||
databaseName: data.databaseName,
|
||||
databaseUser:
|
||||
data.databaseUser || databasesUserDefaultPlaceholder[data.type],
|
||||
description: data.description,
|
||||
});
|
||||
} else if (data.type === "mysql") {
|
||||
promise = createMysql({
|
||||
name: data.name,
|
||||
dockerImage: defaultDockerImage,
|
||||
promise = mysqlMutation.mutateAsync({
|
||||
...commonParams,
|
||||
databasePassword: data.databasePassword,
|
||||
databaseName: data.databaseName,
|
||||
databaseUser:
|
||||
data.databaseUser || databasesUserDefaultPlaceholder[data.type],
|
||||
projectId,
|
||||
databaseRootPassword: data.databaseRootPassword,
|
||||
description: data.description,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -210,6 +231,17 @@ export const AddDatabase = ({ projectId }: Props) => {
|
||||
await promise
|
||||
.then(async () => {
|
||||
toast.success("Database Created");
|
||||
form.reset({
|
||||
type: "postgres",
|
||||
dockerImage: "",
|
||||
name: "",
|
||||
appName: `${projectName}-`,
|
||||
databasePassword: "",
|
||||
description: "",
|
||||
databaseName: "",
|
||||
databaseUser: "",
|
||||
});
|
||||
setVisible(false);
|
||||
await utils.project.one.invalidate({
|
||||
projectId,
|
||||
});
|
||||
@@ -220,7 +252,7 @@ export const AddDatabase = ({ projectId }: Props) => {
|
||||
}
|
||||
};
|
||||
return (
|
||||
<Dialog>
|
||||
<Dialog open={visible} onOpenChange={setVisible}>
|
||||
<DialogTrigger className="w-full">
|
||||
<DropdownMenuItem
|
||||
className="w-full cursor-pointer space-x-3"
|
||||
@@ -230,18 +262,10 @@ export const AddDatabase = ({ projectId }: Props) => {
|
||||
<span>Database</span>
|
||||
</DropdownMenuItem>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-2xl">
|
||||
<DialogContent className="max-h-screen md:max-h-[90vh] overflow-y-auto sm:max-w-2xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Databases</DialogTitle>
|
||||
</DialogHeader>
|
||||
{/* {isError && (
|
||||
<div className="flex items-center flex-row gap-4 rounded-lg bg-red-50 p-2 dark:bg-red-950">
|
||||
<AlertTriangle className="text-red-600 dark:text-red-400" />
|
||||
<span className="text-sm text-red-600 dark:text-red-400">
|
||||
{error?.message}
|
||||
</span>
|
||||
</div>
|
||||
)} */}
|
||||
|
||||
<Form {...form}>
|
||||
<form
|
||||
@@ -264,99 +288,40 @@ export const AddDatabase = ({ projectId }: Props) => {
|
||||
defaultValue={field.value}
|
||||
className="grid w-full grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4"
|
||||
>
|
||||
<FormItem className="flex w-full items-center space-x-3 space-y-0">
|
||||
<FormControl className="w-full">
|
||||
<div>
|
||||
<RadioGroupItem
|
||||
value="postgres"
|
||||
id="postgres"
|
||||
className="peer sr-only"
|
||||
/>
|
||||
<Label
|
||||
htmlFor="postgres"
|
||||
className="flex flex-col items-center justify-between rounded-md border-2 border-muted bg-popover p-4 hover:bg-accent hover:text-accent-foreground peer-data-[state=checked]:border-primary [&:has([data-state=checked])]:border-primary"
|
||||
>
|
||||
<PostgresqlIcon />
|
||||
Postgresql
|
||||
</Label>
|
||||
</div>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
<FormItem className="flex items-center space-x-3 space-y-0">
|
||||
<FormControl className="w-full">
|
||||
<div>
|
||||
<RadioGroupItem
|
||||
value="mysql"
|
||||
id="mysql"
|
||||
className="peer sr-only"
|
||||
/>
|
||||
<Label
|
||||
htmlFor="mysql"
|
||||
className="flex flex-col items-center justify-between rounded-md border-2 border-muted bg-popover p-4 hover:bg-accent hover:text-accent-foreground peer-data-[state=checked]:border-primary [&:has([data-state=checked])]:border-primary"
|
||||
>
|
||||
<MysqlIcon />
|
||||
Mysql
|
||||
</Label>
|
||||
</div>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
<FormItem className="flex items-center space-x-3 space-y-0">
|
||||
<FormControl className="w-full">
|
||||
<div>
|
||||
<RadioGroupItem
|
||||
value="mariadb"
|
||||
id="mariadb"
|
||||
className="peer sr-only"
|
||||
/>
|
||||
<Label
|
||||
htmlFor="mariadb"
|
||||
className="flex flex-col items-center justify-between rounded-md border-2 border-muted bg-popover p-4 hover:bg-accent hover:text-accent-foreground peer-data-[state=checked]:border-primary [&:has([data-state=checked])]:border-primary"
|
||||
>
|
||||
<MariadbIcon />
|
||||
Mariadb
|
||||
</Label>
|
||||
</div>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
<FormItem className="flex items-center space-x-3 space-y-0">
|
||||
<FormControl className="w-full">
|
||||
<div>
|
||||
<RadioGroupItem
|
||||
value="mongo"
|
||||
id="mongo"
|
||||
className="peer sr-only"
|
||||
/>
|
||||
<Label
|
||||
htmlFor="mongo"
|
||||
className="flex flex-col items-center justify-between rounded-md border-2 border-muted bg-popover p-4 hover:bg-accent hover:text-accent-foreground peer-data-[state=checked]:border-primary [&:has([data-state=checked])]:border-primary"
|
||||
>
|
||||
<MongodbIcon />
|
||||
Mongo
|
||||
</Label>
|
||||
</div>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
<FormItem className="flex items-center space-x-3 space-y-0">
|
||||
<FormControl className="w-full">
|
||||
<div>
|
||||
<RadioGroupItem
|
||||
value="redis"
|
||||
id="redis"
|
||||
className="peer sr-only"
|
||||
/>
|
||||
<Label
|
||||
htmlFor="redis"
|
||||
className="flex flex-col items-center justify-between rounded-md border-2 border-muted bg-popover p-4 hover:bg-accent hover:text-accent-foreground peer-data-[state=checked]:border-primary [&:has([data-state=checked])]:border-primary"
|
||||
>
|
||||
<RedisIcon />
|
||||
Redis
|
||||
</Label>
|
||||
</div>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
{Object.entries(databasesMap).map(([key, value]) => (
|
||||
<FormItem
|
||||
key={key}
|
||||
className="flex w-full items-center space-x-3 space-y-0"
|
||||
>
|
||||
<FormControl className="w-full">
|
||||
<div>
|
||||
<RadioGroupItem
|
||||
value={key}
|
||||
id={key}
|
||||
className="peer sr-only"
|
||||
/>
|
||||
<Label
|
||||
htmlFor={key}
|
||||
className="flex flex-col items-center justify-between rounded-md border-2 border-muted bg-popover p-4 hover:bg-accent hover:text-accent-foreground peer-data-[state=checked]:border-primary [&:has([data-state=checked])]:border-primary cursor-pointer"
|
||||
>
|
||||
{value.icon}
|
||||
{value.label}
|
||||
</Label>
|
||||
</div>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
))}
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
{activeMutation[field.value].isError && (
|
||||
<div className="flex flex-row gap-4 rounded-lg bg-red-50 p-2 dark:bg-red-950">
|
||||
<AlertTriangle className="text-red-600 dark:text-red-400" />
|
||||
<span className="text-sm text-red-600 dark:text-red-400">
|
||||
{activeMutation[field.value].error?.message}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
@@ -372,13 +337,34 @@ export const AddDatabase = ({ projectId }: Props) => {
|
||||
<FormItem>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Name" {...field} />
|
||||
<Input
|
||||
placeholder="Name"
|
||||
{...field}
|
||||
onChange={(e) => {
|
||||
const val = e.target.value?.trim() || "";
|
||||
form.setValue("appName", `${slug}-${val}`);
|
||||
field.onChange(val);
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="appName"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>AppName</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="my-app" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
|
||||
@@ -50,17 +50,13 @@ export const DeployRedis = ({ redisId }: Props) => {
|
||||
applicationStatus: "running",
|
||||
})
|
||||
.then(async () => {
|
||||
toast.success("Database Deploying....");
|
||||
toast.success("Deploying Database...");
|
||||
await refetch();
|
||||
await deploy({
|
||||
redisId,
|
||||
})
|
||||
.then(() => {
|
||||
toast.success("Database Deployed Succesfully");
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error to deploy Database");
|
||||
});
|
||||
}).catch(() => {
|
||||
toast.error("Error to deploy Database");
|
||||
});
|
||||
await refetch();
|
||||
})
|
||||
.catch((e) => {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
@@ -129,7 +130,7 @@ export const ShowExternalRedisCredentials = ({ redisId }: Props) => {
|
||||
<div className="grid w-full gap-8">
|
||||
<div className="flex flex-col gap-3">
|
||||
<Label>External Host</Label>
|
||||
<Input disabled value={connectionUrl} />
|
||||
<ToggleVisibilityInput value={connectionUrl} disabled />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { api } from "@/utils/api";
|
||||
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
|
||||
|
||||
interface Props {
|
||||
redisId: string;
|
||||
@@ -25,10 +26,9 @@ export const ShowInternalRedisCredentials = ({ redisId }: Props) => {
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label>Password</Label>
|
||||
<div className="flex flex-row gap-4">
|
||||
<Input
|
||||
disabled
|
||||
<ToggleVisibilityInput
|
||||
value={data?.databasePassword}
|
||||
type="password"
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -44,7 +44,7 @@ export const ShowInternalRedisCredentials = ({ redisId }: Props) => {
|
||||
|
||||
<div className="flex flex-col gap-2 md:col-span-2">
|
||||
<Label>Internal Connection URL </Label>
|
||||
<Input
|
||||
<ToggleVisibilityInput
|
||||
disabled
|
||||
value={`redis://default:${data?.databasePassword}@${data?.appName}:6379`}
|
||||
/>
|
||||
|
||||
26
components/shared/toggle-visibility-input.tsx
Normal file
26
components/shared/toggle-visibility-input.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import { useState } from "react";
|
||||
import { EyeIcon, EyeOffIcon } from "lucide-react";
|
||||
import { Input, type InputProps } from "../ui/input";
|
||||
import { Button } from "../ui/button";
|
||||
|
||||
export const ToggleVisibilityInput = ({ ...props }: InputProps) => {
|
||||
const [isPasswordVisible, setIsPasswordVisible] = useState(false);
|
||||
|
||||
const togglePasswordVisibility = () => {
|
||||
setIsPasswordVisible((prevVisibility) => !prevVisibility);
|
||||
};
|
||||
|
||||
const inputType = isPasswordVisible ? "text" : "password";
|
||||
return (
|
||||
<div className="flex w-full items-center space-x-2">
|
||||
<Input type={inputType} {...props} />
|
||||
<Button onClick={togglePasswordVisibility} variant={"secondary"}>
|
||||
{inputType === "password" ? (
|
||||
<EyeIcon className="size-4 text-muted-foreground" />
|
||||
) : (
|
||||
<EyeOffIcon className="size-4 text-muted-foreground" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user