feat: add Overview dashboard (Services, Backups & Domains tabs)

This commit is contained in:
Narciso
2026-08-13 17:03:42 -04:00
parent 1183c7c4a9
commit 046a608f8c
17 changed files with 2770 additions and 9 deletions
+1
View File
@@ -33,6 +33,7 @@ export * from "./services/mount";
export * from "./services/mysql";
export * from "./services/network";
export * from "./services/notification";
export * from "./services/overview";
export * from "./services/patch";
export * from "./services/patch-repo";
export * from "./services/port";
@@ -0,0 +1,183 @@
// No db/schema imports here — client components import this file directly, and dockerode/ssh2 (child_process) would break the browser bundle.
export type OverviewServiceType =
| "application"
| "postgres"
| "mysql"
| "mariadb"
| "mongo"
| "redis"
| "compose"
| "libsql";
export interface OverviewService {
id: string;
type: OverviewServiceType;
name: string;
appName: string;
status: string | null;
createdAt: string;
serverId: string | null;
serverName: string | null;
icon: string | null;
projectId: string;
projectName: string;
environmentId: string;
environmentName: string;
lastDeployAt: string | null;
}
export type OverviewSortBy =
| "name-asc"
| "name-desc"
| "type-asc"
| "type-desc"
| "createdAt-asc"
| "createdAt-desc"
| "lastDeploy-asc"
| "lastDeploy-desc";
export const sortOverviewServices = (
services: OverviewService[],
sortBy: OverviewSortBy,
): OverviewService[] => {
const [field, direction] = sortBy.split("-") as [string, "asc" | "desc"];
return [...services].sort((a, b) => {
if (field === "name") {
const cmp = a.name.localeCompare(b.name);
return direction === "asc" ? cmp : -cmp;
}
if (field === "type") {
const cmp = a.type.localeCompare(b.type);
return direction === "asc" ? cmp : -cmp;
}
if (field === "createdAt") {
const cmp =
new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
return direction === "asc" ? cmp : -cmp;
}
// lastDeploy: services without a deploy always sort to the end.
const aDate = a.lastDeployAt ? new Date(a.lastDeployAt).getTime() : null;
const bDate = b.lastDeployAt ? new Date(b.lastDeployAt).getTime() : null;
if (aDate === null && bDate === null) return 0;
if (aDate === null) return 1;
if (bDate === null) return -1;
return direction === "desc" ? bDate - aDate : aDate - bDate;
});
};
export const DB_ENGINE_ICON_TYPES = new Set([
"postgres",
"mariadb",
"mysql",
"mongo",
"redis",
"libsql",
]);
export const getServiceOverviewIcon = (service: {
type: OverviewServiceType;
icon: string | null;
}):
| { kind: "db"; engine: string }
| { kind: "custom"; url: string }
| { kind: "generic"; type: "application" | "compose" } => {
if (DB_ENGINE_ICON_TYPES.has(service.type)) {
return { kind: "db", engine: service.type };
}
if (service.icon) {
return { kind: "custom", url: service.icon };
}
return { kind: "generic", type: service.type as "application" | "compose" };
};
export type OverviewBackupKind = "backup" | "volumeBackup";
export interface OverviewBackup {
deploymentId: string;
kind: OverviewBackupKind;
status: string | null;
createdAt: string;
destinationId: string;
destinationName: string;
databaseType: string | null;
serviceType: string | null;
backupType: "database" | "compose" | null;
serviceName: string;
serviceOwnerId: string | null;
serviceOwnerType: OverviewServiceType | "web-server";
serverId: string | null;
projectId: string | null;
environmentId: string | null;
}
export type OverviewDomainOwnerType = "application" | "compose";
export interface OverviewDomain {
domainId: string;
host: string;
path: string | null;
port: number | null;
customEntrypoint: string | null;
https: boolean;
certificateType: "letsencrypt" | "none" | "custom";
createdAt: string;
enabled: boolean;
domainType: "application" | "compose" | "preview" | null;
serviceOwnerId: string;
serviceOwnerType: OverviewDomainOwnerType;
serviceName: string;
projectId: string;
projectName: string;
environmentId: string;
environmentName: string;
}
export type OverviewDomainSortBy =
| "createdAt-asc"
| "createdAt-desc"
| "port-asc"
| "port-desc";
export const sortOverviewDomains = (
domains: OverviewDomain[],
sortBy: OverviewDomainSortBy,
): OverviewDomain[] => {
const [field, direction] = sortBy.split("-") as [string, "asc" | "desc"];
return [...domains].sort((a, b) => {
if (field === "port") {
// Domains without a port sort to the end regardless of direction.
const aPort = a.port;
const bPort = b.port;
if (aPort === null && bPort === null) return 0;
if (aPort === null) return 1;
if (bPort === null) return -1;
return direction === "asc" ? aPort - bPort : bPort - aPort;
}
const cmp =
new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
return direction === "asc" ? cmp : -cmp;
});
};
export const getBackupOverviewIcon = (
row: Pick<
OverviewBackup,
"databaseType" | "serviceType" | "serviceOwnerType"
>,
):
| { kind: "db"; engine: string }
| { kind: "generic"; type: "application" | "compose" }
| { kind: "webServer" } => {
const engine = row.databaseType ?? row.serviceType;
if (engine === "web-server") {
return { kind: "webServer" };
}
if (engine && DB_ENGINE_ICON_TYPES.has(engine)) {
return { kind: "db", engine };
}
if (row.serviceOwnerType === "compose") {
return { kind: "generic", type: "compose" };
}
return { kind: "generic", type: "application" };
};
+810
View File
@@ -0,0 +1,810 @@
import { db } from "@dokploy/server/db";
import {
applications,
backups,
compose,
deployments,
destinations,
domains,
environments,
libsql,
mariadb,
mongo,
mysql,
postgres,
previewDeployments,
projects,
redis,
server as serverTable,
volumeBackups,
} from "@dokploy/server/db/schema";
import { and, desc, eq, inArray, isNull, max, or } from "drizzle-orm";
import type { AnyPgColumn } from "drizzle-orm/pg-core";
import type {
OverviewBackup,
OverviewDomain,
OverviewDomainOwnerType,
OverviewService,
OverviewServiceType,
} from "./overview-shared";
// Re-exported for existing "./overview" consumers; client components should import overview-shared directly.
export * from "./overview-shared";
type TypeQueryConfig = {
type: OverviewServiceType;
table:
| typeof applications
| typeof postgres
| typeof mysql
| typeof mariadb
| typeof mongo
| typeof redis
| typeof compose
| typeof libsql;
idColumn: { name: string };
statusColumn: { name: string };
hasIcon: boolean;
};
async function getServicesOfType(
config: TypeQueryConfig,
orgId: string,
accessedServices: string[] | null,
): Promise<OverviewService[]> {
const table = config.table as typeof applications;
const idCol = (table as unknown as Record<string, unknown>)[
config.idColumn.name
] as typeof applications.applicationId;
const statusCol = (table as unknown as Record<string, unknown>)[
config.statusColumn.name
] as typeof applications.applicationStatus;
const iconCol = config.hasIcon
? ((table as unknown as Record<string, unknown>)
.icon as typeof applications.icon)
: null;
const conditions = [
eq(projects.organizationId, orgId),
...(accessedServices !== null ? [inArray(idCol, accessedServices)] : []),
];
const baseSelect = {
id: idCol,
name: table.name,
appName: table.appName,
status: statusCol,
createdAt: table.createdAt,
serverId: table.serverId,
serverName: serverTable.name,
projectId: projects.projectId,
projectName: projects.name,
environmentId: environments.environmentId,
environmentName: environments.name,
};
// Only select icon when the table has that column — other service tables don't.
const rows = await db
.select(iconCol ? { ...baseSelect, icon: iconCol } : baseSelect)
.from(table)
.innerJoin(
environments,
eq(table.environmentId, environments.environmentId),
)
.innerJoin(projects, eq(environments.projectId, projects.projectId))
.leftJoin(serverTable, eq(table.serverId, serverTable.serverId))
.where(and(...conditions));
return rows.map((row) => ({
...row,
icon: "icon" in row ? (row.icon as string | null) : null,
type: config.type,
lastDeployAt: null,
}));
}
const SERVICE_TYPE_CONFIGS: TypeQueryConfig[] = [
{
type: "application",
table: applications,
idColumn: { name: "applicationId" },
statusColumn: { name: "applicationStatus" },
hasIcon: true,
},
{
type: "postgres",
table: postgres,
idColumn: { name: "postgresId" },
statusColumn: { name: "applicationStatus" },
hasIcon: false,
},
{
type: "mysql",
table: mysql,
idColumn: { name: "mysqlId" },
statusColumn: { name: "applicationStatus" },
hasIcon: false,
},
{
type: "mariadb",
table: mariadb,
idColumn: { name: "mariadbId" },
statusColumn: { name: "applicationStatus" },
hasIcon: false,
},
{
type: "mongo",
table: mongo,
idColumn: { name: "mongoId" },
statusColumn: { name: "applicationStatus" },
hasIcon: false,
},
{
type: "redis",
table: redis,
idColumn: { name: "redisId" },
statusColumn: { name: "applicationStatus" },
hasIcon: false,
},
{
type: "compose",
table: compose,
idColumn: { name: "composeId" },
statusColumn: { name: "composeStatus" },
hasIcon: true,
},
{
type: "libsql",
table: libsql,
idColumn: { name: "libsqlId" },
statusColumn: { name: "applicationStatus" },
hasIcon: false,
},
];
// Only application/compose have a deployments FK, so lastDeployAt only applies to those.
async function attachLastDeployAt(
services: OverviewService[],
): Promise<OverviewService[]> {
const applicationIds = services
.filter((s) => s.type === "application")
.map((s) => s.id);
const composeIds = services
.filter((s) => s.type === "compose")
.map((s) => s.id);
const [appDeploys, composeDeploys] = await Promise.all([
applicationIds.length > 0
? db
.select({
applicationId: deployments.applicationId,
lastDeployAt: max(deployments.createdAt),
})
.from(deployments)
.where(inArray(deployments.applicationId, applicationIds))
.groupBy(deployments.applicationId)
: Promise.resolve([]),
composeIds.length > 0
? db
.select({
composeId: deployments.composeId,
lastDeployAt: max(deployments.createdAt),
})
.from(deployments)
.where(inArray(deployments.composeId, composeIds))
.groupBy(deployments.composeId)
: Promise.resolve([]),
]);
const lastAppDeploy = new Map<string, string>();
for (const row of appDeploys) {
if (row.applicationId && row.lastDeployAt) {
lastAppDeploy.set(row.applicationId, row.lastDeployAt);
}
}
const lastComposeDeploy = new Map<string, string>();
for (const row of composeDeploys) {
if (row.composeId && row.lastDeployAt) {
lastComposeDeploy.set(row.composeId, row.lastDeployAt);
}
}
return services.map((service) => {
if (service.type === "application") {
return {
...service,
lastDeployAt: lastAppDeploy.get(service.id) ?? null,
};
}
if (service.type === "compose") {
return {
...service,
lastDeployAt: lastComposeDeploy.get(service.id) ?? null,
};
}
return service;
});
}
export const getAllServicesForOrganization = async (
orgId: string,
accessedServices: string[] | null,
): Promise<OverviewService[]> => {
if (accessedServices !== null && accessedServices.length === 0) {
return [];
}
const results = await Promise.all(
SERVICE_TYPE_CONFIGS.map((config) =>
getServicesOfType(config, orgId, accessedServices),
),
);
return attachLastDeployAt(results.flat());
};
type Owner = {
id: string;
name: string;
serverId: string | null;
type: OverviewServiceType;
projectId: string | null;
environmentId: string | null;
};
type OwnerRow = {
name: string;
serverId: string | null;
environment?: {
environmentId: string;
project?: { projectId: string } | null;
} | null;
};
function ownerFrom(
row: OwnerRow,
id: string,
type: OverviewServiceType,
): Owner {
return {
id,
name: row.name,
serverId: row.serverId,
type,
projectId: row.environment?.project?.projectId ?? null,
environmentId: row.environment?.environmentId ?? null,
};
}
function pickBackupOwner(backup: {
postgres?: (OwnerRow & { postgresId: string }) | null;
mariadb?: (OwnerRow & { mariadbId: string }) | null;
mysql?: (OwnerRow & { mysqlId: string }) | null;
mongo?: (OwnerRow & { mongoId: string }) | null;
libsql?: (OwnerRow & { libsqlId: string }) | null;
compose?: (OwnerRow & { composeId: string }) | null;
}): Owner | null {
if (backup.postgres)
return ownerFrom(backup.postgres, backup.postgres.postgresId, "postgres");
if (backup.mariadb)
return ownerFrom(backup.mariadb, backup.mariadb.mariadbId, "mariadb");
if (backup.mysql)
return ownerFrom(backup.mysql, backup.mysql.mysqlId, "mysql");
if (backup.mongo)
return ownerFrom(backup.mongo, backup.mongo.mongoId, "mongo");
if (backup.libsql)
return ownerFrom(backup.libsql, backup.libsql.libsqlId, "libsql");
if (backup.compose)
return ownerFrom(backup.compose, backup.compose.composeId, "compose");
return null;
}
function pickVolumeBackupOwner(volumeBackup: {
application?: (OwnerRow & { applicationId: string }) | null;
postgres?: (OwnerRow & { postgresId: string }) | null;
mariadb?: (OwnerRow & { mariadbId: string }) | null;
mysql?: (OwnerRow & { mysqlId: string }) | null;
mongo?: (OwnerRow & { mongoId: string }) | null;
redis?: (OwnerRow & { redisId: string }) | null;
libsql?: (OwnerRow & { libsqlId: string }) | null;
compose?: (OwnerRow & { composeId: string }) | null;
}): Owner | null {
if (volumeBackup.application)
return ownerFrom(
volumeBackup.application,
volumeBackup.application.applicationId,
"application",
);
if (volumeBackup.postgres)
return ownerFrom(
volumeBackup.postgres,
volumeBackup.postgres.postgresId,
"postgres",
);
if (volumeBackup.mariadb)
return ownerFrom(
volumeBackup.mariadb,
volumeBackup.mariadb.mariadbId,
"mariadb",
);
if (volumeBackup.mysql)
return ownerFrom(volumeBackup.mysql, volumeBackup.mysql.mysqlId, "mysql");
if (volumeBackup.mongo)
return ownerFrom(volumeBackup.mongo, volumeBackup.mongo.mongoId, "mongo");
if (volumeBackup.redis)
return ownerFrom(volumeBackup.redis, volumeBackup.redis.redisId, "redis");
if (volumeBackup.libsql)
return ownerFrom(
volumeBackup.libsql,
volumeBackup.libsql.libsqlId,
"libsql",
);
if (volumeBackup.compose)
return ownerFrom(
volumeBackup.compose,
volumeBackup.compose.composeId,
"compose",
);
return null;
}
// Ownerless backups (e.g. the Dokploy host's own database) are visible to everyone.
function ownerAccessCondition(
ownerColumns: AnyPgColumn[],
accessedServices: string[] | null,
) {
if (accessedServices === null) return undefined;
return or(
and(...ownerColumns.map((col) => isNull(col))),
...ownerColumns.map((col) => inArray(col, accessedServices)),
);
}
async function getBackupIdsInOrg(
orgId: string,
accessedServices: string[] | null,
): Promise<string[]> {
const accessCondition = ownerAccessCondition(
[
backups.postgresId,
backups.mariadbId,
backups.mysqlId,
backups.mongoId,
backups.libsqlId,
backups.composeId,
],
accessedServices,
);
const rows = await db
.select({ backupId: backups.backupId })
.from(backups)
.innerJoin(
destinations,
eq(backups.destinationId, destinations.destinationId),
)
.where(
and(
eq(destinations.organizationId, orgId),
...(accessCondition ? [accessCondition] : []),
),
);
return rows.map((r) => r.backupId);
}
async function getVolumeBackupIdsInOrg(
orgId: string,
accessedServices: string[] | null,
): Promise<string[]> {
const accessCondition = ownerAccessCondition(
[
volumeBackups.applicationId,
volumeBackups.postgresId,
volumeBackups.mariadbId,
volumeBackups.mysqlId,
volumeBackups.mongoId,
volumeBackups.redisId,
volumeBackups.libsqlId,
volumeBackups.composeId,
],
accessedServices,
);
const rows = await db
.select({ volumeBackupId: volumeBackups.volumeBackupId })
.from(volumeBackups)
.innerJoin(
destinations,
eq(volumeBackups.destinationId, destinations.destinationId),
)
.where(
and(
eq(destinations.organizationId, orgId),
...(accessCondition ? [accessCondition] : []),
),
);
return rows.map((r) => r.volumeBackupId);
}
const ownerServiceColumns = {
columns: { name: true, serverId: true },
with: {
environment: {
columns: { environmentId: true },
with: { project: { columns: { projectId: true } } },
},
},
} as const;
// "Backups" here = run history (deployments with backupId/volumeBackupId), not the schedule configs.
export const getAllBackupsForOrganization = async (
orgId: string,
accessedServices: string[] | null,
permissions: { backup: boolean; volumeBackup: boolean },
): Promise<OverviewBackup[]> => {
if (accessedServices !== null && accessedServices.length === 0) {
return [];
}
const [backupIds, volumeBackupIds] = await Promise.all([
permissions.backup
? getBackupIdsInOrg(orgId, accessedServices)
: Promise.resolve([]),
permissions.volumeBackup
? getVolumeBackupIdsInOrg(orgId, accessedServices)
: Promise.resolve([]),
]);
if (backupIds.length === 0 && volumeBackupIds.length === 0) {
return [];
}
const conditions = [
...(backupIds.length > 0 ? [inArray(deployments.backupId, backupIds)] : []),
...(volumeBackupIds.length > 0
? [inArray(deployments.volumeBackupId, volumeBackupIds)]
: []),
];
const whereClause =
conditions.length === 1 ? conditions[0] : or(...conditions);
const rows = await db.query.deployments.findMany({
where: whereClause,
orderBy: desc(deployments.createdAt),
limit: 500,
with: {
backup: {
with: {
destination: { columns: { destinationId: true, name: true } },
postgres: {
columns: { postgresId: true, ...ownerServiceColumns.columns },
with: ownerServiceColumns.with,
},
mariadb: {
columns: { mariadbId: true, ...ownerServiceColumns.columns },
with: ownerServiceColumns.with,
},
mysql: {
columns: { mysqlId: true, ...ownerServiceColumns.columns },
with: ownerServiceColumns.with,
},
mongo: {
columns: { mongoId: true, ...ownerServiceColumns.columns },
with: ownerServiceColumns.with,
},
libsql: {
columns: { libsqlId: true, ...ownerServiceColumns.columns },
with: ownerServiceColumns.with,
},
compose: {
columns: { composeId: true, ...ownerServiceColumns.columns },
with: ownerServiceColumns.with,
},
},
},
volumeBackup: {
with: {
destination: { columns: { destinationId: true, name: true } },
application: {
columns: { applicationId: true, ...ownerServiceColumns.columns },
with: ownerServiceColumns.with,
},
postgres: {
columns: { postgresId: true, ...ownerServiceColumns.columns },
with: ownerServiceColumns.with,
},
mariadb: {
columns: { mariadbId: true, ...ownerServiceColumns.columns },
with: ownerServiceColumns.with,
},
mysql: {
columns: { mysqlId: true, ...ownerServiceColumns.columns },
with: ownerServiceColumns.with,
},
mongo: {
columns: { mongoId: true, ...ownerServiceColumns.columns },
with: ownerServiceColumns.with,
},
redis: {
columns: { redisId: true, ...ownerServiceColumns.columns },
with: ownerServiceColumns.with,
},
libsql: {
columns: { libsqlId: true, ...ownerServiceColumns.columns },
with: ownerServiceColumns.with,
},
compose: {
columns: { composeId: true, ...ownerServiceColumns.columns },
with: ownerServiceColumns.with,
},
},
},
},
});
const result: OverviewBackup[] = [];
for (const deployment of rows) {
if (deployment.backup) {
const owner = pickBackupOwner(deployment.backup);
result.push({
deploymentId: deployment.deploymentId,
kind: "backup",
status: deployment.status,
createdAt: deployment.createdAt,
destinationId: deployment.backup.destination?.destinationId ?? "",
destinationName: deployment.backup.destination?.name ?? "Unknown",
databaseType: deployment.backup.databaseType,
serviceType: null,
backupType: deployment.backup.backupType,
serviceName: owner?.name ?? "Dokploy Server",
serviceOwnerId: owner?.id ?? null,
serviceOwnerType: owner?.type ?? "web-server",
serverId: owner?.serverId ?? null,
projectId: owner?.projectId ?? null,
environmentId: owner?.environmentId ?? null,
});
} else if (deployment.volumeBackup) {
const owner = pickVolumeBackupOwner(deployment.volumeBackup);
result.push({
deploymentId: deployment.deploymentId,
kind: "volumeBackup",
status: deployment.status,
createdAt: deployment.createdAt,
destinationId: deployment.volumeBackup.destination?.destinationId ?? "",
destinationName: deployment.volumeBackup.destination?.name ?? "Unknown",
databaseType: null,
serviceType: deployment.volumeBackup.serviceType,
backupType: null,
serviceName:
owner?.name ?? deployment.volumeBackup.serviceName ?? "Unknown",
serviceOwnerId: owner?.id ?? null,
serviceOwnerType:
owner?.type ??
(deployment.volumeBackup.serviceType as OverviewServiceType),
serverId: owner?.serverId ?? null,
projectId: owner?.projectId ?? null,
environmentId: owner?.environmentId ?? null,
});
}
}
return result;
};
type DomainOwnerRow = {
name: string;
environment?: {
environmentId: string;
name: string;
project?: {
projectId: string;
name: string;
organizationId: string;
} | null;
} | null;
};
type DomainOwner = {
id: string;
name: string;
type: OverviewDomainOwnerType;
organizationId: string | null;
projectId: string | null;
projectName: string | null;
environmentId: string | null;
environmentName: string | null;
};
const domainOwnerColumns = {
columns: { name: true },
with: {
environment: {
columns: { environmentId: true, name: true },
with: {
project: {
columns: { projectId: true, name: true, organizationId: true },
},
},
},
},
} as const;
function domainOwnerFrom(
row: DomainOwnerRow,
id: string,
type: OverviewDomainOwnerType,
): DomainOwner {
return {
id,
name: row.name,
type,
organizationId: row.environment?.project?.organizationId ?? null,
projectId: row.environment?.project?.projectId ?? null,
projectName: row.environment?.project?.name ?? null,
environmentId: row.environment?.environmentId ?? null,
environmentName: row.environment?.name ?? null,
};
}
// Preview domains inherit access from their parent application, so they're scoped by the same accessedServices check as applications.
async function getDomainScopeIdsInOrg(
orgId: string,
accessedServices: string[] | null,
): Promise<{ appIds: string[]; composeIds: string[]; previewIds: string[] }> {
const [appRows, composeRows, previewRows] = await Promise.all([
db
.select({ id: applications.applicationId })
.from(applications)
.innerJoin(
environments,
eq(applications.environmentId, environments.environmentId),
)
.innerJoin(projects, eq(environments.projectId, projects.projectId))
.where(
accessedServices !== null
? and(
eq(projects.organizationId, orgId),
inArray(applications.applicationId, accessedServices),
)
: eq(projects.organizationId, orgId),
),
db
.select({ id: compose.composeId })
.from(compose)
.innerJoin(
environments,
eq(compose.environmentId, environments.environmentId),
)
.innerJoin(projects, eq(environments.projectId, projects.projectId))
.where(
accessedServices !== null
? and(
eq(projects.organizationId, orgId),
inArray(compose.composeId, accessedServices),
)
: eq(projects.organizationId, orgId),
),
db
.select({ id: previewDeployments.previewDeploymentId })
.from(previewDeployments)
.innerJoin(
applications,
eq(previewDeployments.applicationId, applications.applicationId),
)
.innerJoin(
environments,
eq(applications.environmentId, environments.environmentId),
)
.innerJoin(projects, eq(environments.projectId, projects.projectId))
.where(
accessedServices !== null
? and(
eq(projects.organizationId, orgId),
inArray(applications.applicationId, accessedServices),
)
: eq(projects.organizationId, orgId),
),
]);
return {
appIds: appRows.map((r) => r.id),
composeIds: composeRows.map((r) => r.id),
previewIds: previewRows.map((r) => r.id),
};
}
export const getAllDomainsForOrganization = async (
orgId: string,
accessedServices: string[] | null,
): Promise<OverviewDomain[]> => {
if (accessedServices !== null && accessedServices.length === 0) {
return [];
}
const { appIds, composeIds, previewIds } = await getDomainScopeIdsInOrg(
orgId,
accessedServices,
);
if (
appIds.length === 0 &&
composeIds.length === 0 &&
previewIds.length === 0
) {
return [];
}
const scopeConditions = [
...(appIds.length > 0 ? [inArray(domains.applicationId, appIds)] : []),
...(composeIds.length > 0 ? [inArray(domains.composeId, composeIds)] : []),
...(previewIds.length > 0
? [inArray(domains.previewDeploymentId, previewIds)]
: []),
];
const rows = await db.query.domains.findMany({
where: or(...scopeConditions),
with: {
application: {
columns: { applicationId: true, ...domainOwnerColumns.columns },
with: domainOwnerColumns.with,
},
compose: {
columns: { composeId: true, ...domainOwnerColumns.columns },
with: domainOwnerColumns.with,
},
previewDeployment: {
columns: {},
with: {
application: {
columns: { applicationId: true, ...domainOwnerColumns.columns },
with: domainOwnerColumns.with,
},
},
},
},
});
const result: OverviewDomain[] = [];
for (const domain of rows) {
const owner = domain.application
? domainOwnerFrom(
domain.application,
domain.application.applicationId,
"application",
)
: domain.compose
? domainOwnerFrom(domain.compose, domain.compose.composeId, "compose")
: domain.previewDeployment?.application
? domainOwnerFrom(
domain.previewDeployment.application,
domain.previewDeployment.application.applicationId,
"application",
)
: null;
if (!owner || owner.organizationId !== orgId) continue;
if (accessedServices !== null && !accessedServices.includes(owner.id)) {
continue;
}
result.push({
domainId: domain.domainId,
host: domain.host,
path: domain.path,
port: domain.port,
customEntrypoint: domain.customEntrypoint,
https: domain.https,
certificateType: domain.certificateType,
createdAt: domain.createdAt,
enabled: domain.enabled,
domainType: domain.domainType,
serviceOwnerId: owner.id,
serviceOwnerType: owner.type,
serviceName: owner.name,
projectId: owner.projectId ?? "",
projectName: owner.projectName ?? "",
environmentId: owner.environmentId ?? "",
environmentName: owner.environmentName ?? "",
});
}
return result;
};