Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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 (
<div className="container m-8 mx-auto space-y-8">
<div className="flex w-full items-center justify-between">
<h2 className="text-2xl font-bold">Systems</h2>
<div className="flex items-center gap-2">
<CreateSystemDialog workspace={workspace}>
<Button variant="outline">New System</Button>
</CreateSystemDialog>
<CreateDeploymentDialog>
<Button variant="outline">New Deployment</Button>
</CreateDeploymentDialog>
</div>
</div>

<Input
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder="Search systems and deployments..."
className="w-80"
/>
{isLoading &&
Array.from({ length: 2 }).map((_, i) => (
<div key={i} className="rounded-md border">
<SystemDeploymentSkeleton />
</div>
))}
{!isLoading &&
systems.map((s) => (
<SystemDeploymentTable key={s.id} workspace={workspace} system={s} />
))}
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ const DeploymentTable: React.FC<{
environment={env}
deployment={r}
workspace={workspace}
systemSlug={systemSlug}
/>
</td>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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<DeploymentEnvironmentCellProps> = ({
environment,
deployment,
workspace,
systemSlug,
}) => {
const { data: release, isLoading: isReleaseLoading } =
api.release.latest.byDeploymentAndEnvironment.useQuery({
Expand Down Expand Up @@ -68,7 +67,7 @@ const DeploymentEnvironmentCell: React.FC<DeploymentEnvironmentCellProps> = ({
if (release.resourceCount === 0)
return (
<Link
href={`/${workspace.slug}/systems/${deployment.system.slug}/environments/${environment.id}/resources`}
href={`/${workspace.slug}/systems/${systemSlug}/environments/${environment.id}/resources`}
className="flex w-full cursor-pointer items-center justify-between gap-2 rounded-md p-2 hover:bg-secondary/50"
target="_blank"
rel="noopener noreferrer"
Expand Down Expand Up @@ -101,7 +100,7 @@ const DeploymentEnvironmentCell: React.FC<DeploymentEnvironmentCellProps> = ({
<div className="flex w-full items-center justify-center rounded-md p-2 hover:bg-secondary/50">
<Release
workspaceSlug={workspace.slug}
systemSlug={deployment.system.slug}
systemSlug={systemSlug}
deploymentSlug={deployment.slug}
releaseId={release.id}
version={release.version}
Expand Down
30 changes: 2 additions & 28 deletions apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/page.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import { notFound } from "next/navigation";

import { Button } from "@ctrlplane/ui/button";

import { api } from "~/trpc/server";
import { CreateDeploymentDialog } from "../_components/deployments/CreateDeployment";
import { CreateSystemDialog } from "../../(app)/_components/CreateSystem";
import { SystemDeploymentTable } from "./system-deployment-table/SystemDeploymentTable";
import { SystemsPageContent } from "./SystemsPageContent";

export default async function SystemsPage(props: {
params: Promise<{ workspaceSlug: string }>;
Expand All @@ -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 (
<div className="container m-8 mx-auto space-y-8">
<div className="flex w-full items-center justify-between">
<h2 className="text-2xl font-bold">Systems</h2>
<div className="flex items-center gap-2">
<CreateSystemDialog workspace={workspace}>
<Button variant="outline">New System</Button>
</CreateSystemDialog>
<CreateDeploymentDialog>
<Button variant="outline">New Deployment</Button>
</CreateDeploymentDialog>
</div>
</div>

{systems.items.map((s) => (
<SystemDeploymentTable key={s.id} workspace={workspace} system={s} />
))}
</div>
);
return <SystemsPageContent workspace={workspace} />;
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
} from "@ctrlplane/ui/table";

export const SystemDeploymentSkeleton: React.FC = () => (
<Table className="w-full min-w-max bg-background">
<Table className="w-full min-w-max rounded-md bg-background">
<TableHeader className="[&_tr]:border-0">
<TableRow className="hover:bg-transparent">
<TableHead className="w-[350px] rounded-tl-md py-4 pl-6">
Expand Down
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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 (
<div key={system.id} className="space-y-4">
<div className="flex w-full items-center justify-between">
Expand Down Expand Up @@ -73,21 +65,14 @@ export const SystemDeploymentTable: React.FC<{
</DropdownMenu>
</div>

{isLoading && (
<div className="rounded-md border">
<SystemDeploymentSkeleton />
</div>
)}
{!isLoading && (
<div ref={ref} className="overflow-hidden rounded-md border">
<DeploymentTable
workspace={workspace}
systemSlug={system.slug}
environments={environments.data ?? []}
deployments={deployments.data ?? []}
/>
</div>
)}
<div className="overflow-hidden rounded-md border">
<DeploymentTable
workspace={workspace}
systemSlug={system.slug}
environments={system.environments}
deployments={system.deployments}
/>
</div>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -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";

Expand All @@ -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 } =
Expand Down Expand Up @@ -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 (
<div className="scrollbar-thin scrollbar-thumb-neutral-700 scrollbar-track-neutral-800 w-full overflow-x-auto">
Expand Down Expand Up @@ -118,6 +114,7 @@ const DeploymentTable: React.FC<{
environment={env}
deployment={r}
workspace={workspace}
systemSlug={systemSlug}
/>
</div>
</TableCell>
Expand Down
31 changes: 24 additions & 7 deletions packages/api/src/router/system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,16 @@ import {
count,
createEnv,
eq,
ilike,
isNotNull,
isNull,
like,
or,
takeFirst,
takeFirstOrNull,
} from "@ctrlplane/db";
import {
createSystem,
deployment,
environment,
resource,
resourceMatchesMetadata,
Expand Down Expand Up @@ -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)
Expand All @@ -69,15 +77,24 @@ 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(),
);

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);

Expand Down
Loading