forked from github-starred/komodo
do stuff
This commit is contained in:
@@ -20,7 +20,9 @@ import { Fragment, ReactNode, SetStateAction, useState } from "react";
|
||||
const keys = <T extends Record<string, unknown>>(obj: T) =>
|
||||
Object.keys(obj) as Array<keyof T>;
|
||||
|
||||
export const ConfigLayout = <T extends Types.Resource<unknown, unknown>["config"]>({
|
||||
export const ConfigLayout = <
|
||||
T extends Types.Resource<unknown, unknown>["config"]
|
||||
>({
|
||||
config,
|
||||
children,
|
||||
onConfirm,
|
||||
@@ -46,10 +48,12 @@ export const ConfigLayout = <T extends Types.Resource<unknown, unknown>["config"
|
||||
>
|
||||
<History className="w-4 h-4" />
|
||||
</Button>
|
||||
{Object.keys(config).length ? <ConfirmUpdate
|
||||
content={JSON.stringify(config, null, 2)}
|
||||
onConfirm={onConfirm}
|
||||
/> : null}
|
||||
{Object.keys(config).length ? (
|
||||
<ConfirmUpdate
|
||||
content={JSON.stringify(config, null, 2)}
|
||||
onConfirm={onConfirm}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
@@ -200,4 +204,4 @@ export const ConfigAgain = <
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
SearchX,
|
||||
} from "lucide-react";
|
||||
import { ReactNode, useState } from "react";
|
||||
import { cn } from "@lib/utils";
|
||||
import { cn, snake_case_to_upper_space_case } from "@lib/utils";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -52,7 +52,7 @@ export const ConfigItem = ({
|
||||
className
|
||||
)}
|
||||
>
|
||||
<div className="capitalize"> {label} </div>
|
||||
<div className="capitalize"> {snake_case_to_upper_space_case(label)} </div>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
@@ -295,7 +295,7 @@ export const InputList = <T extends { [key: string]: unknown }>({
|
||||
// intent="success"
|
||||
onClick={() => set({ [field]: [...values, ""] } as Partial<T>)}
|
||||
>
|
||||
Add Docker Account
|
||||
Add {snake_case_to_upper_space_case(field as string).slice(0, -1)}
|
||||
</Button>
|
||||
</div>
|
||||
</ConfigItem>
|
||||
|
||||
@@ -94,7 +94,7 @@ export const Page = ({
|
||||
);
|
||||
|
||||
interface SectionProps {
|
||||
title: string;
|
||||
title: ReactNode;
|
||||
children: ReactNode;
|
||||
icon?: ReactNode;
|
||||
actions?: ReactNode;
|
||||
@@ -129,7 +129,7 @@ export const NewResource = ({
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={set}>
|
||||
<DialogTrigger asChild>
|
||||
<Button className="items-center gap-2">
|
||||
<Button variant="secondary" className="items-center gap-2">
|
||||
New {type} <PlusCircle className="w-4 h-4" />
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
|
||||
@@ -15,6 +15,9 @@ import { useNavigate } from "react-router-dom";
|
||||
import { ResourceComponents } from "./resources";
|
||||
import { UsableResource } from "@types";
|
||||
import { RESOURCE_TARGETS } from "@lib/utils";
|
||||
import { DeploymentComponents } from "./resources/deployment";
|
||||
import { BuildComponents } from "./resources/build";
|
||||
import { ServerComponents } from "./resources/server";
|
||||
|
||||
const ResourceGroup = ({
|
||||
type,
|
||||
@@ -26,6 +29,8 @@ const ResourceGroup = ({
|
||||
const data = useRead(`List${type}s`, {}).data;
|
||||
const Components = ResourceComponents[type];
|
||||
|
||||
if (!data || !data.length) return
|
||||
|
||||
return (
|
||||
<CommandGroup heading={`${type}s`}>
|
||||
{data?.map(({ id }) => {
|
||||
@@ -54,9 +59,10 @@ export const Omnibar = () => {
|
||||
|
||||
useEffect(() => {
|
||||
const down = (e: KeyboardEvent) => {
|
||||
console.log(e);
|
||||
// This will ignore Shift + S if it is sent from input / textarea
|
||||
const target = e.target as any;
|
||||
if (target.matches("input") || target.matches("textarea")) return;
|
||||
|
||||
if (e.shiftKey && e.key === "S") {
|
||||
e.preventDefault();
|
||||
set(true);
|
||||
@@ -90,8 +96,31 @@ export const Omnibar = () => {
|
||||
<Home className="w-4 h-4" />
|
||||
Home
|
||||
</CommandItem>
|
||||
<CommandItem
|
||||
className="flex items-center gap-2"
|
||||
onSelect={() => nav("/deployments")}
|
||||
>
|
||||
<DeploymentComponents.Icon />
|
||||
Deployments
|
||||
</CommandItem>
|
||||
<CommandItem
|
||||
className="flex items-center gap-2"
|
||||
onSelect={() => nav("/builds")}
|
||||
>
|
||||
<BuildComponents.Icon />
|
||||
Builds
|
||||
</CommandItem>
|
||||
<CommandItem
|
||||
className="flex items-center gap-2"
|
||||
onSelect={() => nav("/servers")}
|
||||
>
|
||||
<ServerComponents.Icon />
|
||||
Servers
|
||||
</CommandItem>
|
||||
</CommandGroup>
|
||||
|
||||
<CommandSeparator />
|
||||
|
||||
{RESOURCE_TARGETS.map((rt) => (
|
||||
<Fragment key={rt}>
|
||||
<ResourceGroup type={rt} key={rt} onSelect={nav} />
|
||||
|
||||
@@ -17,7 +17,6 @@ import { Config } from "@components/config";
|
||||
import { DataTable } from "@ui/data-table";
|
||||
import { ResourceComponents } from "..";
|
||||
import { Link } from "react-router-dom";
|
||||
import { fmt_date_with_minutes } from "@lib/utils";
|
||||
import { Card, CardDescription, CardHeader, CardTitle } from "@ui/card";
|
||||
|
||||
const useAlerter = (id?: string) =>
|
||||
@@ -131,11 +130,6 @@ const AlerterTable = () => {
|
||||
},
|
||||
},
|
||||
{ header: "Tags", accessorFn: ({ tags }) => tags.join(", ") },
|
||||
{
|
||||
header: "Created",
|
||||
accessorFn: ({ created_at }) =>
|
||||
fmt_date_with_minutes(new Date(created_at)),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
105
frontend/src/components/resources/build/config.tsx
Normal file
105
frontend/src/components/resources/build/config.tsx
Normal file
@@ -0,0 +1,105 @@
|
||||
import { Config } from "@components/config";
|
||||
import {
|
||||
AccountSelector,
|
||||
ConfigItem,
|
||||
ResourceSelector,
|
||||
} from "@components/config/util";
|
||||
import { useRead, useWrite } from "@lib/hooks";
|
||||
import { env_to_text, text_to_env } from "@lib/utils";
|
||||
import { Types } from "@monitor/client";
|
||||
import { Textarea } from "@ui/textarea";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export const BuildConfig = ({ id }: { id: string }) => {
|
||||
const config = useRead("GetBuild", { build: id }).data?.config;
|
||||
// const orgs = useRead("GetAccounts")
|
||||
const [update, set] = useState<Partial<Types.BuildConfig>>({});
|
||||
const { mutate } = useWrite("UpdateBuild");
|
||||
|
||||
if (!config) return null;
|
||||
|
||||
return (
|
||||
<Config
|
||||
config={config}
|
||||
update={update}
|
||||
set={set}
|
||||
onSave={() => mutate({ id, config: update })}
|
||||
components={{
|
||||
general: {
|
||||
general: {
|
||||
builder_id: (id, set) => (
|
||||
<div className="flex justify-between items-center border-b pb-4 min-h-[60px]">
|
||||
<div>Builder</div>
|
||||
<ResourceSelector
|
||||
type="Builder"
|
||||
selected={id}
|
||||
onSelect={(builder_id) => set({ builder_id })}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
git: {
|
||||
repo: true,
|
||||
branch: true,
|
||||
github_account: (account, set) => (
|
||||
<AccountSelector
|
||||
id={update.builder_id ?? config.builder_id ?? undefined}
|
||||
type="Builder"
|
||||
account_type="github"
|
||||
selected={account}
|
||||
onSelect={(github_account) => set({ github_account })}
|
||||
/>
|
||||
),
|
||||
},
|
||||
docker: {
|
||||
build_path: true,
|
||||
dockerfile_path: true,
|
||||
docker_account: (account, set) => (
|
||||
<AccountSelector
|
||||
id={update.builder_id ?? config.builder_id ?? undefined}
|
||||
type="Builder"
|
||||
account_type="docker"
|
||||
selected={account}
|
||||
onSelect={(docker_account) => set({ docker_account })}
|
||||
/>
|
||||
),
|
||||
use_buildx: true,
|
||||
// docker_organization,
|
||||
},
|
||||
},
|
||||
"Build Args": {
|
||||
"Build Args": {
|
||||
build_args: (vars, set) => (
|
||||
<BuildArgs vars={vars ?? []} set={set} />
|
||||
),
|
||||
skip_secret_interp: true,
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const BuildArgs = ({
|
||||
vars,
|
||||
set,
|
||||
}: {
|
||||
vars: Types.EnvironmentVar[];
|
||||
set: (input: Partial<Types.BuildConfig>) => void;
|
||||
}) => {
|
||||
const [args, setArgs] = useState(env_to_text(vars));
|
||||
useEffect(() => {
|
||||
!!args && set({ build_args: text_to_env(args) });
|
||||
}, [args, set]);
|
||||
|
||||
return (
|
||||
<ConfigItem label="Build Args" className="flex-col gap-4 items-start">
|
||||
<Textarea
|
||||
className="min-h-[300px]"
|
||||
placeholder="VARIABLE=value"
|
||||
value={args}
|
||||
onChange={(e) => setArgs(e.target.value)}
|
||||
/>
|
||||
</ConfigItem>
|
||||
);
|
||||
};
|
||||
@@ -1,30 +1,18 @@
|
||||
import { Config } from "@components/config";
|
||||
import {
|
||||
ResourceSelector,
|
||||
AccountSelector,
|
||||
ConfigItem,
|
||||
} from "@components/config/util";
|
||||
import { NewResource } from "@components/layouts";
|
||||
import { ConfirmButton } from "@components/util";
|
||||
import { useExecute, useRead, useWrite } from "@lib/hooks";
|
||||
import {
|
||||
env_to_text,
|
||||
fmt_date_with_minutes,
|
||||
fmt_version,
|
||||
text_to_env,
|
||||
} from "@lib/utils";
|
||||
import { Types } from "@monitor/client";
|
||||
import { fmt_date_with_minutes, fmt_version } from "@lib/utils";
|
||||
import { RequiredResourceComponents } from "@types";
|
||||
import { DataTable } from "@ui/data-table";
|
||||
import { Input } from "@ui/input";
|
||||
import { Ban, Hammer, History, Loader2 } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { ResourceComponents } from "..";
|
||||
import { BuildChart } from "@components/dashboard/builds-chart";
|
||||
import { TagsWithBadge, useTagsFilter } from "@components/tags";
|
||||
import { Textarea } from "@ui/textarea";
|
||||
import { useToast } from "@ui/use-toast";
|
||||
import { BuildConfig } from "./config";
|
||||
|
||||
const useBuild = (id?: string) =>
|
||||
useRead("ListBuilds", {}).data?.find((d) => d.id === id);
|
||||
@@ -50,100 +38,6 @@ const NewBuild = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export const BuildArgs = ({
|
||||
vars,
|
||||
set,
|
||||
}: {
|
||||
vars: Types.EnvironmentVar[];
|
||||
set: (input: Partial<Types.BuildConfig>) => void;
|
||||
}) => {
|
||||
const [args, setArgs] = useState(env_to_text(vars));
|
||||
useEffect(() => {
|
||||
!!args && set({ build_args: text_to_env(args) });
|
||||
}, [args, set]);
|
||||
|
||||
return (
|
||||
<ConfigItem label="Build Args" className="flex-col gap-4 items-start">
|
||||
<Textarea
|
||||
className="min-h-[300px]"
|
||||
placeholder="VARIABLE=value"
|
||||
value={args}
|
||||
onChange={(e) => setArgs(e.target.value)}
|
||||
/>
|
||||
</ConfigItem>
|
||||
);
|
||||
};
|
||||
|
||||
export const BuildConfig = ({ id }: { id: string }) => {
|
||||
const config = useRead("GetBuild", { build: id }).data?.config;
|
||||
// const orgs = useRead("GetAccounts")
|
||||
const [update, set] = useState<Partial<Types.BuildConfig>>({});
|
||||
const { mutate } = useWrite("UpdateBuild");
|
||||
|
||||
if (!config) return null;
|
||||
|
||||
return (
|
||||
<Config
|
||||
config={config}
|
||||
update={update}
|
||||
set={set}
|
||||
onSave={() => mutate({ id, config: update })}
|
||||
components={{
|
||||
general: {
|
||||
general: {
|
||||
builder_id: (id, set) => (
|
||||
<div className="flex justify-between items-center border-b pb-4 min-h-[60px]">
|
||||
<div>Builder</div>
|
||||
<ResourceSelector
|
||||
type="Builder"
|
||||
selected={id}
|
||||
onSelect={(builder_id) => set({ builder_id })}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
git: {
|
||||
repo: true,
|
||||
branch: true,
|
||||
github_account: (account, set) => (
|
||||
<AccountSelector
|
||||
id={update.builder_id ?? config.builder_id ?? undefined}
|
||||
type="Builder"
|
||||
account_type="github"
|
||||
selected={account}
|
||||
onSelect={(github_account) => set({ github_account })}
|
||||
/>
|
||||
),
|
||||
},
|
||||
docker: {
|
||||
build_path: true,
|
||||
dockerfile_path: true,
|
||||
docker_account: (account, set) => (
|
||||
<AccountSelector
|
||||
id={update.builder_id ?? config.builder_id ?? undefined}
|
||||
type="Builder"
|
||||
account_type="docker"
|
||||
selected={account}
|
||||
onSelect={(docker_account) => set({ docker_account })}
|
||||
/>
|
||||
),
|
||||
use_buildx: true,
|
||||
// docker_organization,
|
||||
},
|
||||
},
|
||||
"Build Args": {
|
||||
"Build Args": {
|
||||
build_args: (vars, set) => (
|
||||
<BuildArgs vars={vars ?? []} set={set} />
|
||||
),
|
||||
skip_secret_interp: true,
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const Name = ({ id }: { id: string }) => <>{useBuild(id)?.name}</>;
|
||||
|
||||
const Icon = ({ id }: { id: string }) => {
|
||||
@@ -182,17 +76,6 @@ const BuildTable = () => {
|
||||
header: "Version",
|
||||
accessorFn: ({ info }) => fmt_version(info.version),
|
||||
},
|
||||
// {
|
||||
// header: "Deployments",
|
||||
// cell: ({ row }) => {
|
||||
// const deps = useRead("ListDeployments", {
|
||||
// query: { specific: { build_ids: [row.original.id] } },
|
||||
// })?.data?.map((d) => (
|
||||
// <Link to={`/deployments/${d.id}`}>{d.name}</Link>
|
||||
// ));
|
||||
// return <div className="flex items-center gap-2">{deps}</div>;
|
||||
// },
|
||||
// },
|
||||
{
|
||||
header: "Last Built",
|
||||
accessorFn: ({ info: { last_built_at } }) => {
|
||||
@@ -213,11 +96,6 @@ const BuildTable = () => {
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
header: "Created",
|
||||
accessorFn: ({ created_at }) =>
|
||||
fmt_date_with_minutes(new Date(created_at)),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
@@ -244,7 +122,8 @@ export const BuildComponents: RequiredResourceComponents = {
|
||||
},
|
||||
Actions: ({ id }) => {
|
||||
const { toast } = useToast();
|
||||
const building = useRead("GetBuildActionState", { build: id }).data?.building;
|
||||
const building = useRead("GetBuildActionState", { build: id }).data
|
||||
?.building;
|
||||
const { mutate: run_mutate, isPending: runPending } = useExecute(
|
||||
"RunBuild",
|
||||
{
|
||||
|
||||
90
frontend/src/components/resources/builder/config.tsx
Normal file
90
frontend/src/components/resources/builder/config.tsx
Normal file
@@ -0,0 +1,90 @@
|
||||
import { Config } from "@components/config";
|
||||
import { InputList, ResourceSelector } from "@components/config/util";
|
||||
import { useRead, useWrite } from "@lib/hooks";
|
||||
import { Types } from "@monitor/client";
|
||||
import { useState } from "react";
|
||||
|
||||
export const BuilderConfig = ({ id }: { id: string }) => {
|
||||
const config = useRead("GetBuilder", { builder: id }).data?.config;
|
||||
if (config?.type === "Aws") return <AwsBuilderConfig id={id} />;
|
||||
if (config?.type === "Server") return <ServerBuilderConfig id={id} />;
|
||||
};
|
||||
|
||||
const AwsBuilderConfig = ({ id }: { id: string }) => {
|
||||
const config = useRead("GetBuilder", { builder: id }).data?.config;
|
||||
const [update, set] = useState<Partial<Types.AwsBuilderConfig>>({});
|
||||
const { mutate } = useWrite("UpdateBuilder");
|
||||
if (!config) return null;
|
||||
|
||||
return (
|
||||
<Config
|
||||
config={config.params as Types.AwsBuilderConfig}
|
||||
update={update}
|
||||
set={set}
|
||||
onSave={() => mutate({ id, config: { type: "Aws", params: update } })}
|
||||
components={{
|
||||
general: {
|
||||
general: {
|
||||
region: true,
|
||||
instance_type: true,
|
||||
volume_gb: true,
|
||||
ami_id: true,
|
||||
subnet_id: true,
|
||||
key_pair_name: true,
|
||||
assign_public_ip: true,
|
||||
use_public_ip: true,
|
||||
security_group_ids: (values, set) => (
|
||||
<InputList field="security_group_ids" values={values} set={set} />
|
||||
),
|
||||
docker_accounts: (accounts, set) => (
|
||||
<InputList
|
||||
field="docker_accounts"
|
||||
values={accounts ?? []}
|
||||
set={set}
|
||||
/>
|
||||
),
|
||||
github_accounts: (accounts, set) => (
|
||||
<InputList
|
||||
field="github_accounts"
|
||||
values={accounts ?? []}
|
||||
set={set}
|
||||
/>
|
||||
),
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const ServerBuilderConfig = ({ id }: { id: string }) => {
|
||||
const config = useRead("GetBuilder", { builder: id }).data?.config;
|
||||
const [update, set] = useState<Partial<Types.ServerBuilderConfig>>({});
|
||||
const { mutate } = useWrite("UpdateBuilder");
|
||||
if (!config) return null;
|
||||
|
||||
return (
|
||||
<Config
|
||||
config={config.params as Types.ServerBuilderConfig}
|
||||
update={update}
|
||||
set={set}
|
||||
onSave={() => mutate({ id, config: { type: "Server", params: update } })}
|
||||
components={{
|
||||
general: {
|
||||
general: {
|
||||
id: (id, set) => (
|
||||
<div className="flex justify-between items-center border-b pb-4">
|
||||
Select Server
|
||||
<ResourceSelector
|
||||
type="Server"
|
||||
selected={id}
|
||||
onSelect={(id) => set({ id })}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -1,9 +1,6 @@
|
||||
import { Config } from "@components/config";
|
||||
import { InputList, ResourceSelector } from "@components/config/util";
|
||||
import { NewResource } from "@components/layouts";
|
||||
import { useTagsFilter } from "@components/tags";
|
||||
import { useRead, useWrite } from "@lib/hooks";
|
||||
import { fmt_date_with_minutes } from "@lib/utils";
|
||||
import { Types } from "@monitor/client";
|
||||
import { RequiredResourceComponents } from "@types";
|
||||
import { Card, CardDescription, CardHeader, CardTitle } from "@ui/card";
|
||||
@@ -20,89 +17,11 @@ import {
|
||||
import { Cloud, Bot, Factory } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { BuilderConfig } from "./config";
|
||||
|
||||
const useBuilder = (id?: string) =>
|
||||
useRead("ListBuilders", {}).data?.find((d) => d.id === id);
|
||||
|
||||
const AwsBuilderConfig = ({ id }: { id: string }) => {
|
||||
const config = useRead("GetBuilder", { builder: id }).data?.config;
|
||||
const [update, set] = useState<Partial<Types.AwsBuilderConfig>>({});
|
||||
const { mutate } = useWrite("UpdateBuilder");
|
||||
if (!config) return null;
|
||||
|
||||
return (
|
||||
<Config
|
||||
config={config.params as Types.AwsBuilderConfig}
|
||||
update={update}
|
||||
set={set}
|
||||
onSave={() => mutate({ id, config: { type: "Aws", params: update } })}
|
||||
components={{
|
||||
general: {
|
||||
general: {
|
||||
region: true,
|
||||
instance_type: true,
|
||||
volume_gb: true,
|
||||
ami_id: true,
|
||||
subnet_id: true,
|
||||
key_pair_name: true,
|
||||
assign_public_ip: true,
|
||||
use_public_ip: true,
|
||||
security_group_ids: (values, set) => (
|
||||
<InputList field="security_group_ids" values={values} set={set} />
|
||||
),
|
||||
docker_accounts: (accounts, set) => (
|
||||
<InputList
|
||||
field="docker_accounts"
|
||||
values={accounts ?? []}
|
||||
set={set}
|
||||
/>
|
||||
),
|
||||
github_accounts: (accounts, set) => (
|
||||
<InputList
|
||||
field="github_accounts"
|
||||
values={accounts ?? []}
|
||||
set={set}
|
||||
/>
|
||||
),
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const ServerBuilderConfig = ({ id }: { id: string }) => {
|
||||
const config = useRead("GetBuilder", { builder: id }).data?.config;
|
||||
const [update, set] = useState<Partial<Types.ServerBuilderConfig>>({});
|
||||
const { mutate } = useWrite("UpdateBuilder");
|
||||
if (!config) return null;
|
||||
|
||||
return (
|
||||
<Config
|
||||
config={config.params as Types.ServerBuilderConfig}
|
||||
update={update}
|
||||
set={set}
|
||||
onSave={() => mutate({ id, config: { type: "Server", params: update } })}
|
||||
components={{
|
||||
general: {
|
||||
general: {
|
||||
id: (id, set) => (
|
||||
<div className="flex justify-between items-center border-b pb-4">
|
||||
Select Server
|
||||
<ResourceSelector
|
||||
type="Server"
|
||||
selected={id}
|
||||
onSelect={(id) => set({ id })}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const NewBuilder = () => {
|
||||
const { mutateAsync } = useWrite("CreateBuilder");
|
||||
const [name, setName] = useState("");
|
||||
@@ -196,11 +115,6 @@ const BuilderTable = () => {
|
||||
accessorKey: "info.instance_type",
|
||||
},
|
||||
{ header: "Tags", accessorFn: ({ tags }) => tags.join(", ") },
|
||||
{
|
||||
header: "Created",
|
||||
accessorFn: ({ created_at }) =>
|
||||
fmt_date_with_minutes(new Date(created_at)),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
@@ -223,11 +137,7 @@ export const BuilderComponents: RequiredResourceComponents = {
|
||||
),
|
||||
Icon: () => <Factory className="w-4 h-4" />,
|
||||
Page: {
|
||||
Config: ({ id }) => {
|
||||
const config = useRead("GetBuilder", { builder: id }).data?.config;
|
||||
if (config?.type === "Aws") return <AwsBuilderConfig id={id} />;
|
||||
if (config?.type === "Server") return <ServerBuilderConfig id={id} />;
|
||||
},
|
||||
Config: BuilderConfig,
|
||||
},
|
||||
Table: BuilderTable,
|
||||
Actions: () => null,
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useRead, useWrite } from "@lib/hooks";
|
||||
import { Types } from "@monitor/client";
|
||||
import { RequiredResourceComponents } from "@types";
|
||||
import { AlertTriangle, HardDrive, Rocket, Server } from "lucide-react";
|
||||
import { cn, fmt_date_with_minutes } from "@lib/utils";
|
||||
import { cn } from "@lib/utils";
|
||||
import { useState } from "react";
|
||||
import { NewResource, Section } from "@components/layouts";
|
||||
|
||||
@@ -109,11 +109,6 @@ export const DeploymentTable = ({
|
||||
);
|
||||
},
|
||||
},
|
||||
// {
|
||||
// header: "Description",
|
||||
// accessorKey: "description",
|
||||
// },
|
||||
|
||||
{
|
||||
header: "Server",
|
||||
cell: ({ row }) => {
|
||||
@@ -163,11 +158,6 @@ export const DeploymentTable = ({
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
header: "Created",
|
||||
accessorFn: ({ created_at }) =>
|
||||
fmt_date_with_minutes(new Date(created_at)),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -10,11 +10,11 @@ import { ProcedureComponents } from "./procedure/index";
|
||||
export const ResourceComponents: {
|
||||
[key in UsableResource]: RequiredResourceComponents;
|
||||
} = {
|
||||
Alerter: AlerterComponents,
|
||||
Build: BuildComponents,
|
||||
Builder: BuilderComponents,
|
||||
Deployment: DeploymentComponents,
|
||||
Repo: RepoComponents,
|
||||
Server: ServerComponents,
|
||||
Build: BuildComponents,
|
||||
Repo: RepoComponents,
|
||||
Procedure: ProcedureComponents,
|
||||
Builder: BuilderComponents,
|
||||
Alerter: AlerterComponents,
|
||||
};
|
||||
|
||||
@@ -2,7 +2,6 @@ import { NewResource } from "@components/layouts";
|
||||
import { TagsWithBadge } from "@components/tags";
|
||||
import { ConfirmButton } from "@components/util";
|
||||
import { useExecute, useRead, useWrite } from "@lib/hooks";
|
||||
import { fmt_date_with_minutes } from "@lib/utils";
|
||||
import { Types } from "@monitor/client";
|
||||
import { RequiredResourceComponents } from "@types";
|
||||
import { Card, CardDescription, CardHeader, CardTitle } from "@ui/card";
|
||||
@@ -72,6 +71,10 @@ export const ProcedureComponents: RequiredResourceComponents = {
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
header: "Type",
|
||||
accessorKey: "info.procedure_type",
|
||||
},
|
||||
{
|
||||
header: "Tags",
|
||||
cell: ({ row }) => {
|
||||
@@ -82,11 +85,6 @@ export const ProcedureComponents: RequiredResourceComponents = {
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
header: "Created",
|
||||
accessorFn: ({ created_at }) =>
|
||||
fmt_date_with_minutes(new Date(created_at)),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Config } from "@components/config";
|
||||
import { AccountSelector, ResourceSelector } from "@components/config/util";
|
||||
import { TagsWithBadge } from "@components/tags";
|
||||
import { useRead, useWrite } from "@lib/hooks";
|
||||
import { Types } from "@monitor/client";
|
||||
import { Icon } from "@radix-ui/react-select";
|
||||
@@ -98,7 +99,16 @@ export const RepoComponents: RequiredResourceComponents = {
|
||||
);
|
||||
},
|
||||
},
|
||||
{ header: "Tags", accessorFn: ({ tags }) => tags.join(", ") },
|
||||
{
|
||||
header: "Tags",
|
||||
cell: ({ row }) => {
|
||||
return (
|
||||
<div className="flex gap-1">
|
||||
<TagsWithBadge tag_ids={row.original.tags} />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useInvalidate, useRead, useWrite } from "@lib/hooks";
|
||||
import { cn, fmt_date_with_minutes } from "@lib/utils";
|
||||
import { cn } from "@lib/utils";
|
||||
import { Types } from "@monitor/client";
|
||||
import { RequiredResourceComponents } from "@types";
|
||||
import {
|
||||
@@ -205,11 +205,6 @@ const ServerTable = () => {
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
header: "Created",
|
||||
accessorFn: ({ created_at }) =>
|
||||
fmt_date_with_minutes(new Date(created_at)),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
@@ -224,7 +219,9 @@ export const ServerComponents: RequiredResourceComponents = {
|
||||
Page: {
|
||||
Stats: ({ id }) => <ServerStats server_id={id} />,
|
||||
Deployments: ({ id }) => {
|
||||
const deployments = useRead("ListDeployments", {}).data?.filter(deployment => deployment.info.server_id === id);
|
||||
const deployments = useRead("ListDeployments", {}).data?.filter(
|
||||
(deployment) => deployment.info.server_id === id
|
||||
);
|
||||
return (
|
||||
<Section title="Deployments" icon={<Rocket className="w-4 h-4" />}>
|
||||
<DeploymentTable deployments={deployments} />
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
import { Button } from "../ui/button";
|
||||
import {
|
||||
Box,
|
||||
Boxes,
|
||||
Check,
|
||||
Copy,
|
||||
FolderTree,
|
||||
@@ -45,6 +46,7 @@ import {
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@ui/dropdown-menu";
|
||||
import { ResourceComponents } from "./resources";
|
||||
@@ -357,12 +359,24 @@ export const ResourceTypeDropdown = () => {
|
||||
Dashboard
|
||||
</DropdownMenuItem>
|
||||
</Link>
|
||||
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
<Link to="/resources">
|
||||
<DropdownMenuItem className="flex items-center gap-2">
|
||||
<Boxes className="w-4 h-4" />
|
||||
Resources
|
||||
</DropdownMenuItem>
|
||||
</Link>
|
||||
<Link to="/tree">
|
||||
<DropdownMenuItem className="flex items-center gap-2">
|
||||
<FolderTree className="w-4 h-4" />
|
||||
Tree
|
||||
</DropdownMenuItem>
|
||||
</Link>
|
||||
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
{RESOURCE_TARGETS.map((rt) => {
|
||||
const RTIcon = ResourceComponents[rt].Icon;
|
||||
return (
|
||||
@@ -374,12 +388,18 @@ export const ResourceTypeDropdown = () => {
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
<Link to="/tags">
|
||||
<DropdownMenuItem className="flex items-center gap-2">
|
||||
<Tag className="w-4 h-4" />
|
||||
Tags
|
||||
</DropdownMenuItem>
|
||||
</Link>
|
||||
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
<Link to="/keys">
|
||||
<DropdownMenuItem className="flex items-center gap-2">
|
||||
<Box className="w-4 h-4" />
|
||||
|
||||
@@ -11,23 +11,25 @@ export const keys = <T extends object>(o: T): (keyof T)[] =>
|
||||
Object.keys(o) as (keyof T)[];
|
||||
|
||||
export const RESOURCE_TARGETS: UsableResource[] = [
|
||||
"Alerter",
|
||||
"Build",
|
||||
"Builder",
|
||||
"Deployment",
|
||||
"Repo",
|
||||
"Server",
|
||||
"Procedure",
|
||||
"Deployment",
|
||||
"Server",
|
||||
"Build",
|
||||
"Repo",
|
||||
"Builder",
|
||||
"Alerter",
|
||||
];
|
||||
|
||||
export const fmt_date = (d: Date) => {
|
||||
return `${d.getDate()}/${d.getMonth() + 1} @ ${d.getHours()}:${d.getMinutes()}`;
|
||||
return `${d.getDate()}/${
|
||||
d.getMonth() + 1
|
||||
} @ ${d.getHours()}:${d.getMinutes()}`;
|
||||
};
|
||||
|
||||
export const fmt_date_with_minutes = (d: Date) => {
|
||||
// return `${d.toLocaleDateString()} ${d.toLocaleTimeString()}`;
|
||||
return d.toLocaleString()
|
||||
}
|
||||
return d.toLocaleString();
|
||||
};
|
||||
|
||||
export const fmt_version = (version: Types.Version | undefined) => {
|
||||
if (!version) return "...";
|
||||
@@ -80,3 +82,11 @@ export function text_to_env(env: string): Types.EnvironmentVar[] {
|
||||
})
|
||||
.map(([variable, value]) => ({ variable, value }));
|
||||
}
|
||||
|
||||
/// list_all_items => List All Items
|
||||
export function snake_case_to_upper_space_case(snake: string) {
|
||||
return snake
|
||||
.split("_")
|
||||
.map((item) => item[0].toUpperCase() + item.slice(1))
|
||||
.join(" ");
|
||||
}
|
||||
|
||||
47
frontend/src/pages/all_resources.tsx
Normal file
47
frontend/src/pages/all_resources.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import { Page, Section } from "@components/layouts";
|
||||
import { ResourceComponents } from "@components/resources";
|
||||
import { TagsFilter, useTagsFilter } from "@components/tags";
|
||||
import { useRead } from "@lib/hooks";
|
||||
import { UsableResource } from "@types";
|
||||
import { Input } from "@ui/input";
|
||||
import { useState } from "react";
|
||||
|
||||
export const AllResources = () => {
|
||||
const [search, setSearch] = useState("");
|
||||
const tags = useTagsFilter();
|
||||
return (
|
||||
<Page
|
||||
title="Resources"
|
||||
actions={
|
||||
<div className="grid gap-4 justify-items-end">
|
||||
<div className="flex gap-4">
|
||||
<Input
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
placeholder="search..."
|
||||
className="w-96"
|
||||
/>
|
||||
</div>
|
||||
<TagsFilter />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
{Object.entries(ResourceComponents).map(([type, Components]) => {
|
||||
const count = useRead(
|
||||
`List${type as UsableResource}s`,
|
||||
{}
|
||||
).data?.filter((resource) =>
|
||||
tags.every((tag) => resource.tags.includes(tag))
|
||||
).length;
|
||||
|
||||
if (!count) return;
|
||||
|
||||
return (
|
||||
<Section title={type + "s"} actions={<Components.New />}>
|
||||
<Components.Table />
|
||||
</Section>
|
||||
);
|
||||
})}
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
@@ -10,6 +10,7 @@ import { Tree } from "@pages/tree";
|
||||
import { Tags } from "@pages/tags";
|
||||
import { ResourceUpdates } from "@pages/resource_update";
|
||||
import { UsersPage } from "@pages/users";
|
||||
import { AllResources } from "@pages/all_resources";
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
@@ -17,16 +18,17 @@ const router = createBrowserRouter([
|
||||
element: <Layout />,
|
||||
children: [
|
||||
{ path: "", element: <Dashboard /> },
|
||||
{ path: "tree", element: <Tree /> },
|
||||
{ path: "users", element: <UsersPage /> },
|
||||
{ path: "keys", element: <Keys /> },
|
||||
{ path: "tags", element: <Tags /> },
|
||||
{ path: "users", element: <UsersPage /> },
|
||||
{ path: "tree", element: <Tree /> },
|
||||
{ path: "resources", element: <AllResources /> },
|
||||
{
|
||||
path: ":type",
|
||||
children: [
|
||||
{ path: "", element: <Resources /> },
|
||||
{ path: ":id", element: <Resource /> },
|
||||
{ path: ":id/updates", element: <ResourceUpdates /> }
|
||||
{ path: ":id/updates", element: <ResourceUpdates /> },
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user