mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-06-15 20:25:23 +02:00
Merge pull request #3857 from Dokploy/feat/improve-queries
feat: enhance project and environment services with additional column…
This commit is contained in:
@@ -25,7 +25,6 @@ import {
|
||||
import { api } from "@/utils/api";
|
||||
|
||||
export type Services = {
|
||||
appName: string;
|
||||
serverId?: string | null;
|
||||
name: string;
|
||||
type:
|
||||
|
||||
@@ -2,7 +2,6 @@ import {
|
||||
AlertTriangle,
|
||||
ArrowUpDown,
|
||||
BookIcon,
|
||||
ExternalLinkIcon,
|
||||
FolderInput,
|
||||
Loader2,
|
||||
MoreHorizontalIcon,
|
||||
@@ -16,7 +15,6 @@ import { toast } from "sonner";
|
||||
import { BreadcrumbSidebar } from "@/components/shared/breadcrumb-sidebar";
|
||||
import { DateTooltip } from "@/components/shared/date-tooltip";
|
||||
import { FocusShortcutInput } from "@/components/shared/focus-shortcut-input";
|
||||
import { StatusTooltip } from "@/components/shared/status-tooltip";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
@@ -40,10 +38,8 @@ import {
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import {
|
||||
@@ -280,14 +276,6 @@ export const ShowProjects = () => {
|
||||
)
|
||||
.reduce((acc, curr) => acc + curr, 0);
|
||||
|
||||
const haveServicesWithDomains = project?.environments
|
||||
.map(
|
||||
(env) =>
|
||||
env.applications.length > 0 ||
|
||||
env.compose.length > 0,
|
||||
)
|
||||
.some(Boolean);
|
||||
|
||||
// Find default environment from accessible environments, or fall back to first accessible environment
|
||||
const accessibleEnvironment =
|
||||
project?.environments.find((env) => env.isDefault) ||
|
||||
@@ -313,122 +301,6 @@ export const ShowProjects = () => {
|
||||
}}
|
||||
>
|
||||
<Card className="group relative w-full h-full bg-transparent transition-colors hover:bg-border">
|
||||
{haveServicesWithDomains ? (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
className="absolute -right-3 -top-3 size-9 translate-y-1 rounded-full p-0 opacity-0 transition-all duration-200 group-hover:translate-y-0 group-hover:opacity-100"
|
||||
size="sm"
|
||||
variant="default"
|
||||
>
|
||||
<ExternalLinkIcon className="size-3.5" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
className="w-[200px] space-y-2 overflow-y-auto max-h-[400px]"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{project.environments.some(
|
||||
(env) => env.applications.length > 0,
|
||||
) && (
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuLabel>
|
||||
Applications
|
||||
</DropdownMenuLabel>
|
||||
{project.environments.map((env) =>
|
||||
env.applications.map((app) => (
|
||||
<div key={app.applicationId}>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuLabel className="font-normal capitalize text-xs flex items-center justify-between">
|
||||
{app.name}
|
||||
<StatusTooltip
|
||||
status={
|
||||
app.applicationStatus
|
||||
}
|
||||
/>
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
{app.domains.map((domain) => (
|
||||
<DropdownMenuItem
|
||||
key={domain.domainId}
|
||||
asChild
|
||||
>
|
||||
<Link
|
||||
className="space-x-4 text-xs cursor-pointer justify-between"
|
||||
target="_blank"
|
||||
href={`${
|
||||
domain.https
|
||||
? "https"
|
||||
: "http"
|
||||
}://${domain.host}${
|
||||
domain.path
|
||||
}`}
|
||||
>
|
||||
<span className="truncate">
|
||||
{domain.host}
|
||||
</span>
|
||||
<ExternalLinkIcon className="size-4 shrink-0" />
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuGroup>
|
||||
</div>
|
||||
)),
|
||||
)}
|
||||
</DropdownMenuGroup>
|
||||
)}
|
||||
{project.environments.some(
|
||||
(env) => env.compose.length > 0,
|
||||
) && (
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuLabel>
|
||||
Compose
|
||||
</DropdownMenuLabel>
|
||||
{project.environments.map((env) =>
|
||||
env.compose.map((comp) => (
|
||||
<div key={comp.composeId}>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuLabel className="font-normal capitalize text-xs flex items-center justify-between">
|
||||
{comp.name}
|
||||
<StatusTooltip
|
||||
status={comp.composeStatus}
|
||||
/>
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
{comp.domains.map((domain) => (
|
||||
<DropdownMenuItem
|
||||
key={domain.domainId}
|
||||
asChild
|
||||
>
|
||||
<Link
|
||||
className="space-x-4 text-xs cursor-pointer justify-between"
|
||||
target="_blank"
|
||||
href={`${
|
||||
domain.https
|
||||
? "https"
|
||||
: "http"
|
||||
}://${domain.host}${
|
||||
domain.path
|
||||
}`}
|
||||
>
|
||||
<span className="truncate">
|
||||
{domain.host}
|
||||
</span>
|
||||
<ExternalLinkIcon className="size-4 shrink-0" />
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuGroup>
|
||||
</div>
|
||||
)),
|
||||
)}
|
||||
</DropdownMenuGroup>
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
) : null}
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center justify-between gap-2 overflow-clip">
|
||||
<span className="flex flex-col gap-1.5 ">
|
||||
|
||||
@@ -100,7 +100,7 @@ export const SetupMonitoring = ({ serverId }: Props) => {
|
||||
|
||||
const url = useUrl();
|
||||
|
||||
const { data: projects } = api.project.all.useQuery();
|
||||
const { data: projects } = api.project.allForPermissions.useQuery();
|
||||
|
||||
const extractServicesFromProjects = () => {
|
||||
if (!projects) return [];
|
||||
|
||||
@@ -28,8 +28,12 @@ import {
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { api, type RouterOutputs } from "@/utils/api";
|
||||
|
||||
type Project = RouterOutputs["project"]["all"][number];
|
||||
type Environment = Project["environments"][number];
|
||||
/** Shape returned by project.allForPermissions (admin only). Used for the permissions UI. */
|
||||
type ProjectForPermissions =
|
||||
RouterOutputs["project"]["allForPermissions"][number];
|
||||
type EnvironmentForPermissions = ProjectForPermissions["environments"][number];
|
||||
|
||||
type Environment = EnvironmentForPermissions;
|
||||
|
||||
export type Services = {
|
||||
appName: string;
|
||||
@@ -173,7 +177,9 @@ interface Props {
|
||||
|
||||
export const AddUserPermissions = ({ userId }: Props) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const { data: projects } = api.project.all.useQuery();
|
||||
const { data: projects } = api.project.allForPermissions.useQuery(undefined, {
|
||||
enabled: isOpen,
|
||||
});
|
||||
|
||||
const { data, refetch } = api.user.one.useQuery(
|
||||
{
|
||||
|
||||
@@ -100,7 +100,6 @@ import { appRouter } from "@/server/api/root";
|
||||
import { api } from "@/utils/api";
|
||||
|
||||
export type Services = {
|
||||
appName: string;
|
||||
serverId?: string | null;
|
||||
serverName?: string | null;
|
||||
name: string;
|
||||
@@ -146,7 +145,6 @@ export const extractServicesFromEnvironment = (
|
||||
}
|
||||
}
|
||||
return {
|
||||
appName: item.appName,
|
||||
name: item.name,
|
||||
type: "application",
|
||||
id: item.applicationId,
|
||||
@@ -161,7 +159,6 @@ export const extractServicesFromEnvironment = (
|
||||
|
||||
const mariadb: Services[] =
|
||||
environment.mariadb?.map((item) => ({
|
||||
appName: item.appName,
|
||||
name: item.name,
|
||||
type: "mariadb",
|
||||
id: item.mariadbId,
|
||||
@@ -174,7 +171,6 @@ export const extractServicesFromEnvironment = (
|
||||
|
||||
const postgres: Services[] =
|
||||
environment.postgres?.map((item) => ({
|
||||
appName: item.appName,
|
||||
name: item.name,
|
||||
type: "postgres",
|
||||
id: item.postgresId,
|
||||
@@ -187,7 +183,6 @@ export const extractServicesFromEnvironment = (
|
||||
|
||||
const mongo: Services[] =
|
||||
environment.mongo?.map((item) => ({
|
||||
appName: item.appName,
|
||||
name: item.name,
|
||||
type: "mongo",
|
||||
id: item.mongoId,
|
||||
@@ -200,7 +195,6 @@ export const extractServicesFromEnvironment = (
|
||||
|
||||
const redis: Services[] =
|
||||
environment.redis?.map((item) => ({
|
||||
appName: item.appName,
|
||||
name: item.name,
|
||||
type: "redis",
|
||||
id: item.redisId,
|
||||
@@ -213,7 +207,6 @@ export const extractServicesFromEnvironment = (
|
||||
|
||||
const mysql: Services[] =
|
||||
environment.mysql?.map((item) => ({
|
||||
appName: item.appName,
|
||||
name: item.name,
|
||||
type: "mysql",
|
||||
id: item.mysqlId,
|
||||
@@ -242,7 +235,6 @@ export const extractServicesFromEnvironment = (
|
||||
}
|
||||
}
|
||||
return {
|
||||
appName: item.appName,
|
||||
name: item.name,
|
||||
type: "compose",
|
||||
id: item.composeId,
|
||||
|
||||
@@ -37,7 +37,11 @@ import { TRPCError } from "@trpc/server";
|
||||
import { and, desc, eq, ilike, or, sql } from "drizzle-orm";
|
||||
import type { AnyPgColumn } from "drizzle-orm/pg-core";
|
||||
import { z } from "zod";
|
||||
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
|
||||
import {
|
||||
adminProcedure,
|
||||
createTRPCRouter,
|
||||
protectedProcedure,
|
||||
} from "@/server/api/trpc";
|
||||
import {
|
||||
apiCreateProject,
|
||||
apiFindOneProject,
|
||||
@@ -219,31 +223,69 @@ export const projectRouter = createTRPCRouter({
|
||||
applications.applicationId,
|
||||
accessedServices,
|
||||
),
|
||||
with: { domains: true },
|
||||
columns: {
|
||||
applicationId: true,
|
||||
name: true,
|
||||
applicationStatus: true,
|
||||
},
|
||||
},
|
||||
mariadb: {
|
||||
where: buildServiceFilter(mariadb.mariadbId, accessedServices),
|
||||
columns: {
|
||||
mariadbId: true,
|
||||
name: true,
|
||||
applicationStatus: true,
|
||||
},
|
||||
},
|
||||
mongo: {
|
||||
where: buildServiceFilter(mongo.mongoId, accessedServices),
|
||||
columns: {
|
||||
mongoId: true,
|
||||
name: true,
|
||||
applicationStatus: true,
|
||||
},
|
||||
},
|
||||
mysql: {
|
||||
where: buildServiceFilter(mysql.mysqlId, accessedServices),
|
||||
columns: {
|
||||
mysqlId: true,
|
||||
name: true,
|
||||
applicationStatus: true,
|
||||
},
|
||||
},
|
||||
postgres: {
|
||||
where: buildServiceFilter(
|
||||
postgres.postgresId,
|
||||
accessedServices,
|
||||
),
|
||||
columns: {
|
||||
postgresId: true,
|
||||
name: true,
|
||||
applicationStatus: true,
|
||||
},
|
||||
},
|
||||
redis: {
|
||||
where: buildServiceFilter(redis.redisId, accessedServices),
|
||||
columns: {
|
||||
redisId: true,
|
||||
name: true,
|
||||
applicationStatus: true,
|
||||
},
|
||||
},
|
||||
compose: {
|
||||
where: buildServiceFilter(compose.composeId, accessedServices),
|
||||
with: { domains: true },
|
||||
columns: {
|
||||
composeId: true,
|
||||
name: true,
|
||||
composeStatus: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
environmentId: true,
|
||||
isDefault: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: desc(projects.createdAt),
|
||||
@@ -255,21 +297,50 @@ export const projectRouter = createTRPCRouter({
|
||||
environments: {
|
||||
with: {
|
||||
applications: {
|
||||
with: {
|
||||
domains: true,
|
||||
columns: {
|
||||
applicationId: true,
|
||||
name: true,
|
||||
applicationStatus: true,
|
||||
},
|
||||
},
|
||||
mariadb: {
|
||||
columns: {
|
||||
mariadbId: true,
|
||||
},
|
||||
},
|
||||
mongo: {
|
||||
columns: {
|
||||
mongoId: true,
|
||||
},
|
||||
},
|
||||
mysql: {
|
||||
columns: {
|
||||
mysqlId: true,
|
||||
},
|
||||
},
|
||||
postgres: {
|
||||
columns: {
|
||||
postgresId: true,
|
||||
},
|
||||
},
|
||||
redis: {
|
||||
columns: {
|
||||
redisId: true,
|
||||
},
|
||||
},
|
||||
mariadb: true,
|
||||
mongo: true,
|
||||
mysql: true,
|
||||
postgres: true,
|
||||
redis: true,
|
||||
compose: {
|
||||
with: {
|
||||
domains: true,
|
||||
columns: {
|
||||
composeId: true,
|
||||
name: true,
|
||||
composeStatus: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
name: true,
|
||||
environmentId: true,
|
||||
isDefault: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
where: eq(projects.organizationId, ctx.session.activeOrganizationId),
|
||||
@@ -277,6 +348,106 @@ export const projectRouter = createTRPCRouter({
|
||||
});
|
||||
}),
|
||||
|
||||
/** All projects with full environments and services for the admin permissions UI. Admin only. */
|
||||
allForPermissions: adminProcedure.query(async ({ ctx }) => {
|
||||
return await db.query.projects.findMany({
|
||||
where: eq(projects.organizationId, ctx.session.activeOrganizationId),
|
||||
orderBy: desc(projects.createdAt),
|
||||
columns: {
|
||||
projectId: true,
|
||||
name: true,
|
||||
},
|
||||
with: {
|
||||
environments: {
|
||||
columns: {
|
||||
environmentId: true,
|
||||
name: true,
|
||||
isDefault: true,
|
||||
},
|
||||
with: {
|
||||
applications: {
|
||||
columns: {
|
||||
applicationId: true,
|
||||
appName: true,
|
||||
name: true,
|
||||
createdAt: true,
|
||||
applicationStatus: true,
|
||||
description: true,
|
||||
serverId: true,
|
||||
},
|
||||
},
|
||||
mariadb: {
|
||||
columns: {
|
||||
mariadbId: true,
|
||||
appName: true,
|
||||
name: true,
|
||||
createdAt: true,
|
||||
applicationStatus: true,
|
||||
description: true,
|
||||
serverId: true,
|
||||
},
|
||||
},
|
||||
postgres: {
|
||||
columns: {
|
||||
postgresId: true,
|
||||
appName: true,
|
||||
name: true,
|
||||
createdAt: true,
|
||||
applicationStatus: true,
|
||||
description: true,
|
||||
serverId: true,
|
||||
},
|
||||
},
|
||||
mysql: {
|
||||
columns: {
|
||||
mysqlId: true,
|
||||
appName: true,
|
||||
name: true,
|
||||
createdAt: true,
|
||||
applicationStatus: true,
|
||||
description: true,
|
||||
serverId: true,
|
||||
},
|
||||
},
|
||||
mongo: {
|
||||
columns: {
|
||||
mongoId: true,
|
||||
appName: true,
|
||||
name: true,
|
||||
createdAt: true,
|
||||
applicationStatus: true,
|
||||
description: true,
|
||||
serverId: true,
|
||||
},
|
||||
},
|
||||
redis: {
|
||||
columns: {
|
||||
redisId: true,
|
||||
appName: true,
|
||||
name: true,
|
||||
createdAt: true,
|
||||
applicationStatus: true,
|
||||
description: true,
|
||||
serverId: true,
|
||||
},
|
||||
},
|
||||
compose: {
|
||||
columns: {
|
||||
composeId: true,
|
||||
appName: true,
|
||||
name: true,
|
||||
createdAt: true,
|
||||
composeStatus: true,
|
||||
description: true,
|
||||
serverId: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}),
|
||||
|
||||
search: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
|
||||
@@ -34,42 +34,139 @@ export const createEnvironment = async (
|
||||
export const findEnvironmentById = async (environmentId: string) => {
|
||||
const environment = await db.query.environments.findFirst({
|
||||
where: eq(environments.environmentId, environmentId),
|
||||
columns: {
|
||||
name: true,
|
||||
description: true,
|
||||
environmentId: true,
|
||||
isDefault: true,
|
||||
projectId: true,
|
||||
env: true,
|
||||
},
|
||||
with: {
|
||||
applications: {
|
||||
with: {
|
||||
deployments: true,
|
||||
server: true,
|
||||
server: {
|
||||
columns: {
|
||||
name: true,
|
||||
serverId: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
name: true,
|
||||
applicationId: true,
|
||||
createdAt: true,
|
||||
applicationStatus: true,
|
||||
description: true,
|
||||
serverId: true,
|
||||
},
|
||||
},
|
||||
mariadb: {
|
||||
with: {
|
||||
server: true,
|
||||
server: {
|
||||
columns: {
|
||||
name: true,
|
||||
serverId: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
mariadbId: true,
|
||||
name: true,
|
||||
createdAt: true,
|
||||
applicationStatus: true,
|
||||
description: true,
|
||||
serverId: true,
|
||||
},
|
||||
},
|
||||
mongo: {
|
||||
with: {
|
||||
server: true,
|
||||
server: {
|
||||
columns: {
|
||||
name: true,
|
||||
serverId: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
mongoId: true,
|
||||
name: true,
|
||||
createdAt: true,
|
||||
applicationStatus: true,
|
||||
description: true,
|
||||
serverId: true,
|
||||
},
|
||||
},
|
||||
mysql: {
|
||||
with: {
|
||||
server: true,
|
||||
server: {
|
||||
columns: {
|
||||
name: true,
|
||||
serverId: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
mysqlId: true,
|
||||
name: true,
|
||||
createdAt: true,
|
||||
applicationStatus: true,
|
||||
description: true,
|
||||
serverId: true,
|
||||
},
|
||||
},
|
||||
postgres: {
|
||||
with: {
|
||||
server: true,
|
||||
server: {
|
||||
columns: {
|
||||
name: true,
|
||||
serverId: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
postgresId: true,
|
||||
name: true,
|
||||
description: true,
|
||||
createdAt: true,
|
||||
applicationStatus: true,
|
||||
serverId: true,
|
||||
},
|
||||
},
|
||||
redis: {
|
||||
with: {
|
||||
server: true,
|
||||
server: {
|
||||
columns: {
|
||||
name: true,
|
||||
serverId: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
redisId: true,
|
||||
name: true,
|
||||
createdAt: true,
|
||||
applicationStatus: true,
|
||||
description: true,
|
||||
serverId: true,
|
||||
},
|
||||
},
|
||||
compose: {
|
||||
with: {
|
||||
deployments: true,
|
||||
server: true,
|
||||
server: {
|
||||
columns: {
|
||||
name: true,
|
||||
serverId: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
composeId: true,
|
||||
name: true,
|
||||
createdAt: true,
|
||||
composeStatus: true,
|
||||
description: true,
|
||||
serverId: true,
|
||||
},
|
||||
},
|
||||
project: true,
|
||||
@@ -98,6 +195,12 @@ export const findEnvironmentsByProjectId = async (projectId: string) => {
|
||||
compose: true,
|
||||
project: true,
|
||||
},
|
||||
columns: {
|
||||
name: true,
|
||||
description: true,
|
||||
environmentId: true,
|
||||
isDefault: true,
|
||||
},
|
||||
});
|
||||
return projectEnvironments;
|
||||
};
|
||||
@@ -169,6 +272,7 @@ export const duplicateEnvironment = async (
|
||||
name: input.name,
|
||||
description: input.description || originalEnvironment.description,
|
||||
projectId: originalEnvironment.projectId,
|
||||
env: originalEnvironment.env,
|
||||
})
|
||||
.returning()
|
||||
.then((value) => value[0]);
|
||||
|
||||
Reference in New Issue
Block a user