diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/(job)/jobs/JobTable.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/(job)/jobs/JobTable.tsx index deb583e27..bf710a515 100644 --- a/apps/webservice/src/app/[workspaceSlug]/(app)/(job)/jobs/JobTable.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/(app)/(job)/jobs/JobTable.tsx @@ -27,7 +27,7 @@ import { VariableCell } from "~/app/[workspaceSlug]/(app)/_components/job-table/ import { JobTableStatusIcon } from "~/app/[workspaceSlug]/(app)/_components/JobTableStatusIcon"; import { useFilter } from "~/app/[workspaceSlug]/(app)/_components/useFilter"; import { api } from "~/trpc/react"; -import { nFormatter } from "../../systems/[systemSlug]/_components/nFormatter"; +import { nFormatter } from "../../systems-old/[systemSlug]/_components/nFormatter"; type JobTableProps = { workspaceId: string; diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/(resources)/resources/ResourcePageContent.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/(resources)/resources/ResourcePageContent.tsx index 41899d4be..7bebd99b2 100644 --- a/apps/webservice/src/app/[workspaceSlug]/(app)/(resources)/resources/ResourcePageContent.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/(app)/(resources)/resources/ResourcePageContent.tsx @@ -131,7 +131,7 @@ export const ResourcePageContent: React.FC<{ return ; return ( -
+
@@ -224,7 +224,7 @@ export const ResourcePageContent: React.FC<{ /> )} {resources.data != null && resources.data.total > 0 && ( -
+
; - searchParams: Promise<{ view?: string }>; - } -) { - const searchParams = await props.searchParams; - const params = await props.params; - const workspace = await api.workspace.bySlug(params.workspaceSlug); - if (workspace == null) notFound(); - - const view = - searchParams.view != null - ? await api.resource.view.byId(searchParams.view) - : null; - - return ; -} diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/_components/MetadataInfo.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/MetadataInfo.tsx index a8fe32080..95cc31ca0 100644 --- a/apps/webservice/src/app/[workspaceSlug]/(app)/_components/MetadataInfo.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/MetadataInfo.tsx @@ -3,7 +3,7 @@ import { IconSparkles } from "@tabler/icons-react"; import { Input } from "@ctrlplane/ui/input"; import { ReservedMetadataKey } from "@ctrlplane/validators/conditions"; -import { useMatchSorterWithSearch } from "../systems/[systemSlug]/environments/useMatchSorter"; +import { useMatchSorterWithSearch } from "../systems-old/[systemSlug]/environments/useMatchSorter"; export const MetadataInfo: React.FC<{ metadata: Record }> = ( props, diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/_components/deployment-resource-drawer/TableRow.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/deployment-resource-drawer/TableRow.tsx index 97d520270..0d72288e1 100644 --- a/apps/webservice/src/app/[workspaceSlug]/(app)/_components/deployment-resource-drawer/TableRow.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/deployment-resource-drawer/TableRow.tsx @@ -23,8 +23,8 @@ import { import { JobFilterType, JobStatusReadable } from "@ctrlplane/validators/jobs"; import { api } from "~/trpc/react"; -import { JobDropdownMenu } from "../../systems/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/JobDropdownMenu"; -import { DeployButton } from "../../systems/[systemSlug]/deployments/DeployButton"; +import { JobDropdownMenu } from "../../systems-old/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/JobDropdownMenu"; +import { DeployButton } from "../../systems-old/[systemSlug]/deployments/DeployButton"; import { JobLinksCell } from "../job-table/JobLinksCell"; import { JobTableStatusIcon } from "../JobTableStatusIcon"; diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/_components/job-drawer/JobDrawer.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/job-drawer/JobDrawer.tsx index 9de37c364..099359a80 100644 --- a/apps/webservice/src/app/[workspaceSlug]/(app)/_components/job-drawer/JobDrawer.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/job-drawer/JobDrawer.tsx @@ -13,8 +13,8 @@ import { Button, buttonVariants } from "@ctrlplane/ui/button"; import { Drawer, DrawerContent, DrawerTitle } from "@ctrlplane/ui/drawer"; import { ReservedMetadataKey } from "@ctrlplane/validators/conditions"; -import { JobDropdownMenu } from "~/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/JobDropdownMenu"; -import { useReleaseChannel } from "~/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/useReleaseChannel"; +import { JobDropdownMenu } from "~/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/JobDropdownMenu"; +import { useReleaseChannel } from "~/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/useReleaseChannel"; import { api } from "~/trpc/react"; import { MetadataInfo } from "../MetadataInfo"; import { JobAgent } from "./JobAgent"; diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/_components/resource-condition/useResourceFilter.ts b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/resource-condition/useResourceFilter.ts index 4ee59453a..b62ee7dc4 100644 --- a/apps/webservice/src/app/[workspaceSlug]/(app)/_components/resource-condition/useResourceFilter.ts +++ b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/resource-condition/useResourceFilter.ts @@ -1,10 +1,14 @@ import type { ResourceCondition } from "@ctrlplane/validators/resources"; -import { useCallback, useMemo } from "react"; +import React, { useCallback, useMemo } from "react"; import LZString from "lz-string"; +import { useDebounce } from "react-use"; + +import { ColumnOperator } from "@ctrlplane/validators/conditions"; import { useQueryParams } from "../useQueryParams"; export const useResourceFilter = () => { + const [search, setSearch] = React.useState(""); const { getParam, setParams } = useQueryParams(); const filterHash = getParam("filter"); @@ -34,5 +38,30 @@ export const useResourceFilter = () => { [setParams], ); - return { filter, setFilter, viewId }; + useDebounce( + () => { + if (search === "") return; + setFilter({ + type: "comparison", + operator: "and", + conditions: [ + // Keep any non-name conditions from existing filter + ...(filter && "conditions" in filter + ? filter.conditions.filter( + (c: ResourceCondition) => c.type !== "name", + ) + : []), + { + type: "name", + operator: ColumnOperator.Contains, + value: search, + }, + ], + }); + }, + 500, + [search], + ); + + return { filter, setFilter, viewId, search, setSearch }; }; diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/dashboard/page.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/dashboard/page.tsx index 45b1ee574..66ed756da 100644 --- a/apps/webservice/src/app/[workspaceSlug]/(app)/dashboard/page.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/(app)/dashboard/page.tsx @@ -4,7 +4,7 @@ import { notFound } from "next/navigation"; import { Card } from "@ctrlplane/ui/card"; import { api } from "../../../../trpc/server"; -import { JobHistoryChart } from "../systems/JobHistoryChart"; +import { JobHistoryChart } from "../systems-old/JobHistoryChart"; import { ResourceAnnotationPieChart } from "./ResourceAnnotationPieChart"; type PageProps = { @@ -18,11 +18,9 @@ export async function generateMetadata(props: PageProps): Promise { }; } -export default async function Dashboard( - props: { - params: Promise<{ workspaceSlug: string }>; - } -) { +export default async function Dashboard(props: { + params: Promise<{ workspaceSlug: string }>; +}) { const params = await props.params; const { workspaceSlug } = params; const workspace = await api.workspace.bySlug(workspaceSlug).catch(() => null); diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/page.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/page.tsx index a973a2003..5cb2f488b 100644 --- a/apps/webservice/src/app/[workspaceSlug]/(app)/page.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/(app)/page.tsx @@ -1,10 +1,8 @@ import { redirect } from "next/navigation"; -export default async function WorkspacePage( - props: { - params: Promise<{ workspaceSlug: string }>; - } -) { +export default async function WorkspacePage(props: { + params: Promise<{ workspaceSlug: string }>; +}) { const params = await props.params; redirect(`/${params.workspaceSlug}/systems`); } diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/JobHistoryChart.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/JobHistoryChart.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/JobHistoryChart.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/JobHistoryChart.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/SystemActionsDropdown.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/SystemActionsDropdown.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/SystemActionsDropdown.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/SystemActionsDropdown.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/SystemGettingStarted.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/SystemGettingStarted.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/SystemGettingStarted.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/SystemGettingStarted.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/SystemsBreadcrumb.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/SystemsBreadcrumb.tsx similarity index 98% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/SystemsBreadcrumb.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/SystemsBreadcrumb.tsx index 9e721a954..61c729fd7 100644 --- a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/SystemsBreadcrumb.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/SystemsBreadcrumb.tsx @@ -18,7 +18,7 @@ import { } from "@ctrlplane/ui/breadcrumb"; import { Button } from "@ctrlplane/ui/button"; -import { SystemActionsDropdown } from "~/app/[workspaceSlug]/(app)/systems/SystemActionsDropdown"; +import { SystemActionsDropdown } from "~/app/[workspaceSlug]/(app)/systems-old/SystemActionsDropdown"; import { api } from "~/trpc/server"; export const SystemBreadcrumbNavbar = async ({ diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/SystemsList.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/SystemsList.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/SystemsList.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/SystemsList.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/SystemsTable.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/SystemsTable.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/SystemsTable.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/SystemsTable.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/TopNav.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/TopNav.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/TopNav.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/TopNav.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/_components/DeleteSystemDialog.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/_components/DeleteSystemDialog.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/_components/DeleteSystemDialog.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/_components/DeleteSystemDialog.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/_components/EditSystemDialog.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/_components/EditSystemDialog.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/_components/EditSystemDialog.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/_components/EditSystemDialog.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/_components/nFormatter.ts b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/_components/nFormatter.ts similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/_components/nFormatter.ts rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/_components/nFormatter.ts diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/_components/variables/ConfigFields.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/_components/variables/ConfigFields.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/_components/variables/ConfigFields.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/_components/variables/ConfigFields.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/_components/variables/VariableDeploymentInput.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/_components/variables/VariableDeploymentInput.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/_components/variables/VariableDeploymentInput.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/_components/variables/VariableDeploymentInput.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/_components/variables/VariableEnvironmentInput.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/_components/variables/VariableEnvironmentInput.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/_components/variables/VariableEnvironmentInput.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/_components/variables/VariableEnvironmentInput.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/_components/variables/VariableInputs.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/_components/variables/VariableInputs.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/_components/variables/VariableInputs.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/_components/variables/VariableInputs.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/_components/variables/VariableResourceInput.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/_components/variables/VariableResourceInput.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/_components/variables/VariableResourceInput.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/_components/variables/VariableResourceInput.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/DeployButton.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/DeployButton.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/DeployButton.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/DeployButton.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/DeploymentBarChart.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/DeploymentBarChart.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/DeploymentBarChart.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/DeploymentBarChart.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/DeploymentGettingStarted.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/DeploymentGettingStarted.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/DeploymentGettingStarted.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/DeploymentGettingStarted.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/ReleaseDropdownMenu.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/ReleaseDropdownMenu.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/ReleaseDropdownMenu.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/ReleaseDropdownMenu.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/ReleaseEnvironmentCell.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/ReleaseEnvironmentCell.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/ReleaseEnvironmentCell.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/ReleaseEnvironmentCell.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/TableCells.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/TableCells.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/TableCells.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/TableCells.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/TableDeployments.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/TableDeployments.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/TableDeployments.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/TableDeployments.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/DeploymentNavBar.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/DeploymentNavBar.tsx similarity index 98% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/DeploymentNavBar.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/DeploymentNavBar.tsx index f4de6e7b5..453ce7042 100644 --- a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/DeploymentNavBar.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/DeploymentNavBar.tsx @@ -11,7 +11,7 @@ import { NavigationMenuList, } from "@ctrlplane/ui/navigation-menu"; -import { nFormatter } from "~/app/[workspaceSlug]/(app)/systems/[systemSlug]/_components/nFormatter"; +import { nFormatter } from "~/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/_components/nFormatter"; import { NavigationMenuAction } from "./NavigationMenuAction"; type DeploymentNavBarProps = { diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/DeploymentResourcesDialog.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/DeploymentResourcesDialog.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/DeploymentResourcesDialog.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/DeploymentResourcesDialog.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/EditDeploymentSection.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/EditDeploymentSection.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/EditDeploymentSection.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/EditDeploymentSection.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/JobAgentSection.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/JobAgentSection.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/JobAgentSection.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/JobAgentSection.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/NavigationMenuAction.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/NavigationMenuAction.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/NavigationMenuAction.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/NavigationMenuAction.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/NavigationMenuTab.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/NavigationMenuTab.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/NavigationMenuTab.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/NavigationMenuTab.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/SettingsSidebar.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/SettingsSidebar.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/SettingsSidebar.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/SettingsSidebar.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/configure/job-agent/JobAgentConfigForm.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/configure/job-agent/JobAgentConfigForm.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/configure/job-agent/JobAgentConfigForm.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/configure/job-agent/JobAgentConfigForm.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/configure/job-agent/page.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/configure/job-agent/page.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/configure/job-agent/page.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/configure/job-agent/page.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/hooks/CreateHookDialog.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/hooks/CreateHookDialog.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/hooks/CreateHookDialog.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/hooks/CreateHookDialog.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/hooks/DeleteHookDialog.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/hooks/DeleteHookDialog.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/hooks/DeleteHookDialog.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/hooks/DeleteHookDialog.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/hooks/EditHookDialog.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/hooks/EditHookDialog.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/hooks/EditHookDialog.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/hooks/EditHookDialog.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/hooks/HookActionsDropdown.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/hooks/HookActionsDropdown.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/hooks/HookActionsDropdown.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/hooks/HookActionsDropdown.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/hooks/HooksTable.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/hooks/HooksTable.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/hooks/HooksTable.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/hooks/HooksTable.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/hooks/page.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/hooks/page.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/hooks/page.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/hooks/page.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/layout.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/layout.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/layout.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/layout.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/page.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/page.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/page.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/page.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/release-channels/CreateReleaseChannelDialog.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/release-channels/CreateReleaseChannelDialog.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/release-channels/CreateReleaseChannelDialog.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/release-channels/CreateReleaseChannelDialog.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/release-channels/ReleaseChannelsTable.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/release-channels/ReleaseChannelsTable.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/release-channels/ReleaseChannelsTable.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/release-channels/ReleaseChannelsTable.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/release-channels/page.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/release-channels/page.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/release-channels/page.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/release-channels/page.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/CreateVariableDialog.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/releases/CreateVariableDialog.tsx similarity index 98% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/CreateVariableDialog.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/releases/CreateVariableDialog.tsx index 5e40557b4..a4b109174 100644 --- a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/CreateVariableDialog.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/releases/CreateVariableDialog.tsx @@ -34,7 +34,7 @@ import { ConfigTypeSelector, NumberConfigFields, StringConfigFields, -} from "~/app/[workspaceSlug]/(app)/systems/[systemSlug]/_components/variables/ConfigFields"; +} from "~/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/_components/variables/ConfigFields"; import { api } from "~/trpc/react"; const schema = z.object({ diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/DeploymentPageContent.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/releases/DeploymentPageContent.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/DeploymentPageContent.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/releases/DeploymentPageContent.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/JobHistoryPopover.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/releases/JobHistoryPopover.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/JobHistoryPopover.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/releases/JobHistoryPopover.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/ReleaseDistributionBarChart.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/releases/ReleaseDistributionBarChart.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/ReleaseDistributionBarChart.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/releases/ReleaseDistributionBarChart.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/ReleaseDistributionPopover.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/releases/ReleaseDistributionPopover.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/ReleaseDistributionPopover.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/releases/ReleaseDistributionPopover.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/ApprovalCheck.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/ApprovalCheck.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/ApprovalCheck.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/ApprovalCheck.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/EnvironmentApprovalRow.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/EnvironmentApprovalRow.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/EnvironmentApprovalRow.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/EnvironmentApprovalRow.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/EnvironmentNode.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/EnvironmentNode.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/EnvironmentNode.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/EnvironmentNode.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/FlowDiagram.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/FlowDiagram.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/FlowDiagram.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/FlowDiagram.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/FlowNode.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/FlowNode.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/FlowNode.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/FlowNode.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/FlowPolicyNode.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/FlowPolicyNode.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/FlowPolicyNode.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/FlowPolicyNode.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/JobDropdownMenu.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/JobDropdownMenu.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/JobDropdownMenu.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/JobDropdownMenu.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/JobsTable.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/JobsTable.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/JobsTable.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/JobsTable.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/ResourceReleaseTable.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/ResourceReleaseTable.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/ResourceReleaseTable.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/ResourceReleaseTable.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/StatusIcons.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/StatusIcons.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/StatusIcons.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/StatusIcons.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/TriggerNode.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/TriggerNode.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/TriggerNode.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/TriggerNode.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/page.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/page.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/page.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/page.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/useReleaseChannel.ts b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/useReleaseChannel.ts similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/useReleaseChannel.ts rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/useReleaseChannel.ts diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/page.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/releases/page.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/page.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/releases/page.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/resources/[resourceId]/page.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/resources/[resourceId]/page.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/resources/[resourceId]/page.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/resources/[resourceId]/page.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/resources/page.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/resources/page.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/resources/page.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/resources/page.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/variables/AddVariableValueDialog.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/variables/AddVariableValueDialog.tsx similarity index 98% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/variables/AddVariableValueDialog.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/variables/AddVariableValueDialog.tsx index 1a8d8648d..b9373b7bd 100644 --- a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/variables/AddVariableValueDialog.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/variables/AddVariableValueDialog.tsx @@ -36,7 +36,7 @@ import { VariableBooleanInput, VariableChoiceSelect, VariableStringInput, -} from "~/app/[workspaceSlug]/(app)/systems/[systemSlug]/_components/variables/VariableInputs"; +} from "~/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/_components/variables/VariableInputs"; import { api } from "~/trpc/react"; const schema = z.object({ diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/variables/EditVariableDialog.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/variables/EditVariableDialog.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/variables/EditVariableDialog.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/variables/EditVariableDialog.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/variables/VariableDropdown.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/variables/VariableDropdown.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/variables/VariableDropdown.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/variables/VariableDropdown.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/variables/VariableTable.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/variables/VariableTable.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/variables/VariableTable.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/variables/VariableTable.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/variables/VariableValueDropdown.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/variables/VariableValueDropdown.tsx similarity index 98% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/variables/VariableValueDropdown.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/variables/VariableValueDropdown.tsx index a24525569..4b61e7442 100644 --- a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/variables/VariableValueDropdown.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/variables/VariableValueDropdown.tsx @@ -53,7 +53,7 @@ import { VariableBooleanInput, VariableChoiceSelect, VariableStringInput, -} from "~/app/[workspaceSlug]/(app)/systems/[systemSlug]/_components/variables/VariableInputs"; +} from "~/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/_components/variables/VariableInputs"; import { api } from "~/trpc/react"; const editVariableValueFormSchema = z.object({ diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/variables/page.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/variables/page.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/variables/page.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/variables/page.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/variables/variable-data.ts b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/variables/variable-data.ts similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/variables/variable-data.ts rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/[deploymentSlug]/variables/variable-data.ts diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/page.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/page.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/page.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/page.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/status-color.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/status-color.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/status-color.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/deployments/status-color.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/environments/DeleteNodeDialog.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/environments/DeleteNodeDialog.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/environments/DeleteNodeDialog.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/environments/DeleteNodeDialog.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/environments/EnvFlowBuilder.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/environments/EnvFlowBuilder.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/environments/EnvFlowBuilder.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/environments/EnvFlowBuilder.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/environments/EnvFlowPanel.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/environments/EnvFlowPanel.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/environments/EnvFlowPanel.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/environments/EnvFlowPanel.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/environments/FlowNodeTypes.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/environments/FlowNodeTypes.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/environments/FlowNodeTypes.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/environments/FlowNodeTypes.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/environments/SidepanelContext.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/environments/SidepanelContext.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/environments/SidepanelContext.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/environments/SidepanelContext.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/environments/TargetFilterUniquenessIndicator.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/environments/TargetFilterUniquenessIndicator.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/environments/TargetFilterUniquenessIndicator.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/environments/TargetFilterUniquenessIndicator.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/environments/[environmentId]/ResourceCharts.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/environments/[environmentId]/ResourceCharts.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/environments/[environmentId]/ResourceCharts.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/environments/[environmentId]/ResourceCharts.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/environments/[environmentId]/page.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/environments/[environmentId]/page.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/environments/[environmentId]/page.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/environments/[environmentId]/page.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/environments/edges.ts b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/environments/edges.ts similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/environments/edges.ts rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/environments/edges.ts diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/environments/layout.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/environments/layout.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/environments/layout.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/environments/layout.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/environments/page.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/environments/page.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/environments/page.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/environments/page.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/environments/useMatchSorter.ts b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/environments/useMatchSorter.ts similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/environments/useMatchSorter.ts rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/environments/useMatchSorter.ts diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/page.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/page.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/page.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/page.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/runbooks/DeleteRunbookDialog.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/runbooks/DeleteRunbookDialog.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/runbooks/DeleteRunbookDialog.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/runbooks/DeleteRunbookDialog.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/runbooks/EditRunbookDialog.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/runbooks/EditRunbookDialog.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/runbooks/EditRunbookDialog.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/runbooks/EditRunbookDialog.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/runbooks/EditRunbookForm.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/runbooks/EditRunbookForm.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/runbooks/EditRunbookForm.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/runbooks/EditRunbookForm.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/runbooks/RunbookGettingStarted.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/runbooks/RunbookGettingStarted.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/runbooks/RunbookGettingStarted.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/runbooks/RunbookGettingStarted.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/runbooks/RunbookRow.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/runbooks/RunbookRow.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/runbooks/RunbookRow.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/runbooks/RunbookRow.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/runbooks/TriggerRunbook.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/runbooks/TriggerRunbook.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/runbooks/TriggerRunbook.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/runbooks/TriggerRunbook.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/runbooks/[runbookId]/RunbookNavBar.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/runbooks/[runbookId]/RunbookNavBar.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/runbooks/[runbookId]/RunbookNavBar.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/runbooks/[runbookId]/RunbookNavBar.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/runbooks/[runbookId]/layout.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/runbooks/[runbookId]/layout.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/runbooks/[runbookId]/layout.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/runbooks/[runbookId]/layout.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/runbooks/[runbookId]/page.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/runbooks/[runbookId]/page.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/runbooks/[runbookId]/page.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/runbooks/[runbookId]/page.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/runbooks/[runbookId]/settings/EditRunbook.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/runbooks/[runbookId]/settings/EditRunbook.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/runbooks/[runbookId]/settings/EditRunbook.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/runbooks/[runbookId]/settings/EditRunbook.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/runbooks/[runbookId]/settings/page.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/runbooks/[runbookId]/settings/page.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/runbooks/[runbookId]/settings/page.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/runbooks/[runbookId]/settings/page.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/runbooks/create/CreateRunbookForm.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/runbooks/create/CreateRunbookForm.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/runbooks/create/CreateRunbookForm.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/runbooks/create/CreateRunbookForm.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/runbooks/create/RunbookVariableEditor.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/runbooks/create/RunbookVariableEditor.tsx similarity index 98% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/runbooks/create/RunbookVariableEditor.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/runbooks/create/RunbookVariableEditor.tsx index c6d1bc029..590867a1c 100644 --- a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/runbooks/create/RunbookVariableEditor.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/runbooks/create/RunbookVariableEditor.tsx @@ -32,7 +32,7 @@ import { ResourceConfigFields, RunbookConfigTypeSelector, StringConfigFields, -} from "~/app/[workspaceSlug]/(app)/systems/[systemSlug]/_components/variables/ConfigFields"; +} from "~/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/_components/variables/ConfigFields"; type RunbookVariableEditorProps = { value: InsertRunbookVariable; diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/runbooks/create/page.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/runbooks/create/page.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/runbooks/create/page.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/runbooks/create/page.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/runbooks/page.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/runbooks/page.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/runbooks/page.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/runbooks/page.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/variable-sets/CreateVariableSetDialog.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/variable-sets/CreateVariableSetDialog.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/variable-sets/CreateVariableSetDialog.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/variable-sets/CreateVariableSetDialog.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/variable-sets/GettingStartedVariableSets.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/variable-sets/GettingStartedVariableSets.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/variable-sets/GettingStartedVariableSets.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/variable-sets/GettingStartedVariableSets.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/variable-sets/VariableSetsTable.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/variable-sets/VariableSetsTable.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/variable-sets/VariableSetsTable.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/variable-sets/VariableSetsTable.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/variable-sets/[variableSetId]/page.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/variable-sets/[variableSetId]/page.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/variable-sets/[variableSetId]/page.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/variable-sets/[variableSetId]/page.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/variable-sets/page.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/variable-sets/page.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/variable-sets/page.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/[systemSlug]/variable-sets/page.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/page.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/page.tsx similarity index 100% rename from apps/webservice/src/app/[workspaceSlug]/(app)/systems/page.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/systems-old/page.tsx diff --git a/apps/webservice/src/app/[workspaceSlug]/(appv2)/TopNav.tsx b/apps/webservice/src/app/[workspaceSlug]/(appv2)/TopNav.tsx new file mode 100644 index 000000000..f65ac076a --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(appv2)/TopNav.tsx @@ -0,0 +1,54 @@ +import Image from "next/image"; +import { notFound } from "next/navigation"; +import { IconUser } from "@tabler/icons-react"; + +import { Avatar, AvatarFallback, AvatarImage } from "@ctrlplane/ui/avatar"; +import { Input } from "@ctrlplane/ui/input"; + +import { api } from "~/trpc/server"; +import { WorkspaceDropdown } from "./WorkspaceDropdown"; + +export const TopNav: React.FC<{ workspaceSlug: string }> = async ({ + workspaceSlug, +}) => { + const workspace = await api.workspace.bySlug(workspaceSlug).catch(() => null); + if (workspace == null) notFound(); + const viewer = await api.user.viewer(); + const workspaces = await api.workspace.list(); + return ( + + ); +}; diff --git a/apps/webservice/src/app/[workspaceSlug]/(appv2)/TopSidebarIcon.tsx b/apps/webservice/src/app/[workspaceSlug]/(appv2)/TopSidebarIcon.tsx new file mode 100644 index 000000000..6eba2f54e --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(appv2)/TopSidebarIcon.tsx @@ -0,0 +1,40 @@ +"use client"; + +import Link from "next/link"; +import { usePathname } from "next/navigation"; + +import { cn } from "@ctrlplane/ui"; + +export const TopSidebarIcon: React.FC<{ + icon: React.ReactNode; + label: string; + href: string; +}> = ({ icon, label, href }) => { + const pathname = usePathname(); + const active = pathname.startsWith(href); + + return ( +
+ + + {icon} + + + {label} + + +
+ ); +}; diff --git a/apps/webservice/src/app/[workspaceSlug]/(appv2)/WorkspaceDropdown.tsx b/apps/webservice/src/app/[workspaceSlug]/(appv2)/WorkspaceDropdown.tsx new file mode 100644 index 000000000..1d6e002be --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(appv2)/WorkspaceDropdown.tsx @@ -0,0 +1,87 @@ +"use client"; + +import type { Workspace } from "@ctrlplane/db/schema"; +import Link from "next/link"; +import { IconCheck, IconChevronDown } from "@tabler/icons-react"; + +import { Button } from "@ctrlplane/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuPortal, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, + DropdownMenuTrigger, +} from "@ctrlplane/ui/dropdown-menu"; + +import { api } from "~/trpc/react"; + +export const WorkspaceDropdown: React.FC<{ + workspace: Workspace; + workspaces: Workspace[]; + viewer: { email: string }; +}> = ({ workspace, workspaces, viewer }) => { + const update = api.profile.update.useMutation(); + return ( + + + + + + + Workspace settings + + + Invite and manage users + + + + + + Switch workspaces + + + + {viewer.email} + + {workspaces.map((ws) => ( + update.mutate({ activeWorkspaceId: ws.id })} + > + + {ws.name} + {ws.id === workspace.id && ( + + + + )} + + + ))} + + + + Create or join workspace + + + + + + + ); +}; diff --git a/apps/webservice/src/app/[workspaceSlug]/(appv2)/_components/ConfigEditor.tsx b/apps/webservice/src/app/[workspaceSlug]/(appv2)/_components/ConfigEditor.tsx new file mode 100644 index 000000000..ea60c1ea9 --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(appv2)/_components/ConfigEditor.tsx @@ -0,0 +1,40 @@ +"use client"; + +import React, { useEffect } from "react"; +import Editor, { loader } from "@monaco-editor/react"; +import colors from "tailwindcss/colors"; + +import { Card } from "@ctrlplane/ui/card"; + +export const ConfigEditor: React.FC<{ + value: string; + onChange?: (v: string) => void; + readOnly?: boolean; +}> = ({ readOnly, value, onChange }) => { + useEffect(() => { + loader.init().then((monaco) => { + monaco.editor.defineTheme("vs-dark-custom", { + base: "vs-dark", + inherit: true, + rules: [], + colors: { + "editor.background": colors.neutral[950], + }, + }); + }); + }, []); + return ( + +
+ onChange?.(v ?? "")} + options={{ readOnly, minimap: { enabled: false } }} + /> +
+
+ ); +}; diff --git a/apps/webservice/src/app/[workspaceSlug]/(appv2)/_components/CreateResource.tsx b/apps/webservice/src/app/[workspaceSlug]/(appv2)/_components/CreateResource.tsx new file mode 100644 index 000000000..c7bacafd7 --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(appv2)/_components/CreateResource.tsx @@ -0,0 +1,255 @@ +"use client"; + +import type { Workspace } from "@ctrlplane/db/schema"; +import type React from "react"; +import { useEffect, useState } from "react"; +import { useRouter } from "next/navigation"; +import { IconX } from "@tabler/icons-react"; +import yaml from "js-yaml"; +import { z } from "zod"; + +import { Button } from "@ctrlplane/ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@ctrlplane/ui/dialog"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, + FormRootError, + useFieldArray, + useForm, +} from "@ctrlplane/ui/form"; +import { Input } from "@ctrlplane/ui/input"; +import { Label } from "@ctrlplane/ui/label"; + +import { api } from "~/trpc/react"; +import { ConfigEditor } from "./ConfigEditor"; + +const createResourceSchema = z.object({ + name: z.string(), + kind: z.string(), + identifier: z.string().min(4), + version: z.string(), + config: z.string().refine((val) => { + try { + const output = yaml.load(val); + const isValidRecord = z.record(z.any()).safeParse(output).success; + return isValidRecord; + } catch { + return false; + } + }, "Config must be valid YAML Object"), + metadata: z.array(z.object({ key: z.string(), value: z.string() })), +}); + +const defaultValues = { + name: "", + identifier: "", + kind: "", + version: "", + metadata: [{ key: "", value: "" }], + config: "", +}; + +export const CreateResourceDialog: React.FC<{ + children: React.ReactNode; + workspace: Workspace; + onSuccess?: () => void; +}> = ({ children, workspace, onSuccess }) => { + const [open, setOpen] = useState(false); + + const form = useForm({ + schema: createResourceSchema, + defaultValues, + mode: "onSubmit", + }); + + useEffect(() => { + if (!open) form.reset(); + }, [form, open]); + + const router = useRouter(); + const create = api.resource.create.useMutation(); + const onSubmit = form.handleSubmit(async (data) => { + const config = yaml.load(data.config) as Record; + const resource = await create.mutateAsync({ + ...data, + config, + metadata: Object.fromEntries( + data.metadata.map(({ key, value }) => [key, value]), + ), + workspaceId: workspace.id, + }); + + const query = new URLSearchParams(window.location.search); + query.set("resource_id", resource.id); + router.replace(`?${query.toString()}`); + router.refresh(); + setOpen(false); + onSuccess?.(); + }); + + const { fields, append, remove } = useFieldArray({ + name: "metadata", + control: form.control, + }); + + return ( + + {children} + +
+ + + Bootstrap Resource + + Resources are typically created automatically through scanners + that discover and register new resources in your infrastructure. + However, you can manually bootstrap a resource if needed. + + + + ( + + Name + + + + + + )} + /> + + ( + + Identifier + + + + + + )} + /> + +
+ ( + + Version + + + + + + )} + /> + + ( + + Kind + + + + + + )} + /> +
+ + ( + + Config + + + + + + )} + /> + +
+
+ +
+ {fields.map((field, index) => ( + ( + + +
+ + onChange({ ...value, key: `${e.target.value}` }) + } + /> + + onChange({ ...value, value: `${e.target.value}` }) + } + /> + +
+
+
+ )} + /> + ))} + +
+ + + + + + + +
+
+ ); +}; diff --git a/apps/webservice/src/app/[workspaceSlug]/(appv2)/_components/PageHeader.tsx b/apps/webservice/src/app/[workspaceSlug]/(appv2)/_components/PageHeader.tsx new file mode 100644 index 000000000..3d79f7ef2 --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(appv2)/_components/PageHeader.tsx @@ -0,0 +1,17 @@ +import { cn } from "@ctrlplane/ui"; + +export const PageHeader: React.FC<{ + children: React.ReactNode; + className?: string; +}> = ({ children, className }) => { + return ( +
+ {children} +
+ ); +}; diff --git a/apps/webservice/src/app/[workspaceSlug]/(appv2)/_components/TableSortHeader.tsx b/apps/webservice/src/app/[workspaceSlug]/(appv2)/_components/TableSortHeader.tsx new file mode 100644 index 000000000..b5a0bb2de --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(appv2)/_components/TableSortHeader.tsx @@ -0,0 +1,61 @@ +import type { StatsColumn } from "@ctrlplane/validators/deployments"; +import { useRouter, useSearchParams } from "next/navigation"; +import { IconChevronDown } from "@tabler/icons-react"; + +import { cn } from "@ctrlplane/ui"; +import { StatsOrder } from "@ctrlplane/validators/deployments"; + +const useTableSortHeader = () => { + const router = useRouter(); + const params = useSearchParams(); + + const orderByParam = params.get("order-by"); + const orderParam = params.get("order"); + + const setOrderBy = (orderBy: StatsColumn) => { + const url = new URL(window.location.href); + if (orderByParam !== orderBy) { + url.searchParams.set("order-by", orderBy); + url.searchParams.set("order", StatsOrder.Asc); + router.replace(`${url.pathname}?${url.searchParams.toString()}`); + return; + } + + const newOrder = + orderParam === StatsOrder.Asc ? StatsOrder.Desc : StatsOrder.Asc; + url.searchParams.set("order", newOrder); + router.replace(`${url.pathname}?${url.searchParams.toString()}`); + }; + + return { orderByParam, orderParam, setOrderBy }; +}; + +export const TableSortHeader: React.FC<{ + children: React.ReactNode; + orderByKey: StatsColumn; +}> = ({ children, orderByKey }) => { + const { orderByParam, orderParam, setOrderBy } = useTableSortHeader(); + + const isActive = orderByParam === orderByKey; + + return ( +
setOrderBy(orderByKey)} + > + {children} + {isActive && ( + + )} +
+ ); +}; diff --git a/apps/webservice/src/app/[workspaceSlug]/(appv2)/_components/deployments/DeploymentHistoryGraph.tsx b/apps/webservice/src/app/[workspaceSlug]/(appv2)/_components/deployments/DeploymentHistoryGraph.tsx new file mode 100644 index 000000000..8a02490e1 --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(appv2)/_components/deployments/DeploymentHistoryGraph.tsx @@ -0,0 +1,71 @@ +import { useInView } from "react-intersection-observer"; + +import { Skeleton } from "@ctrlplane/ui/skeleton"; + +import { api } from "~/trpc/react"; + +export const HistorySkeleton: React.FC = () => ( +
+ {Array.from({ length: 30 }).map((_, i) => ( + + ))} +
+); + +type DeploymentHistoryGraphProps = { + deploymentId: string; + resourceId?: string; +}; + +const DeploymentHistoryGraph: React.FC = ({ + deploymentId, + resourceId, +}) => { + const { timeZone } = Intl.DateTimeFormat().resolvedOptions(); + const { data, isLoading } = api.deployment.stats.history.useQuery({ + deploymentId, + resourceId, + timeZone, + }); + + if (isLoading) return ; + + return ( +
+ {data?.map(({ successRate }, j) => ( +
+ {successRate == null ? ( +
+ ) : ( + <> +
+
+ + )} +
+ ))} +
+ ); +}; + +export const LazyDeploymentHistoryGraph: React.FC< + DeploymentHistoryGraphProps +> = (props) => { + const { ref, inView } = useInView(); + return ( +
+ {!inView && } + {inView && } +
+ ); +}; diff --git a/apps/webservice/src/app/[workspaceSlug]/(appv2)/_components/navigation/Tabs.tsx b/apps/webservice/src/app/[workspaceSlug]/(appv2)/_components/navigation/Tabs.tsx new file mode 100644 index 000000000..e3b41a759 --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(appv2)/_components/navigation/Tabs.tsx @@ -0,0 +1,31 @@ +import Link from "next/link"; + +export const Tabs: React.FC<{ + children: React.ReactNode; +}> = ({ children }) => { + return ( +
{children}
+ ); +}; + +export const TabsList: React.FC<{ + children: React.ReactNode; +}> = ({ children }) => { + return
{children}
; +}; + +export const TabLink: React.FC<{ + href: string; + isActive?: boolean; + children: React.ReactNode; +}> = ({ href, isActive, children }) => { + return ( + + {children} + + ); +}; diff --git a/apps/webservice/src/app/[workspaceSlug]/(appv2)/_components/reactflow/ArrowEdge.tsx b/apps/webservice/src/app/[workspaceSlug]/(appv2)/_components/reactflow/ArrowEdge.tsx new file mode 100644 index 000000000..b416a43f0 --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(appv2)/_components/reactflow/ArrowEdge.tsx @@ -0,0 +1,20 @@ +import type { EdgeProps } from "reactflow"; +import { BaseEdge, getSmoothStepPath, MarkerType } from "reactflow"; +import colors from "tailwindcss/colors"; + +export const ArrowEdge: React.FC = (edge) => { + const { markerEnd, style } = edge; + const [edgePath] = getSmoothStepPath(edge); + + return ( + + ); +}; diff --git a/apps/webservice/src/app/[workspaceSlug]/(appv2)/_components/reactflow/ReactFlowProvider.tsx b/apps/webservice/src/app/[workspaceSlug]/(appv2)/_components/reactflow/ReactFlowProvider.tsx new file mode 100644 index 000000000..3cd4d0b36 --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(appv2)/_components/reactflow/ReactFlowProvider.tsx @@ -0,0 +1,3 @@ +"use client"; +// Make sure we wrap this in use client +export { ReactFlowProvider } from "reactflow"; diff --git a/apps/webservice/src/app/[workspaceSlug]/(appv2)/_components/reactflow/edges.ts b/apps/webservice/src/app/[workspaceSlug]/(appv2)/_components/reactflow/edges.ts new file mode 100644 index 000000000..b5d683380 --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(appv2)/_components/reactflow/edges.ts @@ -0,0 +1,58 @@ +import type * as SCHEMA from "@ctrlplane/db/schema"; +import { MarkerType } from "reactflow"; +import colors from "tailwindcss/colors"; + +const markerEnd = { + type: MarkerType.Arrow, + color: colors.neutral[700], +}; + +export const createEdgesWhereEnvironmentHasNoPolicy = ( + envs: SCHEMA.Environment[], + standalonePolicies: SCHEMA.EnvironmentPolicy[], +) => + envs.map((e) => { + const isUsingStandalonePolicy = standalonePolicies.some( + (p) => p.id === e.policyId, + ); + const source = isUsingStandalonePolicy ? e.policyId : "trigger"; + return { + id: source + "-" + e.id, + source, + target: e.id, + markerEnd, + }; + }); + +export const createEdgesFromPolicyToEnvironment = ( + envs: Array<{ id: string; policyId?: string | null }>, +) => + envs.map((e) => ({ + id: `${e.policyId ?? "trigger"}-${e.id}`, + source: e.policyId ?? "trigger", + target: e.id, + markerEnd, + })); + +export const createEdgesFromPolicyDeployment = ( + policyDeployments: Array, +) => + policyDeployments.map((p) => ({ + id: p.id, + source: p.environmentId, + target: p.policyId, + markerEnd, + })); + +export const createEdgesWherePolicyHasNoEnvironment = ( + policies: Array, + policyDeployments: Array, +) => + policies + .filter((t) => !policyDeployments.some((p) => p.policyId === t.id)) + .map((e) => ({ + id: "trigger-" + e.id, + source: "trigger", + target: e.id, + markerEnd, + })); diff --git a/apps/webservice/src/app/[workspaceSlug]/(appv2)/_components/reactflow/layout.ts b/apps/webservice/src/app/[workspaceSlug]/(appv2)/_components/reactflow/layout.ts new file mode 100644 index 000000000..bcf875d96 --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(appv2)/_components/reactflow/layout.ts @@ -0,0 +1,150 @@ +"use client"; + +import type { Edge, Node, ReactFlowInstance } from "reactflow"; +import { useCallback, useEffect, useState } from "react"; +import dagre from "dagre"; +import { useReactFlow } from "reactflow"; + +const generateLevels = (nodes: Node[], edges: Edge[]) => { + const levels: Record = {}; + const edgeMap: Record = {}; + + edges.forEach((edge) => { + if (!edgeMap[edge.target]) edgeMap[edge.target] = []; + edgeMap[edge.target]!.push(edge.source); + }); + + const calculateLevel = (nodeId: string): number => { + if (levels[nodeId] !== undefined) return levels[nodeId]; + + const sources = edgeMap[nodeId] ?? []; + if (sources.length === 0) { + levels[nodeId] = 0; + return 0; + } + const maxSourceLevel = Math.max(...sources.map(calculateLevel)); + levels[nodeId] = maxSourceLevel + 1; + + return levels[nodeId]; + }; + + nodes.forEach((node) => calculateLevel(node.id)); + + return levels; +}; + +export const getLayoutedElementsDagre = ( + nodes: Node[], + edges: Edge[], + direction = "TB", + extraEdgeLength = 0, + nodesep = 50, +) => { + const dagreGraph = new dagre.graphlib.Graph(); + dagreGraph.setDefaultEdgeLabel(() => ({})); + dagreGraph.setGraph({ rankdir: direction, nodesep }); + + nodes.forEach((node) => dagreGraph.setNode(node.id, node)); + edges.forEach((edge) => dagreGraph.setEdge(edge.source, edge.target)); + dagre.layout(dagreGraph); + const levels = generateLevels(nodes, edges); + + return { + nodes: nodes.map((node) => { + const position = dagreGraph.node(node.id); + // We are shifting the dagre node position (anchor=center center) to the top left + // so it matches the React Flow node anchor point (top left). + const x = + position.x - (node.width ?? 0) / 2 + extraEdgeLength * levels[node.id]!; + const y = position.y - (node.height ?? 0) / 2; + + return { ...node, position: { x, y } }; + }), + edges, + }; +}; + +type LayoutConfig = { + direction?: "TB" | "LR" | "BT" | "RL"; + extraEdgeLength?: number; + nodesep?: number; + padding?: number; + maxZoom?: number; + focusedNodeId?: string; +}; + +export const useLayoutAndFitView = (nodes: Node[], config?: LayoutConfig) => { + const [isLayouted, setIsLayouted] = useState(false); + const [isViewFitted, setIsViewFitted] = useState(false); + + const { getNodes, setNodes, setEdges, getEdges } = useReactFlow(); + + const onLayout = useCallback(() => { + const layouted = getLayoutedElementsDagre( + getNodes(), + getEdges(), + config?.direction, + config?.extraEdgeLength, + config?.nodesep, + ); + setNodes(layouted.nodes); + setEdges(layouted.edges); + setIsLayouted(true); + }, [getNodes, getEdges, setNodes, setEdges, setIsLayouted, config]); + + const [reactFlowInstance, setReactFlowInstance] = + useState(null); + + useEffect(() => { + if (reactFlowInstance != null) onLayout(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [reactFlowInstance]); + + const setDefaultView = useCallback(() => { + reactFlowInstance?.fitView({ + padding: config?.padding ?? 0.12, + maxZoom: config?.maxZoom ?? 1, + }); + setIsViewFitted(true); + }, [reactFlowInstance, config]); + + useEffect(() => { + if ( + reactFlowInstance != null && + nodes.length && + isLayouted && + !isViewFitted + ) { + if (config?.focusedNodeId == null) { + setDefaultView(); + return; + } + + const focusedNode = nodes.find((n) => n.id === config.focusedNodeId); + if (focusedNode == null) { + setDefaultView(); + return; + } + + reactFlowInstance.fitView({ + padding: config.padding ?? 0.12, + maxZoom: config.maxZoom ?? 2, + }); + reactFlowInstance.setCenter( + focusedNode.position.x + 100, + focusedNode.position.y + 100, + { zoom: 1.5, duration: 500 }, + ); + setIsViewFitted(true); + } + }, [ + reactFlowInstance, + nodes, + isLayouted, + isViewFitted, + config, + setDefaultView, + ]); + + return { setReactFlowInstance, onLayout }; +}; diff --git a/apps/webservice/src/app/[workspaceSlug]/(appv2)/_components/resources/ReleaseCell.tsx b/apps/webservice/src/app/[workspaceSlug]/(appv2)/_components/resources/ReleaseCell.tsx new file mode 100644 index 000000000..5268cac59 --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(appv2)/_components/resources/ReleaseCell.tsx @@ -0,0 +1,107 @@ +"use client"; + +import type { + Deployment, + Job, + Release, + ReleaseJobTrigger, +} from "@ctrlplane/db/schema"; +import Link from "next/link"; +import { useParams } from "next/navigation"; +import { + IconAlertCircle, + IconCircleCheck, + IconCircleDashed, + IconCircleX, + IconClock, + IconLoader2, +} from "@tabler/icons-react"; +import { format } from "date-fns"; + +import { JobStatus } from "@ctrlplane/validators/jobs"; + +export const ReleaseIcon: React.FC<{ + job?: Job; +}> = ({ job }) => { + if (job?.status === JobStatus.Pending) + return ( +
+ +
+ ); + + if (job?.status === JobStatus.InProgress) + return ( +
+ +
+ ); + + if (job?.status === JobStatus.Successful) + return ( +
+ +
+ ); + + if (job?.status === JobStatus.Cancelled) + return ( +
+ +
+ ); + + if (job?.status === JobStatus.Failure) + return ( +
+ +
+ ); + + if (job?.status === JobStatus.Skipped) + return ( +
+ +
+ ); + + if ( + job?.status === JobStatus.InvalidJobAgent || + job?.status === JobStatus.InvalidIntegration + ) + return ( +
+ +
+ ); + + return null; +}; + +export const ReleaseCell: React.FC<{ + deployment: Deployment; + releaseJobTrigger: ReleaseJobTrigger & { + release?: Partial; + job?: Job; + }; +}> = ({ deployment, releaseJobTrigger }) => { + const params = useParams<{ workspaceSlug: string; systemSlug: string }>(); + return ( + + +
+
+ + {releaseJobTrigger.release?.version} + +
+
+ {format(releaseJobTrigger.createdAt, "MMM d, hh:mm aa")} +
+
+ + ); +}; diff --git a/apps/webservice/src/app/[workspaceSlug]/(appv2)/deployments2/page.tsx b/apps/webservice/src/app/[workspaceSlug]/(appv2)/deployments2/page.tsx new file mode 100644 index 000000000..8d3d6328a --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(appv2)/deployments2/page.tsx @@ -0,0 +1,34 @@ +import { + Breadcrumb, + BreadcrumbItem, + BreadcrumbLink, + BreadcrumbList, + BreadcrumbPage, + BreadcrumbSeparator, +} from "@ctrlplane/ui/breadcrumb"; +import { Separator } from "@ctrlplane/ui/separator"; +import { SidebarTrigger } from "@ctrlplane/ui/sidebar"; + +import { PageHeader } from "../_components/PageHeader"; + +export default function DeploymentsPage() { + return ( +
+ + + + + + + Deployments + + + + Data Fetching + + + + +
+ ); +} diff --git a/apps/webservice/src/app/[workspaceSlug]/(appv2)/layout.tsx b/apps/webservice/src/app/[workspaceSlug]/(appv2)/layout.tsx new file mode 100644 index 000000000..c5ee05ce8 --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(appv2)/layout.tsx @@ -0,0 +1,74 @@ +import React from "react"; +import { + IconBook, + IconCategory, + IconChartBar, + IconCube, + IconPlug, + IconRocket, + IconSettings, +} from "@tabler/icons-react"; + +import { TopNav } from "./TopNav"; +import { TopSidebarIcon } from "./TopSidebarIcon"; + +export const metadata = { + title: "Ctrlplane", +}; + +export default async function Layout(props: { + params: Promise<{ workspaceSlug: string }>; + children: React.ReactNode; +}) { + const params = await props.params; + return ( +
+ + +
+ + +
+ {props.children} +
+
+
+ ); +} diff --git a/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/SidebarKinds.tsx b/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/SidebarKinds.tsx new file mode 100644 index 000000000..10ac37450 --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/SidebarKinds.tsx @@ -0,0 +1,75 @@ +"use client"; + +import type { Workspace } from "@ctrlplane/db/schema"; +import React from "react"; +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import LZString from "lz-string"; + +import { Badge } from "@ctrlplane/ui/badge"; +import { + SidebarGroup, + SidebarGroupLabel, + SidebarMenu, + SidebarMenuButton, +} from "@ctrlplane/ui/sidebar"; +import { + ComparisonOperator, + FilterType, +} from "@ctrlplane/validators/conditions"; +import { ResourceFilterType } from "@ctrlplane/validators/resources"; + +import { api } from "~/trpc/react"; +import { ResourceIcon } from "../../(app)/_components/ResourceIcon"; + +export const SidebarGroupKinds: React.FC<{ workspace: Workspace }> = ({ + workspace, +}) => { + const pathname = usePathname(); + const kinds = api.workspace.resourceKinds.useQuery(workspace.id); + return ( + + Types + + {kinds.data?.length === 0 && ( +
+ No resources found +
+ )} + {kinds.data?.map(({ version, kind, count }) => { + const url = `/${workspace.slug}/resources?filter=${LZString.compressToEncodedURIComponent( + JSON.stringify({ + type: FilterType.Comparison, + operator: ComparisonOperator.And, + conditions: [ + { + type: ResourceFilterType.Kind, + value: kind, + operator: "equals", + }, + ], + }), + )}`; + return ( + + + + {kind} + + {count} + + + + ); + })} +
+
+ ); +}; diff --git a/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/SidebarLink.tsx b/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/SidebarLink.tsx new file mode 100644 index 000000000..c4b4395a2 --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/SidebarLink.tsx @@ -0,0 +1,30 @@ +"use client"; + +import Link from "next/link"; +import { usePathname } from "next/navigation"; + +import { cn } from "@ctrlplane/ui"; +import { SidebarMenuButton } from "@ctrlplane/ui/sidebar"; + +export const SidebarLink: React.FC<{ + icon?: React.ReactNode; + href: string; + children: React.ReactNode; +}> = ({ icon, href, children }) => { + const pathname = usePathname(); + const active = pathname.startsWith(href); + return ( + + + {icon} + {children} + + + ); +}; diff --git a/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/[resourceId]/DeploymentTabs.tsx b/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/[resourceId]/DeploymentTabs.tsx new file mode 100644 index 000000000..578f8181c --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/[resourceId]/DeploymentTabs.tsx @@ -0,0 +1,52 @@ +"use client"; + +import React from "react"; +import { useParams, usePathname } from "next/navigation"; + +import { TabLink, Tabs, TabsList } from "../../_components/navigation/Tabs"; + +const getActiveTab = (url: string) => { + if (url.endsWith("/visualize")) return "visualize"; + if (url.endsWith("/variables")) return "variables"; + if (url.endsWith("/properties")) return "properties"; + return "deployments"; +}; + +export const DeploymentTabs: React.FC = () => { + const { workspaceSlug, resourceId } = useParams<{ + workspaceSlug: string; + resourceId: string; + }>(); + + const pathname = usePathname(); + const activeTab = getActiveTab(pathname); + const baseUrl = `/${workspaceSlug}/resources/${resourceId}`; + + return ( + + + + Deployments + + + Visualize + + + Variables + + + Properties + + + + ); +}; diff --git a/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/[resourceId]/deployments/ResourceDeploymentRow.tsx b/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/[resourceId]/deployments/ResourceDeploymentRow.tsx new file mode 100644 index 000000000..c32bb1e6f --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/[resourceId]/deployments/ResourceDeploymentRow.tsx @@ -0,0 +1,102 @@ +import type { RouterOutputs } from "@ctrlplane/api"; +import type * as SCHEMA from "@ctrlplane/db/schema"; +import { useParams, useRouter } from "next/navigation"; +import { formatDistanceToNowStrict } from "date-fns"; +import prettyMilliseconds from "pretty-ms"; + +import { cn } from "@ctrlplane/ui"; +import { TableCell, TableRow } from "@ctrlplane/ui/table"; + +import { LazyDeploymentHistoryGraph } from "~/app/[workspaceSlug]/(appv2)/_components/deployments/DeploymentHistoryGraph"; + +type DeploymentStats = + RouterOutputs["deployment"]["stats"]["byWorkspaceId"][number] & { + latestTrigger?: RouterOutputs["job"]["config"]["byWorkspaceId"]["list"][number]; + resource: SCHEMA.Resource; + }; + +type ResourceDeploymentRowProps = { stats: DeploymentStats }; + +export const ResourceDeploymentRow: React.FC = ({ + stats, +}) => { + const { workspaceSlug } = useParams<{ workspaceSlug: string }>(); + const router = useRouter(); + + const successRate = stats.successRate ?? 0; + + return ( + { + router.push( + `/${workspaceSlug}/systems/${stats.systemSlug}/deployments/${stats.id}/releases`, + ); + }} + > + +
+ {stats.name} + + {stats.systemName} / {stats.name} + +
+
+ + + {stats.latestTrigger?.release.version ?? "No release"} + + + + + + + + {stats.p50 != null + ? prettyMilliseconds(Math.round(stats.p50 * 1000), { + unitCount: 2, + secondsDecimalDigits: 0, + }) + : "N/A"} + + + {stats.p90 != null + ? prettyMilliseconds(Math.round(stats.p90 * 1000), { + unitCount: 2, + secondsDecimalDigits: 0, + }) + : "N/A"} + + + +
+
+
+
+
{successRate.toFixed(0)}%
+
+ + + +
+ {stats.lastRunAt + ? formatDistanceToNowStrict(stats.lastRunAt, { + addSuffix: false, + }) + : "No runs"} +
+
+ + ); +}; diff --git a/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/[resourceId]/deployments/ResourceDeploymentRowSkeleton.tsx b/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/[resourceId]/deployments/ResourceDeploymentRowSkeleton.tsx new file mode 100644 index 000000000..9fb5eb49b --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/[resourceId]/deployments/ResourceDeploymentRowSkeleton.tsx @@ -0,0 +1,41 @@ +import { Skeleton } from "@ctrlplane/ui/skeleton"; + +import { HistorySkeleton } from "~/app/[workspaceSlug]/(appv2)/_components/deployments/DeploymentHistoryGraph"; + +export const ResourceDeploymentRowSkeleton: React.FC<{ opacity: number }> = ({ + opacity, +}) => ( + + + {/* hardcode so that the skeleton doesn't cause visual shift of the table header */} +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +); diff --git a/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/[resourceId]/deployments/ResourceDeploymentTable.tsx b/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/[resourceId]/deployments/ResourceDeploymentTable.tsx new file mode 100644 index 000000000..7cbe8ede2 --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/[resourceId]/deployments/ResourceDeploymentTable.tsx @@ -0,0 +1,172 @@ +"use client"; + +import type * as SCHEMA from "@ctrlplane/db/schema"; +import type { StatsOrder } from "@ctrlplane/validators/deployments"; +import type { JobCondition } from "@ctrlplane/validators/jobs"; +import { useMemo } from "react"; +import { useSearchParams } from "next/navigation"; +import { subDays } from "date-fns"; +import _ from "lodash"; + +import { Card } from "@ctrlplane/ui/card"; +import { + Table, + TableBody, + TableHead, + TableHeader, + TableRow, +} from "@ctrlplane/ui/table"; +import { + ColumnOperator, + ComparisonOperator, + DateOperator, + FilterType, +} from "@ctrlplane/validators/conditions"; +import { StatsColumn } from "@ctrlplane/validators/deployments"; +import { JobFilterType } from "@ctrlplane/validators/jobs"; + +import { TableSortHeader } from "~/app/[workspaceSlug]/(appv2)/_components/TableSortHeader"; +import { api } from "~/trpc/react"; +import { ResourceDeploymentRow } from "./ResourceDeploymentRow"; +import { ResourceDeploymentRowSkeleton } from "./ResourceDeploymentRowSkeleton"; + +type ResourceDeploymentsTableProps = { resource: SCHEMA.Resource }; + +const getFilter = ( + resourceId: string, + startDate: Date, + endDate: Date, +): JobCondition => { + const resourceFilter: JobCondition = { + type: JobFilterType.JobResource, + operator: ColumnOperator.Equals, + value: resourceId, + }; + + const startDateFilter: JobCondition = { + type: FilterType.CreatedAt, + operator: DateOperator.AfterOrOn, + value: startDate.toISOString(), + }; + + const endDateFilter: JobCondition = { + type: FilterType.CreatedAt, + operator: DateOperator.BeforeOrOn, + value: endDate.toISOString(), + }; + + return { + type: FilterType.Comparison, + operator: ComparisonOperator.And, + conditions: [resourceFilter, startDateFilter, endDateFilter], + }; +}; + +export const ResourceDeploymentsTable: React.FC< + ResourceDeploymentsTableProps +> = ({ resource }) => { + const { workspaceId } = resource; + + const params = useSearchParams(); + const orderByParam = params.get("order-by"); + const orderParam = params.get("order"); + + const endDate = useMemo(() => new Date(), []); + const startDate = subDays(endDate, 14); + const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; + + const filter = getFilter(resource.id, startDate, endDate); + + const triggersQ = api.job.config.byWorkspaceId.list.useQuery({ + workspaceId, + filter, + limit: 100, + }); + + const triggers = triggersQ.data ?? []; + + const latestTriggersByDeployment = _.chain(triggers) + .groupBy((t) => t.release.deploymentId) + .map((groupedTriggers) => _.maxBy(groupedTriggers, (t) => t.job.startedAt)) + .filter((t) => t != null) + .value(); + + const statsQ = api.deployment.stats.byWorkspaceId.useQuery({ + workspaceId, + resourceId: resource.id, + startDate, + endDate, + timezone, + orderBy: orderByParam != null ? (orderByParam as StatsColumn) : undefined, + order: orderParam != null ? (orderParam as StatsOrder) : undefined, + }); + + const stats = statsQ.data ?? []; + + const statsWithLatestTrigger = stats.map((stat) => { + const latestTrigger = latestTriggersByDeployment.find( + (t) => t.release.deploymentId === stat.id, + ); + return { ...stat, latestTrigger, resource }; + }); + + const isLoading = triggersQ.isLoading || statsQ.isLoading; + + return ( + + + + + + + Deployment + + + + Version + + History + + + + P50 Duration + + + + + + P90 Duration + + + + + + Success Rate + + + + + + Last Run + + + + + + + {!isLoading && + statsWithLatestTrigger.map((stat) => ( + + ))} + {isLoading && + Array.from({ length: 3 }).map((_, index) => ( + + ))} + +
+
+ ); +}; diff --git a/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/[resourceId]/layout.tsx b/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/[resourceId]/layout.tsx new file mode 100644 index 000000000..96e74a96c --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/[resourceId]/layout.tsx @@ -0,0 +1,59 @@ +import Link from "next/link"; +import { notFound } from "next/navigation"; +import { IconArrowLeft, IconEdit, IconLock } from "@tabler/icons-react"; + +import { + Breadcrumb, + BreadcrumbItem, + BreadcrumbList, + BreadcrumbPage, +} from "@ctrlplane/ui/breadcrumb"; +import { Button } from "@ctrlplane/ui/button"; +import { Separator } from "@ctrlplane/ui/separator"; + +import { api } from "~/trpc/server"; +import { PageHeader } from "../../_components/PageHeader"; +import { DeploymentTabs } from "./DeploymentTabs"; + +export default async function Layout(props: { + children: React.ReactNode; + params: Promise<{ resourceId: string; workspaceSlug: string }>; +}) { + const params = await props.params; + const resource = await api.resource.byId(params.resourceId); + if (resource == null) notFound(); + return ( +
+ +
+ + + + + + + + Resource List + + + +
+ +
+ + +
+
+ +
+

{resource.name}

+ +
{props.children}
+
+
+ ); +} diff --git a/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/[resourceId]/page.tsx b/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/[resourceId]/page.tsx new file mode 100644 index 000000000..7059085ed --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/[resourceId]/page.tsx @@ -0,0 +1,20 @@ +import { notFound } from "next/navigation"; + +import { api } from "~/trpc/server"; +import { ResourceDeploymentsTable } from "./deployments/ResourceDeploymentTable"; + +type Params = Promise<{ resourceId: string }>; +type SearchParams = Promise<{ tab?: string }>; + +export default async function ResourcePage(props: { + params: Params; + searchParams: SearchParams; +}) { + const params = await props.params; + const { resourceId } = params; + + const resource = await api.resource.byId(resourceId); + if (resource == null) notFound(); + + return ; +} diff --git a/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/[resourceId]/visualize/DepEdge.tsx b/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/[resourceId]/visualize/DepEdge.tsx new file mode 100644 index 000000000..fe0b3b3d3 --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/[resourceId]/visualize/DepEdge.tsx @@ -0,0 +1,49 @@ +import type { EdgeProps } from "reactflow"; +import { capitalCase } from "change-case"; +import { BaseEdge, EdgeLabelRenderer, getBezierPath } from "reactflow"; + +export const DepEdge: React.FC = ({ + sourceX, + sourceY, + targetX, + targetY, + sourcePosition, + targetPosition, + label, + style = {}, + markerEnd, +}) => { + const [edgePath, labelX, labelY] = getBezierPath({ + sourceX, + sourceY, + sourcePosition, + targetX, + targetY, + targetPosition, + }); + + const edgeLabel = capitalCase(String(label).replace(/_/g, " ")); + + return ( + <> + + +
+ {edgeLabel} +
+
+ + ); +}; diff --git a/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/[resourceId]/visualize/ResourceVisualizationDiagram.tsx b/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/[resourceId]/visualize/ResourceVisualizationDiagram.tsx new file mode 100644 index 000000000..5817aaa31 --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/[resourceId]/visualize/ResourceVisualizationDiagram.tsx @@ -0,0 +1,86 @@ +"use client"; + +import type { RouterOutputs } from "@ctrlplane/api"; +import React from "react"; +import { IconLoader2, IconNetworkOff } from "@tabler/icons-react"; +import ReactFlow, { + ReactFlowProvider, + useEdgesState, + useNodesState, +} from "reactflow"; + +import { useLayoutAndFitView } from "~/app/[workspaceSlug]/(appv2)/_components/reactflow/layout"; +import { api } from "~/trpc/react"; +import { edgeTypes, getEdges } from "./edges"; +import { getNodes, nodeTypes } from "./nodes/nodes"; + +type Relationships = NonNullable; + +type ResourceVisualizationDiagramProps = { + relationships: Relationships; +}; + +export const ResourceVisualizationDiagram: React.FC< + ResourceVisualizationDiagramProps +> = ({ relationships }) => { + const [nodes, _, onNodesChange] = useNodesState<{ label: string }>( + getNodes(relationships), + ); + + const [edges, __, onEdgesChange] = useEdgesState(getEdges(relationships)); + + const { setReactFlowInstance } = useLayoutAndFitView(nodes, { + direction: "LR", + extraEdgeLength: 50, + focusedNodeId: relationships.resource.id, + }); + + return ( + + ); +}; + +type ResourceVisualizationDiagramProviderProps = { + resourceId: string; +}; + +export const ResourceVisualizationDiagramProvider: React.FC< + ResourceVisualizationDiagramProviderProps +> = ({ resourceId }) => { + const { data: relationships, isLoading } = + api.resource.relationships.useQuery(resourceId, { + refetchInterval: 60_000, + }); + + if (isLoading) + return ( +
+ +
+ ); + + if (!relationships) + return ( +
+ No relationships found +
+ ); + + return ( + + + + ); +}; diff --git a/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/[resourceId]/visualize/edges.ts b/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/[resourceId]/visualize/edges.ts new file mode 100644 index 000000000..dd06d497c --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/[resourceId]/visualize/edges.ts @@ -0,0 +1,140 @@ +import type { RouterOutputs } from "@ctrlplane/api"; +import type * as SCHEMA from "@ctrlplane/db/schema"; +import type { EdgeTypes } from "reactflow"; +import { MarkerType } from "reactflow"; +import colors from "tailwindcss/colors"; +import { isPresent } from "ts-is-present"; + +import { DepEdge } from "./DepEdge"; + +type Provider = SCHEMA.ResourceProvider & { + google: SCHEMA.ResourceProviderGoogle | null; +}; + +const markerEnd = { + type: MarkerType.Arrow, + color: colors.neutral[800], +}; + +export const edgeTypes: EdgeTypes = { default: DepEdge }; + +const createEdgesFromResourceToEnvironments = ( + resource: SCHEMA.Resource, + environments: SCHEMA.Environment[], +) => + environments.map((environment) => ({ + id: `${resource.id}-${environment.id}`, + source: resource.id, + target: environment.id, + style: { stroke: colors.neutral[800] }, + markerEnd, + label: "in", + })); + +const createEdgeFromProviderToResource = ( + provider: Provider | null, + resource: SCHEMA.Resource, +) => + provider != null + ? { + id: `${provider.id}-${resource.id}`, + source: `${provider.id}-${resource.id}`, + target: resource.id, + style: { stroke: colors.neutral[800] }, + markerEnd, + label: "discovered", + } + : null; + +type Relationships = NonNullable; + +const createEdgesFromEnvironmentToDeployments = ( + environments: SCHEMA.Environment[], + deployments: SCHEMA.Deployment[], +) => + environments + .flatMap((e) => deployments.map((d) => ({ e, d }))) + .map(({ e, d }) => ({ + id: `${e.id}-${d.id}`, + source: e.id, + target: `${e.id}-${d.id}`, + label: "deploys", + style: { stroke: colors.neutral[800] }, + markerEnd, + })); + +const createEdgesFromDeploymentsToResources = (relationships: Relationships) => + relationships.nodes.map((resource) => { + const { parent } = resource; + if (parent == null) return null; + + const allReleaseJobTriggers = relationships.nodes + .flatMap((r) => r.workspace.systems) + .flatMap((s) => s.environments) + .flatMap((e) => e.latestActiveReleases) + .map((rel) => rel.releaseJobTrigger); + + const releaseJobTrigger = allReleaseJobTriggers.find( + (j) => j.jobId === parent.jobId, + ); + if (releaseJobTrigger == null) return null; + + const { deploymentId } = releaseJobTrigger.release; + const { environmentId } = releaseJobTrigger; + + return { + id: `${releaseJobTrigger.jobId}-${resource.id}`, + source: `${environmentId}-${deploymentId}`, + target: resource.id, + style: { stroke: colors.neutral[800] }, + markerEnd, + label: "created", + }; + }); + +export const getEdges = (relationships: Relationships) => { + const resourceToEnvEdges = relationships.nodes.flatMap((r) => + createEdgesFromResourceToEnvironments( + r, + r.workspace.systems.flatMap((s) => s.environments), + ), + ); + const environmentToDeploymentEdges = relationships.nodes.flatMap((r) => + r.workspace.systems.flatMap((s) => + createEdgesFromEnvironmentToDeployments(s.environments, s.deployments), + ), + ); + const providerEdges = relationships.nodes.flatMap((r) => + r.provider != null ? [createEdgeFromProviderToResource(r.provider, r)] : [], + ); + const deploymentEdges = createEdgesFromDeploymentsToResources(relationships); + + const { resource } = relationships; + + const fromEdges = relationships.associations.from.map((r) => ({ + id: `${r.resource.id}-${resource.id}`, + source: r.resource.id, + target: resource.id, + style: { stroke: colors.neutral[800] }, + markerEnd, + label: r.type, + })); + + const toEdges = relationships.associations.to.map((r) => ({ + id: `${resource.id}-${r.resource.id}`, + source: resource.id, + target: r.resource.id, + style: { stroke: colors.neutral[800] }, + markerEnd, + label: r.type, + })); + + return [ + ...resourceToEnvEdges, + ...environmentToDeploymentEdges, + ...providerEdges, + ...deploymentEdges, + ...fromEdges, + ...toEdges, + ].filter(isPresent); +}; diff --git a/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/[resourceId]/visualize/nodes/DeploymentNode.tsx b/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/[resourceId]/visualize/nodes/DeploymentNode.tsx new file mode 100644 index 000000000..99cf54b6b --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/[resourceId]/visualize/nodes/DeploymentNode.tsx @@ -0,0 +1,102 @@ +import type * as SCHEMA from "@ctrlplane/db/schema"; +import type { NodeProps } from "reactflow"; +import React from "react"; +import { Handle, Position } from "reactflow"; + +import { cn } from "@ctrlplane/ui"; +import { JobStatus, JobStatusReadable } from "@ctrlplane/validators/jobs"; + +import { useDeploymentEnvResourceDrawer } from "~/app/[workspaceSlug]/(app)/_components/deployment-resource-drawer/useDeploymentResourceDrawer"; +import { ReleaseIcon } from "~/app/[workspaceSlug]/(appv2)/_components/resources/ReleaseCell"; +import { api } from "~/trpc/react"; + +type DeploymentNodeProps = NodeProps<{ + label: string; + deployment: SCHEMA.Deployment; + environment: SCHEMA.Environment; + resource: SCHEMA.Resource; +}>; + +export const DeploymentNode: React.FC = ({ data }) => { + const { deployment, environment, resource } = data; + const { setDeploymentEnvResourceId } = useDeploymentEnvResourceDrawer(); + + const resourceId = resource.id; + const environmentId = environment.id; + const latestActiveReleasesQ = + api.resource.activeReleases.byResourceAndEnvironmentId.useQuery( + { resourceId, environmentId }, + { refetchInterval: 5_000 }, + ); + const latestActiveReleases = latestActiveReleasesQ.data ?? []; + const activeRelease = latestActiveReleases.find( + (r) => r.releaseJobTrigger.release.deploymentId === deployment.id, + ); + + const isInProgress = latestActiveReleases.some( + (r) => r.releaseJobTrigger.job.status === JobStatus.InProgress, + ); + const isPending = latestActiveReleases.some( + (r) => r.releaseJobTrigger.job.status === JobStatus.Pending, + ); + const isSuccess = latestActiveReleases.every( + (r) => r.releaseJobTrigger.job.status === JobStatus.Successful, + ); + + const releaseJobTrigger = activeRelease?.releaseJobTrigger; + + return ( + <> +
+ setDeploymentEnvResourceId(deployment.id, environment.id, resource.id) + } + > + +
+ {deployment.name} + {releaseJobTrigger != null && ( + + {releaseJobTrigger.release.name} -{" "} + {JobStatusReadable[releaseJobTrigger.job.status]} + + )} + {releaseJobTrigger == null && ( + + No active release + + )} +
+
+ + + + ); +}; diff --git a/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/[resourceId]/visualize/nodes/EnvironmentNode.tsx b/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/[resourceId]/visualize/nodes/EnvironmentNode.tsx new file mode 100644 index 000000000..6b7517352 --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/[resourceId]/visualize/nodes/EnvironmentNode.tsx @@ -0,0 +1,47 @@ +"use client"; + +import type { RouterOutputs } from "@ctrlplane/api"; +import type { NodeProps } from "reactflow"; +import React from "react"; +import { IconPlant } from "@tabler/icons-react"; +import { Handle, Position } from "reactflow"; + +import { useEnvironmentDrawer } from "~/app/[workspaceSlug]/(app)/_components/environment-drawer/EnvironmentDrawer"; + +type Environment = NonNullable< + RouterOutputs["resource"]["relationships"] +>["nodes"][number]["workspace"]["systems"][number]["environments"][number]; + +type EnvironmentNodeProps = NodeProps<{ + label: string; + environment: Environment; +}>; + +export const EnvironmentNode: React.FC = (node) => { + const { data } = node; + const { setEnvironmentId } = useEnvironmentDrawer(); + return ( + <> +
setEnvironmentId(data.environment.id)} + > +
+ + Environment +
+ {data.label} +
+ + + + ); +}; diff --git a/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/[resourceId]/visualize/nodes/ProviderNode.tsx b/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/[resourceId]/visualize/nodes/ProviderNode.tsx new file mode 100644 index 000000000..f194704a9 --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/[resourceId]/visualize/nodes/ProviderNode.tsx @@ -0,0 +1,53 @@ +import type * as SCHEMA from "@ctrlplane/db/schema"; +import type { NodeProps } from "reactflow"; +import React from "react"; +import { IconBrandGoogleFilled, IconCube } from "@tabler/icons-react"; +import { Handle, Position } from "reactflow"; + +type ProviderNodeProps = NodeProps<{ + id: string; + name: string; + label: string; + workspaceId: string; + google: SCHEMA.ResourceProviderGoogle | null; +}>; + +export const ProviderIcon: React.FC<{ node: ProviderNodeProps }> = ({ + node, +}) => { + const { google } = node.data; + if (google != null) + return ; + return ; +}; + +const ProviderLabel: React.FC<{ node: ProviderNodeProps }> = ({ node }) => { + const { google } = node.data; + if (google != null) return Google Provider; + return Resource Provider; +}; + +export const ProviderNode: React.FC = (node) => { + const { data } = node; + return ( + <> +
+
+ + +
+ {data.label} +
+ + + + ); +}; diff --git a/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/[resourceId]/visualize/nodes/ResourceNode.tsx b/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/[resourceId]/visualize/nodes/ResourceNode.tsx new file mode 100644 index 000000000..fc1008e4b --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/[resourceId]/visualize/nodes/ResourceNode.tsx @@ -0,0 +1,48 @@ +import type { NodeProps } from "reactflow"; +import { Handle, Position } from "reactflow"; + +import { cn } from "@ctrlplane/ui"; + +import { useResourceDrawer } from "~/app/[workspaceSlug]/(app)/_components/resource-drawer/useResourceDrawer"; +import { ResourceIcon } from "~/app/[workspaceSlug]/(app)/_components/ResourceIcon"; + +type ResourceNodeProps = NodeProps<{ + name: string; + label: string; + id: string; + kind: string; + version: string; + isBaseNode: boolean; +}>; +export const ResourceNode: React.FC = (node) => { + const { data } = node; + const { setResourceId } = useResourceDrawer(); + return ( + <> +
setResourceId(data.id)} + > +
+ + {data.kind} +
+ {data.name} +
+ + + + + ); +}; diff --git a/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/[resourceId]/visualize/nodes/nodes.ts b/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/[resourceId]/visualize/nodes/nodes.ts new file mode 100644 index 000000000..7e08b4d9a --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/[resourceId]/visualize/nodes/nodes.ts @@ -0,0 +1,88 @@ +import type { RouterOutputs } from "@ctrlplane/api"; +import type { NodeTypes } from "reactflow"; +import { isPresent } from "ts-is-present"; + +import { DeploymentNode } from "./DeploymentNode"; +import { EnvironmentNode } from "./EnvironmentNode"; +import { ProviderNode } from "./ProviderNode"; +import { ResourceNode } from "./ResourceNode"; + +type Relationships = NonNullable; + +enum NodeType { + Resource = "resource", + Environment = "environment", + Provider = "provider", + Deployment = "deployment", +} + +export const nodeTypes: NodeTypes = { + [NodeType.Resource]: ResourceNode, + [NodeType.Environment]: EnvironmentNode, + [NodeType.Provider]: ProviderNode, + [NodeType.Deployment]: DeploymentNode, +}; + +const getResourceNodes = (relationships: Relationships) => + relationships.nodes.map((r) => ({ + id: r.id, + type: NodeType.Resource, + data: { + ...r, + label: r.identifier, + isBaseNode: r.id === relationships.resource.id, + }, + position: { x: 0, y: 0 }, + })); + +const getProviderNodes = (relationships: Relationships) => + relationships.nodes + .map((r) => + r.provider != null + ? { + id: `${r.provider.id}-${r.id}`, + type: NodeType.Provider, + data: { ...r.provider, label: r.provider.name }, + position: { x: 0, y: 0 }, + } + : null, + ) + .filter(isPresent); + +const getEnvironmentNodes = (relationships: Relationships) => + relationships.nodes + .flatMap((r) => r.workspace.systems) + .flatMap((s) => s.environments.map((e) => ({ s, e }))) + .map(({ s, e }) => ({ + id: e.id, + type: NodeType.Environment, + data: { environment: e, label: `${s.name}/${e.name}` }, + position: { x: 0, y: 0 }, + })); + +const getDeploymentNodes = (relationships: Relationships) => + relationships.nodes.flatMap((r) => + r.workspace.systems.flatMap((system) => + system.environments.flatMap((environment) => + system.deployments.map((deployment) => ({ + id: `${environment.id}-${deployment.id}`, + type: NodeType.Deployment, + data: { + deployment, + environment, + resource: r, + label: deployment.name, + }, + position: { x: 0, y: 0 }, + })), + ), + ), + ); + +export const getNodes = (relationships: Relationships) => + [ + ...getResourceNodes(relationships), + ...getProviderNodes(relationships), + ...getEnvironmentNodes(relationships), + ...getDeploymentNodes(relationships), + ].filter(isPresent); diff --git a/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/[resourceId]/visualize/page.tsx b/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/[resourceId]/visualize/page.tsx new file mode 100644 index 000000000..27a2b16bf --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/[resourceId]/visualize/page.tsx @@ -0,0 +1,10 @@ +import { ResourceVisualizationDiagramProvider } from "./ResourceVisualizationDiagram"; + +export default async function VisualizePage(props: { + params: Promise<{ resourceId: string }>; +}) { + const params = await props.params; + const { resourceId } = params; + + return ; +} diff --git a/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/list/ResourceGettingStarted.tsx b/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/list/ResourceGettingStarted.tsx new file mode 100644 index 000000000..2e7b127ff --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/list/ResourceGettingStarted.tsx @@ -0,0 +1,49 @@ +import type { Workspace } from "@ctrlplane/db/schema"; +import Link from "next/link"; +import { IconTopologyComplex } from "@tabler/icons-react"; + +import { Button, buttonVariants } from "@ctrlplane/ui/button"; + +import { CreateResourceDialog } from "../../_components/CreateResource"; + +export const ResourceGettingStarted: React.FC<{ workspace: Workspace }> = ({ + workspace, +}) => { + return ( +
+
+
+ +
+
Resources
+
+

+ Resources are the destinations where your jobs are executed. They + can represent a wide range of entities, from a traditional + infrastructure resource like an EKS cluster to a more abstract + resource like a Salesforce account. +

+

+ To keep the status of resources up-to-date, they should be created + by providers. You can then attach metadata to these resources, + allowing Environments to easily filter and include them in specific + workflows. +

+
+
+ + + + + Documentation + +
+
+
+ ); +}; diff --git a/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/list/ResourcePageContent.tsx b/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/list/ResourcePageContent.tsx new file mode 100644 index 000000000..d51c3d61c --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/list/ResourcePageContent.tsx @@ -0,0 +1,238 @@ +"use client"; + +import type * as schema from "@ctrlplane/db/schema"; +import type { ResourceCondition } from "@ctrlplane/validators/resources"; +import React, { useEffect } from "react"; +import { useRouter } from "next/navigation"; +import { + IconDots, + IconDownload, + IconFilter, + IconLoader2, + IconSearch, +} from "@tabler/icons-react"; +import range from "lodash/range"; +import { useDebounce, useKey } from "react-use"; + +import { Badge } from "@ctrlplane/ui/badge"; +import { Button } from "@ctrlplane/ui/button"; +import { Skeleton } from "@ctrlplane/ui/skeleton"; +import { ColumnOperator } from "@ctrlplane/validators/conditions"; +import { + defaultCondition, + isEmptyCondition, +} from "@ctrlplane/validators/resources"; + +import { NoFilterMatch } from "~/app/[workspaceSlug]/(app)/_components/filter/NoFilterMatch"; +import { ResourceConditionBadge } from "~/app/[workspaceSlug]/(app)/_components/resource-condition/ResourceConditionBadge"; +import { + CreateResourceViewDialog, + ResourceConditionDialog, +} from "~/app/[workspaceSlug]/(app)/_components/resource-condition/ResourceConditionDialog"; +import { ResourceViewActionsDropdown } from "~/app/[workspaceSlug]/(app)/_components/resource-condition/ResourceViewActionsDropdown"; +import { useResourceFilter } from "~/app/[workspaceSlug]/(app)/_components/resource-condition/useResourceFilter"; +import { api } from "~/trpc/react"; +import { exportResources } from "./export-resources"; +import { ResourceGettingStarted } from "./ResourceGettingStarted"; +import { ResourcesTable } from "./ResourcesTable"; + +export const SearchInput: React.FC<{ + value: string; + onChange: (v: string) => void; +}> = ({ value, onChange }) => { + const [isExpanded, setIsExpanded] = React.useState(false); + const inputRef = React.useRef(null); + useKey("Escape", () => setIsExpanded(false)); + + useEffect(() => { + if (isExpanded) inputRef.current?.focus(); + }, [isExpanded]); + + return ( +
+ + + onChange(e.target.value)} + ref={inputRef} + type="text" + className={`bg-transparent outline-none transition-all duration-200 ${ + isExpanded ? "w-[150px] pl-1" : "w-0" + }`} + placeholder="Search..." + onBlur={() => setIsExpanded(false)} + onKeyDown={(e) => { + if (e.key === "Enter") setIsExpanded(false); + }} + /> +
+ ); +}; + +export const ResourcePageContent: React.FC<{ + workspace: schema.Workspace; + view: schema.ResourceView | null; +}> = ({ workspace, view }) => { + const [search, setSearch] = React.useState(""); + const { filter, setFilter } = useResourceFilter(); + + useDebounce( + () => { + if (search === "") return; + setFilter({ + type: "comparison", + operator: "and", + conditions: [ + // Keep any non-name conditions from existing filter + ...(filter && "conditions" in filter + ? filter.conditions.filter( + (c: ResourceCondition) => c.type !== "name", + ) + : []), + { + type: "name", + operator: ColumnOperator.Contains, + value: search, + }, + ], + }); + }, + 500, + [search], + ); + + const workspaceId = workspace.id; + const resourcesAll = api.resource.byWorkspaceId.list.useQuery({ + workspaceId, + limit: 0, + }); + const resources = api.resource.byWorkspaceId.list.useQuery( + { workspaceId, filter: filter ?? undefined, limit: 500 }, + { placeholderData: (prev) => prev }, + ); + + const onFilterChange = (condition: ResourceCondition | null) => { + const cond = condition ?? defaultCondition; + if (isEmptyCondition(cond)) setFilter(null); + if (!isEmptyCondition(cond)) setFilter(cond); + }; + + const router = useRouter(); + + if (resourcesAll.isSuccess && resourcesAll.data.total === 0) + return ; + + return ( +
+
+
+ + +
+ {view == null && ( + + )} + + {filter != null && view == null && ( + + )} + {view != null && ( + <> + {view.name} + + + + + )} +
+
+ {!resources.isLoading && resources.isFetching && ( + + )} +
+
+ {filter != null && view == null && ( + setFilter(v.filter, v.id)} + > + + + )} + {resources.data?.total != null && ( +
+ Total: + + {resources.data.total} + +
+ )} + {resources.data != null && ( + + )} +
+
+ + {resources.isLoading && ( +
+ {range(10).map((i) => ( + + ))} +
+ )} + {resources.isSuccess && resources.data.total === 0 && ( + setFilter(null)} + /> + )} + {resources.data != null && resources.data.total > 0 && ( +
+ + router.push(`/${workspace.slug}/resources/${r.id}`) + } + /> +
+ )} +
+ ); +}; diff --git a/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/list/ResourcesTable.tsx b/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/list/ResourcesTable.tsx new file mode 100644 index 000000000..f724ce58f --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/list/ResourcesTable.tsx @@ -0,0 +1,249 @@ +"use client"; + +import type { Resource } from "@ctrlplane/db/schema"; +import type { ColumnDef } from "@tanstack/react-table"; +import { IconLock, IconX } from "@tabler/icons-react"; +import { + flexRender, + getCoreRowModel, + useReactTable, +} from "@tanstack/react-table"; +import { formatDistanceToNowStrict } from "date-fns"; + +import { cn } from "@ctrlplane/ui"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from "@ctrlplane/ui/alert-dialog"; +import { Button, buttonVariants } from "@ctrlplane/ui/button"; +import { Checkbox } from "@ctrlplane/ui/checkbox"; +import { Separator } from "@ctrlplane/ui/separator"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@ctrlplane/ui/table"; + +import { ResourceIcon } from "~/app/[workspaceSlug]/(app)/_components/ResourceIcon"; +import { api } from "~/trpc/react"; + +const columns: ColumnDef[] = [ + { + id: "select", + header: ({ table }) => ( + table.toggleAllPageRowsSelected(!!value)} + aria-label="Select all" + className={cn( + "opacity-0 transition-opacity group-hover:opacity-100", + (table.getIsAllPageRowsSelected() || + table.getIsSomePageRowsSelected()) && + "opacity-100", + )} + /> + ), + cell: ({ row }) => ( + row.toggleSelected(!!value)} + onClick={(e) => e.stopPropagation()} + aria-label="Select row" + className={cn( + "opacity-0 transition-opacity group-hover:opacity-100", + row.getIsSelected() && "opacity-100", + )} + /> + ), + enableSorting: false, + enableHiding: false, + size: 10, + enableResizing: false, + }, + { + id: "name", + header: "Name", + accessorKey: "name", + cell: (info) => { + const isLocked = info.row.original.lockedAt != null; + return ( +
+ {isLocked && } + {!isLocked && ( + + )} + {info.getValue()} +
+ ); + }, + }, + { + id: "kind", + header: "Kind", + accessorKey: "kind", + cell: (info) => info.getValue(), + }, + { + id: "updatedAt", + header: "Last Sync", + accessorKey: "updatedAt", + cell: (info) => + info.getValue() != null + ? formatDistanceToNowStrict(info.getValue()) + : "", + }, +]; + +export const ResourcesTable: React.FC<{ + activeResourceIds?: string[]; + resources: Resource[]; + onTableRowClick?: (resource: Resource) => void; +}> = ({ resources, onTableRowClick, activeResourceIds }) => { + const deleteResourcesMutation = api.resource.delete.useMutation(); + + const table = useReactTable({ + data: resources, + columns, + defaultColumn: { + minSize: 0, + size: Number.MAX_SAFE_INTEGER, + maxSize: Number.MAX_SAFE_INTEGER, + }, + getCoreRowModel: getCoreRowModel(), + }); + + const utils = api.useUtils(); + const handleDeleteResources = async () => { + const selectedResources = table + .getSelectedRowModel() + .rows.map((row) => row.original.id); + await deleteResourcesMutation.mutateAsync(selectedResources); + await utils.resource.byWorkspaceId.invalidate(); + table.toggleAllRowsSelected(false); + }; + + return ( +
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext(), + )} + + ))} + + ))} + + + {table.getRowModel().rows.map((row) => ( + onTableRowClick?.(row.original)} + > + {row.getVisibleCells().map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + ))} + +
+ {table.getSelectedRowModel().rows.length > 0 && ( +
+
+ + + + + + + + + Are you sure? + + This action cannot be undone. This will permanently delete + the selected resources. + + + + Cancel + + Delete + + + + +
+
+ )} +
+ ); +}; diff --git a/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/list/export-resources.ts b/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/list/export-resources.ts new file mode 100644 index 000000000..14ee672ba --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/list/export-resources.ts @@ -0,0 +1,65 @@ +import type { RouterOutputs } from "@ctrlplane/api"; +import _ from "lodash"; + +type Resource = + RouterOutputs["resource"]["byWorkspaceId"]["list"]["items"][number]; + +const baseFields = [ + "id", + "resourceName", + "kind", + "identifier", + "version", + "provider", + "createdAt", + "updatedAt", +]; + +const resourceArrayToCsv = ( + resourceData: Record[], + metadataKeys: string[], +) => { + const headers = [...baseFields, ...metadataKeys]; + const headerRow = headers + .map((v) => v.replaceAll('"', '""')) + .map((v) => `"${v}"`) + .join(","); + + const dataRows = resourceData.map((row) => + headers + .map((key) => row[key] ?? "") + .map((v) => v.replaceAll('"', '""')) + .map((v) => `"${v}"`) + .join(","), + ); + + return [headerRow, ...dataRows].join("\r\n"); +}; + +export const exportResources = (resources: Resource[]) => { + const metadataKeys = Array.from( + new Set(resources.flatMap((r) => Object.keys(r.metadata))), + ); + const rows: Record[] = resources.map((resource) => ({ + id: resource.id, + resourceName: resource.name, + kind: resource.kind, + identifier: resource.identifier, + version: resource.version, + provider: resource.provider?.name ?? "", + createdAt: resource.createdAt.toISOString(), + updatedAt: resource.updatedAt?.toISOString() ?? "", + ..._.zipObject( + metadataKeys, + metadataKeys.map((key) => resource.metadata[key] ?? ""), + ), + })); + + const csv = resourceArrayToCsv(rows, metadataKeys); + + const blob = new Blob([csv], { type: "text/csv" }); + const url = URL.createObjectURL(blob); + const link = Object.assign(document.createElement("a"), { href: url }); + link.setAttribute("download", "resources.csv"); + link.click(); +}; diff --git a/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/list/layout.tsx b/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/list/layout.tsx new file mode 100644 index 000000000..6ffa73123 --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/list/layout.tsx @@ -0,0 +1,73 @@ +import { notFound } from "next/navigation"; +import { + IconList, + IconPlug, + IconTable, + IconView360Arrow, +} from "@tabler/icons-react"; + +import { + Sidebar, + SidebarContent, + SidebarGroup, + SidebarInset, + SidebarMenu, + SidebarProvider, +} from "@ctrlplane/ui/sidebar"; + +import { api } from "~/trpc/server"; +import { SidebarGroupKinds } from "../SidebarKinds"; +import { SidebarLink } from "../SidebarLink"; + +export default async function Layout(props: { + children: React.ReactNode; + params: Promise<{ workspaceSlug: string }>; +}) { + const params = await props.params; + const workspace = await api.workspace.bySlug(params.workspaceSlug); + if (workspace == null) notFound(); + + return ( +
+ + + + + + } + href={`/${workspace.slug}/resources/list`} + > + List + + } + href={`/${workspace.slug}/resources/providers`} + > + Providers + + } + href={`/${workspace.slug}/resources/groupings`} + > + Groupings + + } + href={`/${workspace.slug}/resources/views`} + > + Views + + + + + + + + + {props.children} + + +
+ ); +} diff --git a/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/list/page.tsx b/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/list/page.tsx new file mode 100644 index 000000000..6cb72383c --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/list/page.tsx @@ -0,0 +1,46 @@ +import { notFound } from "next/navigation"; + +import { + Breadcrumb, + BreadcrumbItem, + BreadcrumbList, + BreadcrumbPage, +} from "@ctrlplane/ui/breadcrumb"; +import { Separator } from "@ctrlplane/ui/separator"; +import { SidebarTrigger } from "@ctrlplane/ui/sidebar"; + +import { api } from "~/trpc/server"; +import { PageHeader } from "../../_components/PageHeader"; +import { ResourcePageContent } from "./ResourcePageContent"; + +export default async function ResourcesPage(props: { + params: Promise<{ workspaceSlug: string }>; + searchParams: Promise<{ view?: string }>; +}) { + const searchParams = await props.searchParams; + const params = await props.params; + const workspace = await api.workspace.bySlug(params.workspaceSlug); + if (workspace == null) notFound(); + + const view = + searchParams.view != null + ? await api.resource.view.byId(searchParams.view) + : null; + + return ( +
+ + + + + + + Resources + + + + + +
+ ); +} diff --git a/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/page.tsx b/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/page.tsx new file mode 100644 index 000000000..16eec1853 --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(appv2)/resources/page.tsx @@ -0,0 +1,8 @@ +import { redirect } from "next/navigation"; + +export default async function ResourcesPage(props: { + params: Promise<{ workspaceSlug: string }>; +}) { + const params = await props.params; + redirect(`/${params.workspaceSlug}/resources/list`); +} diff --git a/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/SystemSelector.tsx b/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/SystemSelector.tsx new file mode 100644 index 000000000..af99e6531 --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/SystemSelector.tsx @@ -0,0 +1,71 @@ +"use client"; + +import type { System } from "@ctrlplane/db/schema"; +import React, { useState } from "react"; +import { useRouter } from "next/navigation"; +import { IconCheck, IconChevronDown } from "@tabler/icons-react"; + +import { cn } from "@ctrlplane/ui"; +import { Button } from "@ctrlplane/ui/button"; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from "@ctrlplane/ui/command"; +import { Popover, PopoverContent, PopoverTrigger } from "@ctrlplane/ui/popover"; + +export const SystemSelector: React.FC<{ + workspaceSlug: string; + selectedSystem: System | null; + systems: System[]; +}> = ({ workspaceSlug, selectedSystem, systems }) => { + const router = useRouter(); + const [open, setOpen] = useState(false); + return ( + + + + + + + + + No Systems found. + + {systems.map((system) => ( + { + router.push(`/${workspaceSlug}/systems2/${system.slug}`); + router.refresh(); + }} + > + + {system.name} + + ))} + + + + + + ); +}; diff --git a/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/[systemSlug]/(raw)/deployments/[deploymentSlug]/layout.tsx b/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/[systemSlug]/(raw)/deployments/[deploymentSlug]/layout.tsx new file mode 100644 index 000000000..b4f9b595d --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/[systemSlug]/(raw)/deployments/[deploymentSlug]/layout.tsx @@ -0,0 +1,68 @@ +import Link from "next/link"; +import { notFound } from "next/navigation"; +import { IconArrowLeft } from "@tabler/icons-react"; + +import { + Breadcrumb, + BreadcrumbItem, + BreadcrumbList, + BreadcrumbPage, +} from "@ctrlplane/ui/breadcrumb"; +import { Separator } from "@ctrlplane/ui/separator"; + +import { + TabLink, + Tabs, + TabsList, +} from "~/app/[workspaceSlug]/(appv2)/_components/navigation/Tabs"; +import { PageHeader } from "~/app/[workspaceSlug]/(appv2)/_components/PageHeader"; +import { api } from "~/trpc/server"; + +export default async function DeploymentLayout(props: { + children: React.ReactNode; + params: Promise<{ + workspaceSlug: string; + systemSlug: string; + deploymentSlug: string; + }>; +}) { + const params = await props.params; + const deployment = await api.deployment.bySlug(params); + if (deployment == null) notFound(); + + return ( +
+ +
+ + + + + + + + Environments List + + + +
+
+
+

{deployment.name}

+ + + + Deployment Targets + + Deployments + Policies + Variables + + + {props.children} +
+
+ ); +} diff --git a/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/[systemSlug]/(raw)/deployments/[deploymentSlug]/page.tsx b/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/[systemSlug]/(raw)/deployments/[deploymentSlug]/page.tsx new file mode 100644 index 000000000..5a24b9038 --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/[systemSlug]/(raw)/deployments/[deploymentSlug]/page.tsx @@ -0,0 +1,3 @@ +export default function DeploymentPage() { + return
Deployment
; +} diff --git a/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/[systemSlug]/(raw)/environments/[environmentId]/config/page.tsx b/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/[systemSlug]/(raw)/environments/[environmentId]/config/page.tsx new file mode 100644 index 000000000..edef19628 --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/[systemSlug]/(raw)/environments/[environmentId]/config/page.tsx @@ -0,0 +1,50 @@ +export default function EnvironmentConfigPage() { + return ( +
+
+
+ + + + +
Rollout & Timing
+
+
+
+

+ Approval & Governance +

+
+
+

+ Deployment Controls +

+
+
+

+ Release Management +

+
+
+

+ Release Channels +

+
+
+

+ Rollout & Timing +

+
+
+
+
+ ); +} diff --git a/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/[systemSlug]/(raw)/environments/[environmentId]/layout.tsx b/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/[systemSlug]/(raw)/environments/[environmentId]/layout.tsx new file mode 100644 index 000000000..5aa88149f --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/[systemSlug]/(raw)/environments/[environmentId]/layout.tsx @@ -0,0 +1,69 @@ +import Link from "next/link"; +import { notFound } from "next/navigation"; +import { IconArrowLeft } from "@tabler/icons-react"; + +import { + Breadcrumb, + BreadcrumbItem, + BreadcrumbList, + BreadcrumbPage, +} from "@ctrlplane/ui/breadcrumb"; +import { Separator } from "@ctrlplane/ui/separator"; + +import { + TabLink, + Tabs, + TabsList, +} from "~/app/[workspaceSlug]/(appv2)/_components/navigation/Tabs"; +import { PageHeader } from "~/app/[workspaceSlug]/(appv2)/_components/PageHeader"; +import { api } from "~/trpc/server"; + +export default async function EnvironmentLayout(props: { + children: React.ReactNode; + params: Promise<{ + workspaceSlug: string; + systemSlug: string; + environmentId: string; + }>; +}) { + const params = await props.params; + const environment = await api.environment.byId(params.environmentId); + if (environment == null) notFound(); + + const url = (tab: string) => + `/${params.workspaceSlug}/systems/${params.systemSlug}/environments/${params.environmentId}/${tab}`; + return ( +
+ +
+ + + + + + + + Environments List + + + +
+
+
+

{environment.name}

+ + + Targets + Configuration + Deployments + Policies + Variables + + + {props.children} +
+
+ ); +} diff --git a/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/[systemSlug]/(raw)/environments/[environmentId]/page.tsx b/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/[systemSlug]/(raw)/environments/[environmentId]/page.tsx new file mode 100644 index 000000000..4cda5bf1d --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/[systemSlug]/(raw)/environments/[environmentId]/page.tsx @@ -0,0 +1,3 @@ +export default function EnvironmentPage() { + return
Environment
; +} diff --git a/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/[systemSlug]/(sidebar)/deployments/page.tsx b/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/[systemSlug]/(sidebar)/deployments/page.tsx new file mode 100644 index 000000000..a835bcd5f --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/[systemSlug]/(sidebar)/deployments/page.tsx @@ -0,0 +1,87 @@ +import Link from "next/link"; +import { notFound } from "next/navigation"; +import { IconDots } from "@tabler/icons-react"; + +import { + Breadcrumb, + BreadcrumbItem, + BreadcrumbList, + BreadcrumbPage, +} from "@ctrlplane/ui/breadcrumb"; +import { Button } from "@ctrlplane/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@ctrlplane/ui/dropdown-menu"; +import { Separator } from "@ctrlplane/ui/separator"; +import { SidebarTrigger } from "@ctrlplane/ui/sidebar"; + +import { api } from "~/trpc/server"; +import { PageHeader } from "../../../../_components/PageHeader"; + +export default async function EnvironmentsPage(props: { + params: Promise<{ workspaceSlug: string; systemSlug: string }>; +}) { + const params = await props.params; + const system = await api.system.bySlug(params).catch(() => null); + if (system == null) notFound(); + + const environments = await api.environment.bySystemId(system.id); + + return ( +
+ + + + + + + Deployments + + + + + + {environments.map((environment) => ( +
+
{environment.name}
+
+
+
+ 1/5 Healthy +
+
+
+
Latest: v1.0.0
+
+
+ + + + + + Add Resources + + + Edit + + + + + Delete + + + +
+
+ ))} +
+ ); +} diff --git a/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/[systemSlug]/(sidebar)/environments/page.tsx b/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/[systemSlug]/(sidebar)/environments/page.tsx new file mode 100644 index 000000000..9b86da501 --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/[systemSlug]/(sidebar)/environments/page.tsx @@ -0,0 +1,87 @@ +import Link from "next/link"; +import { notFound } from "next/navigation"; +import { IconDots } from "@tabler/icons-react"; + +import { + Breadcrumb, + BreadcrumbItem, + BreadcrumbList, + BreadcrumbPage, +} from "@ctrlplane/ui/breadcrumb"; +import { Button } from "@ctrlplane/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@ctrlplane/ui/dropdown-menu"; +import { Separator } from "@ctrlplane/ui/separator"; +import { SidebarTrigger } from "@ctrlplane/ui/sidebar"; + +import { api } from "~/trpc/server"; +import { PageHeader } from "../../../../_components/PageHeader"; + +export default async function EnvironmentsPage(props: { + params: Promise<{ workspaceSlug: string; systemSlug: string }>; +}) { + const params = await props.params; + const system = await api.system.bySlug(params).catch(() => null); + if (system == null) notFound(); + + const environments = await api.environment.bySystemId(system.id); + + return ( +
+ + + + + + + Environments + + + + + + {environments.map((environment) => ( +
+
{environment.name}
+
+
+
+ 1/5 Healthy +
+
+
+
Latest: v1.0.0
+
+
+ + + + + + Add Resources + + + Edit + + + + + Delete + + + +
+
+ ))} +
+ ); +} diff --git a/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/[systemSlug]/(sidebar)/layout.tsx b/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/[systemSlug]/(sidebar)/layout.tsx new file mode 100644 index 000000000..796abbe06 --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/[systemSlug]/(sidebar)/layout.tsx @@ -0,0 +1,111 @@ +import { notFound } from "next/navigation"; +import { + IconBook, + IconPlant, + IconSettings, + IconShield, + IconShip, + IconVariable, +} from "@tabler/icons-react"; + +import { + Sidebar, + SidebarContent, + SidebarGroup, + SidebarGroupLabel, + SidebarHeader, + SidebarInset, + SidebarMenu, + SidebarProvider, +} from "@ctrlplane/ui/sidebar"; + +import { api } from "~/trpc/server"; +import { SidebarLink } from "../../../resources/SidebarLink"; +import { SystemSelector } from "../../SystemSelector"; + +export default async function SystemsLayout(props: { + children: React.ReactNode; + params: Promise<{ workspaceSlug: string; systemSlug: string }>; +}) { + const params = await props.params; + const workspace = await api.workspace.bySlug(params.workspaceSlug); + if (workspace == null) notFound(); + + const systems = await api.system.list({ workspaceId: workspace.id }); + const selectedSystem = systems.items.find( + (system) => system.slug === params.systemSlug, + ); + + return ( +
+ + + + + + + + Release Management + + } + href={`/${workspace.slug}/systems/${params.systemSlug}/environments`} + > + Environments + + } + href={`/${workspace.slug}/systems/${params.systemSlug}/deployments`} + > + Deployments + + } + href={`/${workspace.slug}/systems/${params.systemSlug}/variables`} + > + Variables + + } + href={`/${workspace.slug}/systems/${params.systemSlug}/policies`} + > + Policies + + + + + Operations + + } + href={`/${workspace.slug}/systems/${params.systemSlug}/runbooks`} + > + Runbooks + + + + + + System + + } + href={`/${workspace.slug}/systems/${params.systemSlug}/settings`} + > + Settings + + + + + + + {props.children} + + +
+ ); +} diff --git a/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/[systemSlug]/(sidebar)/page.tsx b/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/[systemSlug]/(sidebar)/page.tsx new file mode 100644 index 000000000..d9672a5a6 --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/[systemSlug]/(sidebar)/page.tsx @@ -0,0 +1,13 @@ +import { notFound } from "next/navigation"; + +import { api } from "~/trpc/server"; + +export default async function SystemsPage(props: { + params: Promise<{ workspaceSlug: string }>; +}) { + const params = await props.params; + const workspace = await api.workspace.bySlug(params.workspaceSlug); + if (workspace == null) notFound(); + + return <>; +} diff --git a/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/[systemSlug]/(sidebar)/runbooks/page.tsx b/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/[systemSlug]/(sidebar)/runbooks/page.tsx new file mode 100644 index 000000000..63a9fef18 --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/[systemSlug]/(sidebar)/runbooks/page.tsx @@ -0,0 +1,28 @@ +import { + Breadcrumb, + BreadcrumbItem, + BreadcrumbList, + BreadcrumbPage, +} from "@ctrlplane/ui/breadcrumb"; +import { Separator } from "@ctrlplane/ui/separator"; +import { SidebarTrigger } from "@ctrlplane/ui/sidebar"; + +import { PageHeader } from "~/app/[workspaceSlug]/(appv2)/_components/PageHeader"; + +export default function RunbooksPage() { + return ( +
+ + + + + + + Runbooks + + + + +
+ ); +} diff --git a/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/[systemSlug]/(sidebar)/variables/page.tsx b/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/[systemSlug]/(sidebar)/variables/page.tsx new file mode 100644 index 000000000..9eed566bb --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/[systemSlug]/(sidebar)/variables/page.tsx @@ -0,0 +1,28 @@ +import { + Breadcrumb, + BreadcrumbItem, + BreadcrumbList, + BreadcrumbPage, +} from "@ctrlplane/ui/breadcrumb"; +import { Separator } from "@ctrlplane/ui/separator"; +import { SidebarTrigger } from "@ctrlplane/ui/sidebar"; + +import { PageHeader } from "~/app/[workspaceSlug]/(appv2)/_components/PageHeader"; + +export default function VariablesPage() { + return ( +
+ + + + + + + Variables + + + + +
+ ); +} diff --git a/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/page.tsx b/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/page.tsx new file mode 100644 index 000000000..d894b660d --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(appv2)/systems/page.tsx @@ -0,0 +1,21 @@ +import { notFound, redirect } from "next/navigation"; + +import { api } from "~/trpc/server"; + +export default async function SystemsPage(props: { + params: Promise<{ workspaceSlug: string }>; +}) { + const params = await props.params; + const workspace = await api.workspace.bySlug(params.workspaceSlug); + if (workspace == null) notFound(); + + const systems = await api.system.list({ + workspaceId: workspace.id, + limit: 1, + }); + const [firstSystem] = systems.items; + if (firstSystem == null) notFound(); + const system = firstSystem; + + return redirect(`/${workspace.slug}/systems/${system.slug}`); +} diff --git a/apps/webservice/src/app/globals.css b/apps/webservice/src/app/globals.css index bd42add2f..cbac91966 100644 --- a/apps/webservice/src/app/globals.css +++ b/apps/webservice/src/app/globals.css @@ -42,7 +42,7 @@ } .dark { - --background: 240 10% 3.9%; + --background: 0 0% 3.9%; --foreground: 0 0% 98%; --card: 240 10% 3.9%; --card-foreground: 0 0% 98%; @@ -68,14 +68,14 @@ --chart-4: 280 65% 60%; --chart-5: 340 75% 55%; - --sidebar-background: 0 0% 0%; - --sidebar-foreground: 240 4.8% 95.9%; - --sidebar-primary: 224.3 76.3% 48%; - --sidebar-primary-foreground: 0 0% 100%; - --sidebar-accent: 240 4% 10%; - --sidebar-accent-foreground: 240 4.8% 95.9%; - --sidebar-border: 240 3.7% 15.9%; - --sidebar-ring: 217.2 91.2% 59.8%; + --sidebar-background: var(--background); + --sidebar-foreground: var(--foreground); + --sidebar-primary: var(--primary); + --sidebar-primary-foreground: var(--primary-foreground); + --sidebar-accent: var(--accent); + --sidebar-accent-foreground: var(--accent-foreground); + --sidebar-border: var(--border); + --sidebar-ring: var(--ring); } } diff --git a/docker-compose.dev.yaml b/docker-compose.dev.yaml index e02c0e414..fa237aeed 100644 --- a/docker-compose.dev.yaml +++ b/docker-compose.dev.yaml @@ -1,5 +1,3 @@ -version: "3.8" - services: database: container_name: ctrlplane-database diff --git a/docker-compose.yaml b/docker-compose.yaml index 5e45e2ed6..17884252f 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,5 +1,3 @@ -version: "3.9" - services: postgres: platform: linux/amd64 diff --git a/packages/api/src/router/deployment-stats.ts b/packages/api/src/router/deployment-stats.ts index 5f9a9e9ed..bcb19f918 100644 --- a/packages/api/src/router/deployment-stats.ts +++ b/packages/api/src/router/deployment-stats.ts @@ -39,6 +39,7 @@ export const deploymentStatsRouter = createTRPCRouter({ .input( z.object({ workspaceId: z.string().uuid(), + resourceId: z.string().uuid().optional(), startDate: z.date(), endDate: z.date(), timezone: z.string(), @@ -48,7 +49,15 @@ export const deploymentStatsRouter = createTRPCRouter({ }), ) .query(async ({ ctx, input }) => { - const { workspaceId, startDate, endDate, orderBy, order, search } = input; + const { + workspaceId, + resourceId, + startDate, + endDate, + orderBy, + order, + search, + } = input; const orderFunc = (field: unknown) => order === StatsOrder.Asc ? sql`${field} ASC NULLS LAST` @@ -147,6 +156,7 @@ export const deploymentStatsRouter = createTRPCRouter({ lte(schema.job.createdAt, endDate), isNull(schema.resource.deletedAt), search ? ilike(schema.deployment.name, `%${search}%`) : undefined, + resourceId ? eq(schema.resource.id, resourceId) : undefined, ), ) .orderBy(orderFunc(getOrderBy())) @@ -168,10 +178,11 @@ export const deploymentStatsRouter = createTRPCRouter({ z.object({ deploymentId: z.string().uuid(), timeZone: z.string(), + resourceId: z.string().uuid().optional(), }), ) .query(async ({ ctx, input }) => { - const { deploymentId, timeZone } = input; + const { deploymentId, timeZone, resourceId } = input; const endDate = new Date(); const startDate = subDays(new Date(), 29); const dates = eachDayOfInterval({ start: startDate, end: endDate }).map( @@ -203,6 +214,9 @@ export const deploymentStatsRouter = createTRPCRouter({ inArray(schema.job.status, analyticsStatuses), gte(schema.job.completedAt, startDate), lt(schema.job.completedAt, endDate), + resourceId + ? eq(schema.releaseJobTrigger.resourceId, resourceId) + : undefined, ), ) .as("base"); diff --git a/packages/ui/src/sidebar.tsx b/packages/ui/src/sidebar.tsx index b9b7c0de3..4467e9ca2 100644 --- a/packages/ui/src/sidebar.tsx +++ b/packages/ui/src/sidebar.tsx @@ -150,7 +150,7 @@ const SidebarProvider = React.forwardRef< } as React.CSSProperties } className={cn( - "group/sidebar-wrapper has-[[data-variant=inset]]:bg-sidebar flex min-h-svh w-full", + "group/sidebar-wrapper has-[[data-variant=inset]]:bg-sidebar flex w-full", className, )} ref={ref} @@ -233,7 +233,7 @@ const Sidebar = React.forwardRef< {/* This is what handles the sidebar gap on desktop */}