mirror of
https://github.com/andwati/dokploy.git
synced 2026-10-31 09:12:15 +00:00
refactor: update project structure to use environmentId instead of projectId across components and API routes; implement environment management features
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
import { TemplateGenerator } from "@/components/dashboard/project/ai/template-generator";
|
||||
|
||||
interface Props {
|
||||
projectId: string;
|
||||
environmentId: string;
|
||||
projectName?: string;
|
||||
}
|
||||
|
||||
export const AddAiAssistant = ({ projectId }: Props) => {
|
||||
return <TemplateGenerator projectId={projectId} />;
|
||||
export const AddAiAssistant = ({ environmentId }: Props) => {
|
||||
return <TemplateGenerator environmentId={environmentId} />;
|
||||
};
|
||||
|
||||
@@ -64,11 +64,11 @@ const AddTemplateSchema = z.object({
|
||||
type AddTemplate = z.infer<typeof AddTemplateSchema>;
|
||||
|
||||
interface Props {
|
||||
projectId: string;
|
||||
environmentId: string;
|
||||
projectName?: string;
|
||||
}
|
||||
|
||||
export const AddApplication = ({ projectId, projectName }: Props) => {
|
||||
export const AddApplication = ({ environmentId, projectName }: Props) => {
|
||||
const utils = api.useUtils();
|
||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
||||
const [visible, setVisible] = useState(false);
|
||||
@@ -94,15 +94,15 @@ export const AddApplication = ({ projectId, projectName }: Props) => {
|
||||
name: data.name,
|
||||
appName: data.appName,
|
||||
description: data.description,
|
||||
projectId,
|
||||
environmentId,
|
||||
serverId: data.serverId,
|
||||
})
|
||||
.then(async () => {
|
||||
toast.success("Service Created");
|
||||
form.reset();
|
||||
setVisible(false);
|
||||
await utils.project.one.invalidate({
|
||||
projectId,
|
||||
await utils.environment.one.invalidate({
|
||||
environmentId,
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
|
||||
@@ -65,11 +65,11 @@ const AddComposeSchema = z.object({
|
||||
type AddCompose = z.infer<typeof AddComposeSchema>;
|
||||
|
||||
interface Props {
|
||||
projectId: string;
|
||||
environmentId: string;
|
||||
projectName?: string;
|
||||
}
|
||||
|
||||
export const AddCompose = ({ projectId, projectName }: Props) => {
|
||||
export const AddCompose = ({ environmentId, projectName }: Props) => {
|
||||
const utils = api.useUtils();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const slug = slugify(projectName);
|
||||
@@ -78,6 +78,9 @@ export const AddCompose = ({ projectId, projectName }: Props) => {
|
||||
const { mutateAsync, isLoading, error, isError } =
|
||||
api.compose.create.useMutation();
|
||||
|
||||
// Get environment data to extract projectId
|
||||
const { data: environment } = api.environment.one.useQuery({ environmentId });
|
||||
|
||||
const hasServers = servers && servers.length > 0;
|
||||
|
||||
const form = useForm<AddCompose>({
|
||||
@@ -98,7 +101,7 @@ export const AddCompose = ({ projectId, projectName }: Props) => {
|
||||
await mutateAsync({
|
||||
name: data.name,
|
||||
description: data.description,
|
||||
projectId,
|
||||
environmentId,
|
||||
composeType: data.composeType,
|
||||
appName: data.appName,
|
||||
serverId: data.serverId,
|
||||
@@ -106,8 +109,9 @@ export const AddCompose = ({ projectId, projectName }: Props) => {
|
||||
.then(async () => {
|
||||
toast.success("Compose Created");
|
||||
setVisible(false);
|
||||
await utils.project.one.invalidate({
|
||||
projectId,
|
||||
// Invalidate the project query to refresh the environment data
|
||||
await utils.environment.one.invalidate({
|
||||
environmentId,
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
|
||||
@@ -170,11 +170,11 @@ const databasesMap = {
|
||||
type AddDatabase = z.infer<typeof mySchema>;
|
||||
|
||||
interface Props {
|
||||
projectId: string;
|
||||
environmentId: string;
|
||||
projectName?: string;
|
||||
}
|
||||
|
||||
export const AddDatabase = ({ projectId, projectName }: Props) => {
|
||||
export const AddDatabase = ({ environmentId, projectName }: Props) => {
|
||||
const utils = api.useUtils();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const slug = slugify(projectName);
|
||||
@@ -185,6 +185,9 @@ export const AddDatabase = ({ projectId, projectName }: Props) => {
|
||||
const mariadbMutation = api.mariadb.create.useMutation();
|
||||
const mysqlMutation = api.mysql.create.useMutation();
|
||||
|
||||
// Get environment data to extract projectId
|
||||
const { data: environment } = api.environment.one.useQuery({ environmentId });
|
||||
|
||||
const hasServers = servers && servers.length > 0;
|
||||
|
||||
const form = useForm<AddDatabase>({
|
||||
@@ -219,7 +222,8 @@ export const AddDatabase = ({ projectId, projectName }: Props) => {
|
||||
name: data.name,
|
||||
appName: data.appName,
|
||||
dockerImage: defaultDockerImage,
|
||||
projectId,
|
||||
projectId: environment?.projectId || "",
|
||||
environmentId,
|
||||
serverId: data.serverId,
|
||||
description: data.description,
|
||||
};
|
||||
@@ -248,7 +252,6 @@ export const AddDatabase = ({ projectId, projectName }: Props) => {
|
||||
...commonParams,
|
||||
databasePassword: data.databasePassword,
|
||||
serverId: data.serverId,
|
||||
projectId,
|
||||
});
|
||||
} else if (data.type === "mariadb") {
|
||||
promise = mariadbMutation.mutateAsync({
|
||||
@@ -287,8 +290,9 @@ export const AddDatabase = ({ projectId, projectName }: Props) => {
|
||||
databaseUser: "",
|
||||
});
|
||||
setVisible(false);
|
||||
await utils.project.one.invalidate({
|
||||
projectId,
|
||||
// Invalidate the project query to refresh the environment data
|
||||
await utils.environment.one.invalidate({
|
||||
environmentId,
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
|
||||
@@ -73,11 +73,11 @@ import { api } from "@/utils/api";
|
||||
const TEMPLATE_BASE_URL_KEY = "dokploy_template_base_url";
|
||||
|
||||
interface Props {
|
||||
projectId: string;
|
||||
environmentId: string;
|
||||
baseUrl?: string;
|
||||
}
|
||||
|
||||
export const AddTemplate = ({ projectId, baseUrl }: Props) => {
|
||||
export const AddTemplate = ({ environmentId, baseUrl }: Props) => {
|
||||
const [query, setQuery] = useState("");
|
||||
const [open, setOpen] = useState(false);
|
||||
const [viewMode, setViewMode] = useState<"detailed" | "icon">("detailed");
|
||||
@@ -91,6 +91,9 @@ export const AddTemplate = ({ projectId, baseUrl }: Props) => {
|
||||
return undefined;
|
||||
});
|
||||
|
||||
// Get environment data to extract projectId
|
||||
const { data: environment } = api.environment.one.useQuery({ environmentId });
|
||||
|
||||
// Save to localStorage when customBaseUrl changes
|
||||
useEffect(() => {
|
||||
if (customBaseUrl) {
|
||||
@@ -490,7 +493,7 @@ export const AddTemplate = ({ projectId, baseUrl }: Props) => {
|
||||
disabled={isLoading}
|
||||
onClick={async () => {
|
||||
const promise = mutateAsync({
|
||||
projectId,
|
||||
environmentId,
|
||||
serverId: serverId || undefined,
|
||||
id: template.id,
|
||||
baseUrl: customBaseUrl,
|
||||
@@ -498,8 +501,9 @@ export const AddTemplate = ({ projectId, baseUrl }: Props) => {
|
||||
toast.promise(promise, {
|
||||
loading: "Setting up...",
|
||||
success: () => {
|
||||
utils.project.one.invalidate({
|
||||
projectId,
|
||||
// Invalidate the project query to refresh the environment data
|
||||
utils.environment.one.invalidate({
|
||||
environmentId,
|
||||
});
|
||||
setOpen(false);
|
||||
return `${template.name} template created successfully`;
|
||||
|
||||
@@ -0,0 +1,382 @@
|
||||
import { useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { api } from "@/utils/api";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { toast } from "sonner";
|
||||
import { ChevronDownIcon, PlusIcon, PencilIcon, TrashIcon } from "lucide-react";
|
||||
|
||||
interface Environment {
|
||||
environmentId: string;
|
||||
name: string;
|
||||
description?: string | null;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
interface AdvancedEnvironmentSelectorProps {
|
||||
projectId: string;
|
||||
environments: Environment[];
|
||||
currentEnvironmentId?: string;
|
||||
}
|
||||
|
||||
export const AdvancedEnvironmentSelector = ({
|
||||
projectId,
|
||||
environments,
|
||||
currentEnvironmentId,
|
||||
}: AdvancedEnvironmentSelectorProps) => {
|
||||
const router = useRouter();
|
||||
const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false);
|
||||
const [isEditDialogOpen, setIsEditDialogOpen] = useState(false);
|
||||
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
|
||||
const [selectedEnvironment, setSelectedEnvironment] = useState<Environment | null>(null);
|
||||
|
||||
// Form states
|
||||
const [name, setName] = useState("");
|
||||
const [description, setDescription] = useState("");
|
||||
|
||||
// API mutations
|
||||
const createEnvironment = api.environment.create.useMutation();
|
||||
const updateEnvironment = api.environment.update.useMutation();
|
||||
const deleteEnvironment = api.environment.remove.useMutation();
|
||||
const duplicateEnvironment = api.environment.duplicate.useMutation();
|
||||
|
||||
// Refetch project data
|
||||
const utils = api.useUtils();
|
||||
|
||||
const handleCreateEnvironment = async () => {
|
||||
try {
|
||||
await createEnvironment.mutateAsync({
|
||||
projectId,
|
||||
name: name.trim(),
|
||||
description: description.trim() || null,
|
||||
});
|
||||
|
||||
toast.success("Environment created successfully");
|
||||
utils.project.one.invalidate({ projectId });
|
||||
setIsCreateDialogOpen(false);
|
||||
setName("");
|
||||
setDescription("");
|
||||
} catch (error) {
|
||||
toast.error("Failed to create environment");
|
||||
}
|
||||
};
|
||||
|
||||
const handleUpdateEnvironment = async () => {
|
||||
if (!selectedEnvironment) return;
|
||||
|
||||
try {
|
||||
await updateEnvironment.mutateAsync({
|
||||
environmentId: selectedEnvironment.environmentId,
|
||||
name: name.trim(),
|
||||
description: description.trim() || null,
|
||||
});
|
||||
|
||||
toast.success("Environment updated successfully");
|
||||
utils.project.one.invalidate({ projectId });
|
||||
setIsEditDialogOpen(false);
|
||||
setSelectedEnvironment(null);
|
||||
setName("");
|
||||
setDescription("");
|
||||
} catch (error) {
|
||||
toast.error("Failed to update environment");
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteEnvironment = async () => {
|
||||
if (!selectedEnvironment) return;
|
||||
|
||||
try {
|
||||
await deleteEnvironment.mutateAsync({
|
||||
environmentId: selectedEnvironment.environmentId,
|
||||
});
|
||||
|
||||
toast.success("Environment deleted successfully");
|
||||
utils.project.one.invalidate({ projectId });
|
||||
setIsDeleteDialogOpen(false);
|
||||
setSelectedEnvironment(null);
|
||||
|
||||
// Redirect to production if we deleted the current environment
|
||||
if (selectedEnvironment.environmentId === currentEnvironmentId) {
|
||||
const productionEnv = environments.find(env => env.name === "production");
|
||||
if (productionEnv) {
|
||||
router.push(`/dashboard/project/${projectId}/environment/${productionEnv.environmentId}`);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error("Failed to delete environment");
|
||||
}
|
||||
};
|
||||
|
||||
const handleDuplicateEnvironment = async (environment: Environment) => {
|
||||
try {
|
||||
const result = await duplicateEnvironment.mutateAsync({
|
||||
environmentId: environment.environmentId,
|
||||
name: `${environment.name}-copy`,
|
||||
description: environment.description,
|
||||
});
|
||||
|
||||
toast.success("Environment duplicated successfully");
|
||||
utils.project.one.invalidate({ projectId });
|
||||
|
||||
// Navigate to the new duplicated environment
|
||||
router.push(`/dashboard/project/${projectId}/environment/${result.environmentId}`);
|
||||
} catch (error) {
|
||||
toast.error("Failed to duplicate environment");
|
||||
}
|
||||
};
|
||||
|
||||
const openEditDialog = (environment: Environment) => {
|
||||
setSelectedEnvironment(environment);
|
||||
setName(environment.name);
|
||||
setDescription(environment.description || "");
|
||||
setIsEditDialogOpen(true);
|
||||
};
|
||||
|
||||
const openDeleteDialog = (environment: Environment) => {
|
||||
setSelectedEnvironment(environment);
|
||||
setIsDeleteDialogOpen(true);
|
||||
};
|
||||
|
||||
const currentEnv = environments.find(env => env.environmentId === currentEnvironmentId);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" className="min-w-[200px] justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<span>{currentEnv?.name || "Select Environment"}</span>
|
||||
{currentEnv?.name === "production" && (
|
||||
<span className="px-1.5 py-0.5 text-xs bg-green-100 text-green-800 rounded">
|
||||
Prod
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<ChevronDownIcon className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-[300px]" align="start">
|
||||
<DropdownMenuLabel>Environments</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
{environments.map((environment) => (
|
||||
<div key={environment.environmentId} className="flex items-center">
|
||||
<DropdownMenuItem
|
||||
className="flex-1 cursor-pointer"
|
||||
onClick={() => {
|
||||
router.push(`/dashboard/project/${projectId}/environment/${environment.environmentId}`);
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center justify-between w-full">
|
||||
<div className="flex items-center gap-2">
|
||||
<span>{environment.name}</span>
|
||||
{environment.name === "production" && (
|
||||
<span className="px-1.5 py-0.5 text-xs bg-green-100 text-green-800 rounded">
|
||||
Prod
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{environment.environmentId === currentEnvironmentId && (
|
||||
<div className="w-2 h-2 bg-blue-500 rounded-full" />
|
||||
)}
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
|
||||
{/* Action buttons for non-production environments */}
|
||||
{environment.name !== "production" && (
|
||||
<div className="flex items-center gap-1 px-2">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-6 w-6 p-0"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
openEditDialog(environment);
|
||||
}}
|
||||
>
|
||||
<PencilIcon className="h-3 w-3" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-6 w-6 p-0 text-red-600 hover:text-red-700"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
openDeleteDialog(environment);
|
||||
}}
|
||||
>
|
||||
<TrashIcon className="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
className="cursor-pointer"
|
||||
onClick={() => setIsCreateDialogOpen(true)}
|
||||
>
|
||||
<PlusIcon className="h-4 w-4 mr-2" />
|
||||
Create Environment
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
{/* Create Environment Dialog */}
|
||||
<Dialog open={isCreateDialogOpen} onOpenChange={setIsCreateDialogOpen}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Create Environment</DialogTitle>
|
||||
<DialogDescription>
|
||||
Create a new environment for your project.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<Label htmlFor="name">Name</Label>
|
||||
<Input
|
||||
id="name"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
placeholder="Environment name"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="description">Description (optional)</Label>
|
||||
<Textarea
|
||||
id="description"
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
placeholder="Environment description"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
setIsCreateDialogOpen(false);
|
||||
setName("");
|
||||
setDescription("");
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleCreateEnvironment}
|
||||
disabled={!name.trim() || createEnvironment.isPending}
|
||||
>
|
||||
{createEnvironment.isPending ? "Creating..." : "Create"}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* Edit Environment Dialog */}
|
||||
<Dialog open={isEditDialogOpen} onOpenChange={setIsEditDialogOpen}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Edit Environment</DialogTitle>
|
||||
<DialogDescription>
|
||||
Update the environment details.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<Label htmlFor="edit-name">Name</Label>
|
||||
<Input
|
||||
id="edit-name"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
placeholder="Environment name"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="edit-description">Description (optional)</Label>
|
||||
<Textarea
|
||||
id="edit-description"
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
placeholder="Environment description"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
setIsEditDialogOpen(false);
|
||||
setSelectedEnvironment(null);
|
||||
setName("");
|
||||
setDescription("");
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleUpdateEnvironment}
|
||||
disabled={!name.trim() || updateEnvironment.isPending}
|
||||
>
|
||||
{updateEnvironment.isPending ? "Updating..." : "Update"}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* Delete Environment Dialog */}
|
||||
<Dialog open={isDeleteDialogOpen} onOpenChange={setIsDeleteDialogOpen}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Delete Environment</DialogTitle>
|
||||
<DialogDescription>
|
||||
Are you sure you want to delete the environment "{selectedEnvironment?.name}"?
|
||||
This action cannot be undone and will also delete all services in this environment.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<DialogFooter>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
setIsDeleteDialogOpen(false);
|
||||
setSelectedEnvironment(null);
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={handleDeleteEnvironment}
|
||||
disabled={deleteEnvironment.isPending}
|
||||
>
|
||||
{deleteEnvironment.isPending ? "Deleting..." : "Delete"}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -90,11 +90,11 @@ export const { useStepper, steps, Scoped } = defineStepper(
|
||||
);
|
||||
|
||||
interface Props {
|
||||
projectId: string;
|
||||
environmentId: string;
|
||||
projectName?: string;
|
||||
}
|
||||
|
||||
export const TemplateGenerator = ({ projectId }: Props) => {
|
||||
export const TemplateGenerator = ({ environmentId }: Props) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const stepper = useStepper();
|
||||
const { data: aiSettings } = api.ai.getAll.useQuery();
|
||||
@@ -103,6 +103,9 @@ export const TemplateGenerator = ({ projectId }: Props) => {
|
||||
useState<TemplateInfo>(defaultTemplateInfo);
|
||||
const utils = api.useUtils();
|
||||
|
||||
// Get environment data to extract projectId
|
||||
const { data: environment } = api.environment.one.useQuery({ environmentId });
|
||||
|
||||
const haveAtleasOneProviderEnabled = aiSettings?.some(
|
||||
(ai) => ai.isEnabled === true,
|
||||
);
|
||||
@@ -121,7 +124,7 @@ export const TemplateGenerator = ({ projectId }: Props) => {
|
||||
|
||||
const onSubmit = async () => {
|
||||
await mutateAsync({
|
||||
projectId,
|
||||
projectId: environment?.projectId || "",
|
||||
id: templateInfo.details?.id || "",
|
||||
name: templateInfo?.details?.name || "",
|
||||
description: templateInfo?.details?.shortDescription || "",
|
||||
@@ -138,9 +141,8 @@ export const TemplateGenerator = ({ projectId }: Props) => {
|
||||
.then(async () => {
|
||||
toast.success("Compose Created");
|
||||
setOpen(false);
|
||||
await utils.project.one.invalidate({
|
||||
projectId,
|
||||
});
|
||||
// Invalidate the project query to refresh the environment data
|
||||
await utils.project.one.invalidate();
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error creating the compose");
|
||||
|
||||
@@ -96,22 +96,8 @@ export const ShowProjects = () => {
|
||||
new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
|
||||
break;
|
||||
case "services": {
|
||||
const aTotalServices =
|
||||
a.mariadb.length +
|
||||
a.mongo.length +
|
||||
a.mysql.length +
|
||||
a.postgres.length +
|
||||
a.redis.length +
|
||||
a.applications.length +
|
||||
a.compose.length;
|
||||
const bTotalServices =
|
||||
b.mariadb.length +
|
||||
b.mongo.length +
|
||||
b.mysql.length +
|
||||
b.postgres.length +
|
||||
b.redis.length +
|
||||
b.applications.length +
|
||||
b.compose.length;
|
||||
const aTotalServices = a.environments.length;
|
||||
const bTotalServices = b.environments.length;
|
||||
comparison = aTotalServices - bTotalServices;
|
||||
break;
|
||||
}
|
||||
@@ -201,23 +187,23 @@ export const ShowProjects = () => {
|
||||
)}
|
||||
<div className="w-full grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4 3xl:grid-cols-5 flex-wrap gap-5">
|
||||
{filteredProjects?.map((project) => {
|
||||
const emptyServices =
|
||||
project?.mariadb.length === 0 &&
|
||||
project?.mongo.length === 0 &&
|
||||
project?.mysql.length === 0 &&
|
||||
project?.postgres.length === 0 &&
|
||||
project?.redis.length === 0 &&
|
||||
project?.applications.length === 0 &&
|
||||
project?.compose.length === 0;
|
||||
// const emptyServices =
|
||||
// project?.environments.length === 0 &&
|
||||
// project?.mongo.length === 0 &&
|
||||
// project?.environments.mysql.length === 0 &&
|
||||
// project?.environments.postgres.length === 0 &&
|
||||
// project?.environments.redis.length === 0 &&
|
||||
// project?.applications.length === 0 &&
|
||||
// project?.compose.length === 0;
|
||||
|
||||
const totalServices =
|
||||
project?.mariadb.length +
|
||||
project?.mongo.length +
|
||||
project?.mysql.length +
|
||||
project?.postgres.length +
|
||||
project?.redis.length +
|
||||
project?.applications.length +
|
||||
project?.compose.length;
|
||||
// const totalServices =
|
||||
// project?.mariadb.length +
|
||||
// project?.mongo.length +
|
||||
// project?.mysql.length +
|
||||
// project?.postgres.length +
|
||||
// project?.redis.length +
|
||||
// project?.applications.length +
|
||||
// project?.compose.length;
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -228,7 +214,7 @@ export const ShowProjects = () => {
|
||||
href={`/dashboard/project/${project.projectId}`}
|
||||
>
|
||||
<Card className="group relative w-full h-full bg-transparent transition-colors hover:bg-border">
|
||||
{project.applications.length > 0 ||
|
||||
{/* {project.applications.length > 0 ||
|
||||
project.compose.length > 0 ? (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
@@ -322,7 +308,7 @@ export const ShowProjects = () => {
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
) : null}
|
||||
) : null} */}
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center justify-between gap-2">
|
||||
<span className="flex flex-col gap-1.5">
|
||||
@@ -393,7 +379,7 @@ export const ShowProjects = () => {
|
||||
Are you sure to delete this
|
||||
project?
|
||||
</AlertDialogTitle>
|
||||
{!emptyServices ? (
|
||||
{/* {!emptyServices ? (
|
||||
<div className="flex flex-row gap-4 rounded-lg bg-yellow-50 p-2 dark:bg-yellow-950">
|
||||
<AlertTriangle className="text-yellow-600 dark:text-yellow-400" />
|
||||
<span className="text-sm text-yellow-600 dark:text-yellow-400">
|
||||
@@ -407,14 +393,14 @@ export const ShowProjects = () => {
|
||||
This action cannot be
|
||||
undone
|
||||
</AlertDialogDescription>
|
||||
)}
|
||||
)} */}
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>
|
||||
Cancel
|
||||
</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
disabled={!emptyServices}
|
||||
// disabled={!emptyServices}
|
||||
onClick={async () => {
|
||||
await mutateAsync({
|
||||
projectId:
|
||||
@@ -452,12 +438,12 @@ export const ShowProjects = () => {
|
||||
<DateTooltip date={project.createdAt}>
|
||||
Created
|
||||
</DateTooltip>
|
||||
<span>
|
||||
{/* <span>
|
||||
{totalServices}{" "}
|
||||
{totalServices === 1
|
||||
? "service"
|
||||
: "services"}
|
||||
</span>
|
||||
</span> */}
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
|
||||
@@ -20,10 +20,10 @@ import {
|
||||
CommandSeparator,
|
||||
} from "@/components/ui/command";
|
||||
import { authClient } from "@/lib/auth-client";
|
||||
import {
|
||||
extractServices,
|
||||
type Services,
|
||||
} from "@/pages/dashboard/project/[projectId]";
|
||||
// import {
|
||||
// extractServices,
|
||||
// type Services,
|
||||
// } from "@/pages/dashboard/project/[projectId]";
|
||||
import { api } from "@/utils/api";
|
||||
import { StatusTooltip } from "../shared/status-tooltip";
|
||||
|
||||
@@ -51,7 +51,7 @@ export const SearchCommand = () => {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<CommandDialog open={open} onOpenChange={setOpen}>
|
||||
{/* <CommandDialog open={open} onOpenChange={setOpen}>
|
||||
<CommandInput
|
||||
placeholder={"Search projects or settings"}
|
||||
value={search}
|
||||
@@ -181,7 +181,7 @@ export const SearchCommand = () => {
|
||||
</CommandItem>
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</CommandDialog>
|
||||
</CommandDialog> */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user