diff --git a/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/SystemsPageContent.tsx b/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/SystemsPageContent.tsx
new file mode 100644
index 000000000..fbf39d137
--- /dev/null
+++ b/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/SystemsPageContent.tsx
@@ -0,0 +1,89 @@
+"use client";
+
+import type * as SCHEMA from "@ctrlplane/db/schema";
+import { useEffect, useState } from "react";
+import { useRouter, useSearchParams } from "next/navigation";
+
+import { Button } from "@ctrlplane/ui/button";
+import { Input } from "@ctrlplane/ui/input";
+
+import { api } from "~/trpc/react";
+import { CreateDeploymentDialog } from "../_components/deployments/CreateDeployment";
+import { CreateSystemDialog } from "../../(app)/_components/CreateSystem";
+import { SystemDeploymentSkeleton } from "./system-deployment-table/SystemDeploymentSkeleton";
+import { SystemDeploymentTable } from "./system-deployment-table/SystemDeploymentTable";
+
+const useSystemFilter = () => {
+ const router = useRouter();
+ const searchParams = useSearchParams();
+ const filter = searchParams.get("filter");
+
+ const setFilter = (filter: string) => {
+ const url = new URL(window.location.href);
+ const params = new URLSearchParams(url.search);
+
+ if (filter === "") {
+ params.delete("filter");
+ router.replace(`${url.pathname}?${params.toString()}`);
+ return;
+ }
+
+ params.set("filter", filter);
+ router.replace(`${url.pathname}?${params.toString()}`);
+ };
+
+ return { filter, setFilter };
+};
+
+export const SystemsPageContent: React.FC<{
+ workspace: SCHEMA.Workspace;
+}> = ({ workspace }) => {
+ const { filter, setFilter } = useSystemFilter();
+ const [search, setSearch] = useState(filter ?? "");
+
+ useEffect(() => {
+ if (search !== (filter ?? "")) setFilter(search);
+ }, [search, filter, setFilter]);
+
+ const workspaceId = workspace.id;
+ const query = filter ?? undefined;
+ const { data, isLoading } = api.system.list.useQuery(
+ { workspaceId, query },
+ { placeholderData: (prev) => prev },
+ );
+
+ const systems = data?.items ?? [];
+
+ return (
+
+
+
Systems
+
+
+
+
+
+
+
+
+
+
+
setSearch(e.target.value)}
+ placeholder="Search systems and deployments..."
+ className="w-80"
+ />
+ {isLoading &&
+ Array.from({ length: 2 }).map((_, i) => (
+
+
+
+ ))}
+ {!isLoading &&
+ systems.map((s) => (
+
+ ))}
+
+ );
+};
diff --git a/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/[systemSlug]/(sidebar)/deployments/TableDeployments.tsx b/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/[systemSlug]/(sidebar)/deployments/TableDeployments.tsx
index 8e00e9bb3..284ea0e12 100644
--- a/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/[systemSlug]/(sidebar)/deployments/TableDeployments.tsx
+++ b/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/[systemSlug]/(sidebar)/deployments/TableDeployments.tsx
@@ -145,6 +145,7 @@ const DeploymentTable: React.FC<{
environment={env}
deployment={r}
workspace={workspace}
+ systemSlug={systemSlug}
/>
);
diff --git a/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/[systemSlug]/_components/deployments/environment-cell/DeploymentEnvironmentCell.tsx b/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/[systemSlug]/_components/deployments/environment-cell/DeploymentEnvironmentCell.tsx
index 1b68493d7..fa305194b 100644
--- a/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/[systemSlug]/_components/deployments/environment-cell/DeploymentEnvironmentCell.tsx
+++ b/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/[systemSlug]/_components/deployments/environment-cell/DeploymentEnvironmentCell.tsx
@@ -1,6 +1,6 @@
"use client";
-import type { RouterOutputs } from "@ctrlplane/api";
+import type * as SCHEMA from "@ctrlplane/db/schema";
import Link from "next/link";
import { useRouter } from "next/navigation";
import {
@@ -18,19 +18,18 @@ import { ApprovalDialog } from "../../release/ApprovalDialog";
import { ReleaseDropdownMenu } from "../../release/ReleaseDropdownMenu";
import { Release } from "./ReleaseInfo";
-type Environment = RouterOutputs["environment"]["bySystemId"][number];
-type Deployment = RouterOutputs["deployment"]["bySystemId"][number];
-
type DeploymentEnvironmentCellProps = {
- environment: Environment;
- deployment: Deployment;
- workspace: { id: string; slug: string };
+ environment: SCHEMA.Environment;
+ deployment: SCHEMA.Deployment;
+ workspace: SCHEMA.Workspace;
+ systemSlug: string;
};
const DeploymentEnvironmentCell: React.FC = ({
environment,
deployment,
workspace,
+ systemSlug,
}) => {
const { data: release, isLoading: isReleaseLoading } =
api.release.latest.byDeploymentAndEnvironment.useQuery({
@@ -68,7 +67,7 @@ const DeploymentEnvironmentCell: React.FC = ({
if (release.resourceCount === 0)
return (
= ({
;
@@ -14,27 +10,5 @@ export default async function SystemsPage(props: {
const workspace = await api.workspace.bySlug(params.workspaceSlug);
if (workspace == null) notFound();
- const systems = await api.system.list({
- workspaceId: workspace.id,
- });
-
- return (
-
-
-
Systems
-
-
-
-
-
-
-
-
-
-
- {systems.items.map((s) => (
-
- ))}
-
- );
+ return ;
}
diff --git a/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/system-deployment-table/SystemDeploymentSkeleton.tsx b/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/system-deployment-table/SystemDeploymentSkeleton.tsx
index fc79ee8b4..c8426e017 100644
--- a/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/system-deployment-table/SystemDeploymentSkeleton.tsx
+++ b/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/system-deployment-table/SystemDeploymentSkeleton.tsx
@@ -9,7 +9,7 @@ import {
} from "@ctrlplane/ui/table";
export const SystemDeploymentSkeleton: React.FC = () => (
-
+
diff --git a/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/system-deployment-table/SystemDeploymentTable.tsx b/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/system-deployment-table/SystemDeploymentTable.tsx
index b0f13db8f..e10518ff1 100644
--- a/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/system-deployment-table/SystemDeploymentTable.tsx
+++ b/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/system-deployment-table/SystemDeploymentTable.tsx
@@ -1,9 +1,8 @@
"use client";
-import type { System, Workspace } from "@ctrlplane/db/schema";
+import type * as SCHEMA from "@ctrlplane/db/schema";
import Link from "next/link";
import { IconDots, IconShip, IconTrash } from "@tabler/icons-react";
-import { useInView } from "react-intersection-observer";
import { Button } from "@ctrlplane/ui/button";
import {
@@ -15,24 +14,17 @@ import {
import { CreateDeploymentDialog } from "~/app/[workspaceSlug]/(appv2)/_components/deployments/CreateDeployment";
import { DeleteSystemDialog } from "~/app/[workspaceSlug]/(appv2)/_components/system/DeleteSystemDialog";
-import { api } from "~/trpc/react";
-import { SystemDeploymentSkeleton } from "./SystemDeploymentSkeleton";
import DeploymentTable from "./TableDeployments";
+type System = SCHEMA.System & {
+ deployments: SCHEMA.Deployment[];
+ environments: SCHEMA.Environment[];
+};
+
export const SystemDeploymentTable: React.FC<{
- workspace: Workspace;
+ workspace: SCHEMA.Workspace;
system: System;
}> = ({ workspace, system }) => {
- const { ref, inView } = useInView();
- const environments = api.environment.bySystemId.useQuery(system.id, {
- enabled: inView,
- });
- const deployments = api.deployment.bySystemId.useQuery(system.id, {
- enabled: inView,
- });
-
- const isLoading = environments.isLoading || deployments.isLoading;
-
return (
@@ -73,21 +65,14 @@ export const SystemDeploymentTable: React.FC<{
- {isLoading && (
-
-
-
- )}
- {!isLoading && (
-
-
-
- )}
+
+
+
);
};
diff --git a/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/system-deployment-table/TableDeployments.tsx b/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/system-deployment-table/TableDeployments.tsx
index d6906e250..8fa3b6d71 100644
--- a/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/system-deployment-table/TableDeployments.tsx
+++ b/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/system-deployment-table/TableDeployments.tsx
@@ -1,7 +1,6 @@
"use client";
-import type { RouterOutputs } from "@ctrlplane/api";
-import type { Workspace } from "@ctrlplane/db/schema";
+import type * as SCHEMA from "@ctrlplane/db/schema";
import Link from "next/link";
import { IconLoader2 } from "@tabler/icons-react";
@@ -20,11 +19,8 @@ import { LazyDeploymentEnvironmentCell } from "~/app/[workspaceSlug]/(appv2)/sys
import { urls } from "~/app/urls";
import { api } from "~/trpc/react";
-type Environment = RouterOutputs["environment"]["bySystemId"][number];
-type Deployment = RouterOutputs["deployment"]["bySystemId"][number];
-
const EnvHeader: React.FC<{
- environment: Environment;
+ environment: SCHEMA.Environment;
workspaceSlug: string;
}> = ({ environment: env, workspaceSlug }) => {
const { data: workspace, isLoading: isWorkspaceLoading } =
@@ -62,10 +58,10 @@ const EnvHeader: React.FC<{
};
const DeploymentTable: React.FC<{
- workspace: Workspace;
+ workspace: SCHEMA.Workspace;
systemSlug: string;
- environments: Environment[];
- deployments: Deployment[];
+ environments: SCHEMA.Environment[];
+ deployments: SCHEMA.Deployment[];
}> = ({ systemSlug, deployments, environments, workspace }) => {
return (
@@ -118,6 +114,7 @@ const DeploymentTable: React.FC<{
environment={env}
deployment={r}
workspace={workspace}
+ systemSlug={systemSlug}
/>
diff --git a/packages/api/src/router/system.ts b/packages/api/src/router/system.ts
index 9b8cfdae6..b3d02ba50 100644
--- a/packages/api/src/router/system.ts
+++ b/packages/api/src/router/system.ts
@@ -9,14 +9,16 @@ import {
count,
createEnv,
eq,
+ ilike,
isNotNull,
isNull,
- like,
+ or,
takeFirst,
takeFirstOrNull,
} from "@ctrlplane/db";
import {
createSystem,
+ deployment,
environment,
resource,
resourceMatchesMetadata,
@@ -51,15 +53,21 @@ export const systemRouter = createTRPCRouter({
.query(({ ctx, input }) => {
const workspaceIdCheck = eq(system.workspaceId, input.workspaceId);
- const checks = and(
- workspaceIdCheck,
- input.query ? like(system.name, `%${input.query}%`) : undefined,
- );
+ const query = input.query
+ ? or(
+ ilike(system.name, `%${input.query}%`),
+ ilike(system.slug, `%${input.query}%`),
+ ilike(deployment.name, `%${input.query}%`),
+ ilike(deployment.slug, `%${input.query}%`),
+ )
+ : undefined;
+ const checks = and(workspaceIdCheck, query);
const items = ctx.db
.select()
.from(system)
.leftJoin(environment, eq(environment.systemId, system.id))
+ .leftJoin(deployment, eq(deployment.systemId, system.id))
.where(checks)
.limit(input.limit)
.offset(input.offset)
@@ -69,7 +77,16 @@ export const systemRouter = createTRPCRouter({
.groupBy((r) => r.system.id)
.map((r) => ({
...r[0]!.system,
- environments: r.map((r) => r.environment).filter(isPresent),
+ environments: _.chain(r)
+ .map((r) => r.environment)
+ .filter(isPresent)
+ .uniqBy((e) => e.id)
+ .value(),
+ deployments: _.chain(r)
+ .map((r) => r.deployment)
+ .filter(isPresent)
+ .uniqBy((d) => d.id)
+ .value(),
}))
.value(),
);
@@ -77,7 +94,7 @@ export const systemRouter = createTRPCRouter({
const total = ctx.db
.select({ count: count().as("total") })
.from(system)
- .where(checks)
+ .where(eq(system.workspaceId, input.workspaceId))
.then(takeFirst)
.then((total) => total.count);