diff --git a/apps/webservice/src/app/[workspaceSlug]/(targets)/targets/TargetsTable.tsx b/apps/webservice/src/app/[workspaceSlug]/(targets)/targets/TargetsTable.tsx index 89dd1de9c..f62ebc3f8 100644 --- a/apps/webservice/src/app/[workspaceSlug]/(targets)/targets/TargetsTable.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/(targets)/targets/TargetsTable.tsx @@ -2,8 +2,7 @@ import type { Target } from "@ctrlplane/db/schema"; import type { ColumnDef } from "@tanstack/react-table"; -import { SiKubernetes, SiTerraform } from "@icons-pack/react-simple-icons"; -import { IconLock, IconServer, IconTarget, IconX } from "@tabler/icons-react"; +import { IconLock, IconX } from "@tabler/icons-react"; import { flexRender, getCoreRowModel, @@ -35,6 +34,7 @@ import { TableRow, } from "@ctrlplane/ui/table"; +import { TargetIcon } from "~/app/[workspaceSlug]/_components/TargetIcon"; import { api } from "~/trpc/react"; const columns: ColumnDef[] = [ @@ -78,24 +78,15 @@ const columns: ColumnDef[] = [ header: "Name", accessorKey: "name", cell: (info) => { - const includes = (key: string) => info.row.original.version.includes(key); - const isKube = includes("kubernetes"); - const isVm = includes("vm") || includes("compute"); const isLocked = info.row.original.lockedAt != null; - const isTerraform = includes("terraform"); - return (
- {isLocked ? ( - - ) : isKube ? ( - - ) : isVm ? ( - - ) : isTerraform ? ( - - ) : ( - + {isLocked && } + {!isLocked && ( + )} {info.getValue()}
diff --git a/apps/webservice/src/app/[workspaceSlug]/_components/EnvironmentDrawer.tsx b/apps/webservice/src/app/[workspaceSlug]/_components/EnvironmentDrawer.tsx index 802bb4896..6ebb1072e 100644 --- a/apps/webservice/src/app/[workspaceSlug]/_components/EnvironmentDrawer.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/_components/EnvironmentDrawer.tsx @@ -5,14 +5,11 @@ import type { TargetCondition } from "@ctrlplane/validators/targets"; import React, { useState } from "react"; import Link from "next/link"; import { useParams, useRouter, useSearchParams } from "next/navigation"; -import { SiKubernetes, SiTerraform } from "@icons-pack/react-simple-icons"; import { IconExternalLink, IconLoader2, IconPlant, IconSelector, - IconServer, - IconTarget, } from "@tabler/icons-react"; import * as LZString from "lz-string"; import { z } from "zod"; @@ -58,6 +55,7 @@ import { import { api } from "~/trpc/react"; import { TargetConditionRender } from "./target-condition/TargetConditionRender"; +import { TargetIcon } from "./TargetIcon"; const DeleteEnvironmentDialog: React.FC<{ environment: schema.Environment; @@ -236,16 +234,6 @@ const TargetViewsCombobox: React.FC<{ ); }; -const TargetIcon: React.FC<{ version: string }> = ({ version }) => { - if (version.includes("kubernetes")) - return ; - if (version.includes("vm") || version.includes("compute")) - return ; - if (version.includes("terraform")) - return ; - return ; -}; - const filterForm = z.object({ targetFilter: targetCondition.optional(), }); @@ -353,7 +341,7 @@ const EditFilterForm: React.FC<{
{targets.data.items.map((target) => (
- +
{target.name} diff --git a/apps/webservice/src/app/[workspaceSlug]/_components/TargetIcon.tsx b/apps/webservice/src/app/[workspaceSlug]/_components/TargetIcon.tsx new file mode 100644 index 000000000..bf47f14c1 --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/_components/TargetIcon.tsx @@ -0,0 +1,18 @@ +import { SiKubernetes, SiTerraform } from "@icons-pack/react-simple-icons"; +import { IconServer, IconTarget, IconUsersGroup } from "@tabler/icons-react"; + +export const TargetIcon: React.FC<{ version: string; kind?: string }> = ({ + version, + kind, +}) => { + if (kind?.toLowerCase().includes("shared")) + return ; + if (version.includes("kubernetes")) + return ; + if (version.includes("vm") || version.includes("compute")) + return ; + if (version.includes("terraform")) + return ; + + return ; +}; diff --git a/apps/webservice/src/app/[workspaceSlug]/_components/target-drawer/relationships/RelationshipsDiagram.tsx b/apps/webservice/src/app/[workspaceSlug]/_components/target-drawer/relationships/RelationshipsDiagram.tsx index 3df63e9a5..f998d39de 100644 --- a/apps/webservice/src/app/[workspaceSlug]/_components/target-drawer/relationships/RelationshipsDiagram.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/_components/target-drawer/relationships/RelationshipsDiagram.tsx @@ -8,8 +8,6 @@ import type { ReactFlowInstance, } from "reactflow"; import { useCallback, useEffect, useState } from "react"; -import { SiKubernetes, SiTerraform } from "@icons-pack/react-simple-icons"; -import { IconTarget } from "@tabler/icons-react"; import ReactFlow, { BaseEdge, EdgeLabelRenderer, @@ -27,6 +25,7 @@ import colors from "tailwindcss/colors"; import { cn } from "@ctrlplane/ui"; import { getLayoutedElementsDagre } from "~/app/[workspaceSlug]/_components/reactflow/layout"; +import { TargetIcon } from "~/app/[workspaceSlug]/_components/TargetIcon"; import { api } from "~/trpc/react"; type TargetNodeProps = NodeProps<{ @@ -53,13 +52,7 @@ const TargetNode: React.FC = (node) => { )} >
- {isKubernetes ? ( - - ) : isTerraform ? ( - - ) : ( - - )} +
{data.kind} diff --git a/apps/webservice/src/app/[workspaceSlug]/systems/[systemSlug]/deployments/[deploymentSlug]/variables/VariableTable.tsx b/apps/webservice/src/app/[workspaceSlug]/systems/[systemSlug]/deployments/[deploymentSlug]/variables/VariableTable.tsx index 31e55f788..c15edcf66 100644 --- a/apps/webservice/src/app/[workspaceSlug]/systems/[systemSlug]/deployments/[deploymentSlug]/variables/VariableTable.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/systems/[systemSlug]/deployments/[deploymentSlug]/variables/VariableTable.tsx @@ -3,13 +3,7 @@ import { useState } from "react"; import Link from "next/link"; import { useParams } from "next/navigation"; -import { SiKubernetes, SiTerraform } from "@icons-pack/react-simple-icons"; -import { - IconChevronRight, - IconDotsVertical, - IconServer, - IconTarget, -} from "@tabler/icons-react"; +import { IconChevronRight, IconDotsVertical } from "@tabler/icons-react"; import { cn } from "@ctrlplane/ui"; import { Badge } from "@ctrlplane/ui/badge"; @@ -24,20 +18,11 @@ import { Table, TableBody, TableCell, TableRow } from "@ctrlplane/ui/table"; import type { VariableData } from "./variable-data"; import { useTargetDrawer } from "~/app/[workspaceSlug]/_components/target-drawer/TargetDrawer"; +import { TargetIcon } from "~/app/[workspaceSlug]/_components/TargetIcon"; import { useMatchSorterWithSearch } from "~/utils/useMatchSorter"; import { VariableDropdown } from "./VariableDropdown"; import { VariableValueDropdown } from "./VariableValueDropdown"; -const TargetIcon: React.FC<{ version: string }> = ({ version }) => { - if (version.includes("kubernetes")) - return ; - if (version.includes("vm") || version.includes("compute")) - return ; - if (version.includes("terraform")) - return ; - return ; -}; - export const VariableTable: React.FC<{ variables: VariableData[]; }> = ({ variables }) => { @@ -249,7 +234,10 @@ export const VariableTable: React.FC<{ >
- +
{t.name} diff --git a/apps/webservice/src/app/api/v1/workspaces/[workspaceId]/targets/route.ts b/apps/webservice/src/app/api/v1/workspaces/[workspaceId]/targets/route.ts index fdf0515e3..74130227a 100644 --- a/apps/webservice/src/app/api/v1/workspaces/[workspaceId]/targets/route.ts +++ b/apps/webservice/src/app/api/v1/workspaces/[workspaceId]/targets/route.ts @@ -9,7 +9,7 @@ import { createTarget } from "@ctrlplane/db/schema"; import { upsertTargets } from "@ctrlplane/job-dispatch"; import { Permission } from "@ctrlplane/validators/auth"; -import { getUser } from "../../../auth"; +import { getUser } from "~/app/api/v1/auth"; const bodySchema = z.array( createTarget @@ -23,20 +23,22 @@ const canUpsertTarget = async (userId: string, workspaceId: string) => .perform(Permission.TargetCreate) .on({ type: "workspace", id: workspaceId }); -const parseBody = (rawBody: string): object => { +const parseJson = (rawBody: string): object => { try { return JSON.parse(rawBody); - } catch { - try { - const yamlResult = yaml.load(rawBody); - if (typeof yamlResult === "object" && yamlResult !== null) { - return yamlResult; - } - } catch { - // YAML parsing failed - } + } catch (e) { + throw new Error("Invalid input: not valid JSON", { cause: e }); + } +}; + +const parseYaml = (rawBody: string) => { + try { + const targets: unknown[] = []; + yaml.loadAll(rawBody, (obj) => targets.push(obj)); + return targets; + } catch (e) { + throw new Error("Invalid input: not valid YAML", { cause: e }); } - throw new Error("Invalid input: not valid JSON or YAML"); }; export const PATCH = async ( @@ -53,8 +55,14 @@ export const PATCH = async ( return NextResponse.json({ error: "Permission denied" }, { status: 403 }); const rawBody = await req.text(); - const parsedBody = parseBody(rawBody); + const contentType = req.headers.get("content-type"); + const parsedBody = + contentType === "application/x-yaml" || contentType === "application/yaml" + ? parseYaml(rawBody) + : parseJson(rawBody); + const parsedTargets = bodySchema.parse(parsedBody); + if (parsedTargets.length === 0) return NextResponse.json({ count: 0 }); const targets = await upsertTargets( db, diff --git a/packages/api/src/router/target.ts b/packages/api/src/router/target.ts index 350fa2a8e..ac13851c1 100644 --- a/packages/api/src/router/target.ts +++ b/packages/api/src/router/target.ts @@ -250,9 +250,10 @@ const targetQuery = (db: Tx, checks: Array>) => target: schema.target, targetProvider: schema.targetProvider, workspace: schema.workspace, - targetMetadata: - sql<_StringStringRecord>`jsonb_object_agg(target_metadata.key, - target_metadata.value)`.as("target_metadata"), + targetMetadata: sql<_StringStringRecord>` + jsonb_object_agg(target_metadata.key, target_metadata.value) + FILTER (WHERE target_metadata.key IS NOT NULL) + `.as("target_metadata"), }) .from(schema.target) .leftJoin( diff --git a/packages/job-dispatch/src/target.ts b/packages/job-dispatch/src/target.ts index f5667db32..0aa53c70b 100644 --- a/packages/job-dispatch/src/target.ts +++ b/packages/job-dispatch/src/target.ts @@ -135,17 +135,18 @@ export const upsertTargets = async ( ), ); - await tx - .insert(targetMetadata) - .values(targetMetadataValues) - .onConflictDoUpdate({ - target: [targetMetadata.targetId, targetMetadata.key], - set: buildConflictUpdateColumns(targetMetadata, ["value"]), - }) - .catch((err) => { - log.error("Error inserting target metadata", { error: err }); - throw err; - }); + if (targetMetadataValues.length > 0) + await tx + .insert(targetMetadata) + .values(targetMetadataValues) + .onConflictDoUpdate({ + target: [targetMetadata.targetId, targetMetadata.key], + set: buildConflictUpdateColumns(targetMetadata, ["value"]), + }) + .catch((err) => { + log.error("Error inserting target metadata", { error: err }); + throw err; + }); await tx .delete(targetMetadata) diff --git a/packages/storage/src/Storage.ts b/packages/storage/src/Storage.ts index 17c99affb..f6c63c9ca 100644 --- a/packages/storage/src/Storage.ts +++ b/packages/storage/src/Storage.ts @@ -1,7 +1,7 @@ import type { Readable } from "node:stream"; import { z } from "zod"; -import type { StorageFile } from "./StorageFile"; +// import type { StorageFile } from "./StorageFile.js"; export const FileMetadata = z.object({ name: z.string(), @@ -43,7 +43,7 @@ export interface StorageDriver { put(key: string, value: string): Promise; putStream(key: string, value: Readable): Promise; - list(prefix: string): Promise; + // list(prefix: string): Promise; getMetaData(key: string): Promise; getSignedUrl(path: string, opts: SignedURLOptions): Promise; diff --git a/packages/storage/src/StorageManager.ts b/packages/storage/src/StorageManager.ts index 096bf0a70..e69de29bb 100644 --- a/packages/storage/src/StorageManager.ts +++ b/packages/storage/src/StorageManager.ts @@ -1,9 +0,0 @@ -import type { Storage } from "@google-cloud/storage"; - -export class StorageManager { - constructor() {} - - use(name: string): Storage { - return null; - } -} diff --git a/packages/storage/src/drivers/fs/driver.ts b/packages/storage/src/drivers/fs/driver.ts index 93944288b..73f3cb506 100644 --- a/packages/storage/src/drivers/fs/driver.ts +++ b/packages/storage/src/drivers/fs/driver.ts @@ -10,7 +10,8 @@ import type { SignedURLOptions, StorageDriver, } from "../../Storage.js"; -import type { StorageFile } from "../../StorageFile.js"; + +// import type { StorageFile } from "../../StorageFile.js"; type FSDriverOptions = { location?: string; @@ -75,9 +76,9 @@ export class FSDriver implements StorageDriver { await fs.writeFile(location, value); } - list(_: string): Promise { - throw new Error("Method not implemented."); - } + // list(_: string): Promise { + // throw new Error("Method not implemented."); + // } getSignedUrl(key: string, opts: SignedURLOptions): Promise { if (this.options.generateSignedURL == null) diff --git a/packages/storage/src/drivers/gcs/driver.ts b/packages/storage/src/drivers/gcs/driver.ts index 2a915d6b4..fca45ad06 100644 --- a/packages/storage/src/drivers/gcs/driver.ts +++ b/packages/storage/src/drivers/gcs/driver.ts @@ -8,7 +8,8 @@ import type { SignedURLOptions, StorageDriver, } from "../../Storage.js"; -import type { StorageFile } from "../../StorageFile.js"; + +// import type { StorageFile } from "../../StorageFile.js"; type GCSDriverBaseOptions = { bucket: string; @@ -66,9 +67,9 @@ export class GCSStorageDriver implements StorageDriver { }); } - list(_: string): Promise { - throw new Error("Not implemented"); - } + // list(_: string): Promise { + // throw new Error("Not implemented"); + // } async getMetaData(key: string): Promise { const [metadata] = await this.bucket.file(key).getMetadata();