forked from github-starred/komodo
service user detection
This commit is contained in:
@@ -142,14 +142,17 @@ pub async fn get_tag_check_owner(
|
||||
async fn update_list_item(
|
||||
update: Update,
|
||||
) -> anyhow::Result<UpdateListItem> {
|
||||
let username =
|
||||
let username = if User::is_service_user(&update.operator) {
|
||||
update.operator.clone()
|
||||
} else {
|
||||
find_one_by_id(&db_client().await.users, &update.operator)
|
||||
.await
|
||||
.context("failed to query mongo for user")?
|
||||
.with_context(|| {
|
||||
format!("no user found with id {}", update.operator)
|
||||
})?
|
||||
.username;
|
||||
.username
|
||||
};
|
||||
let update = UpdateListItem {
|
||||
id: update.id,
|
||||
operation: update.operation,
|
||||
|
||||
@@ -110,10 +110,13 @@ pub trait StateResource {
|
||||
permission_level: PermissionLevel,
|
||||
) -> anyhow::Result<Resource<Self::Config, Self::Info>> {
|
||||
let resource = Self::get_resource(id_or_name).await?;
|
||||
if user.admin {
|
||||
return Ok(resource);
|
||||
}
|
||||
let permissions =
|
||||
Self::get_user_permission_on_resource(&user.id, &resource.id)
|
||||
.await?;
|
||||
if user.admin || permissions >= permission_level {
|
||||
if permissions >= permission_level {
|
||||
Ok(resource)
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
|
||||
@@ -62,6 +62,10 @@ impl User {
|
||||
self.config = UserConfig::default();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_service_user(user_id: &str) -> bool {
|
||||
matches!(user_id, "Procedure" | "Github")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn admin_service_user(user_id: &str) -> Option<User> {
|
||||
|
||||
@@ -9,12 +9,14 @@ export const ApiKeysSummary = () => {
|
||||
return (
|
||||
<Link to="/keys" className="w-full">
|
||||
<Card>
|
||||
<CardHeader className="justify-between">
|
||||
<div>
|
||||
<CardTitle>Api Keys</CardTitle>
|
||||
<CardDescription>{keys_count} Total</CardDescription>
|
||||
<CardHeader>
|
||||
<div className="flex justify-between">
|
||||
<div>
|
||||
<CardTitle>Api Keys</CardTitle>
|
||||
<CardDescription>{keys_count} Total</CardDescription>
|
||||
</div>
|
||||
<Key className="w-4 h-4" />
|
||||
</div>
|
||||
<Key className="w-4 h-4" />
|
||||
</CardHeader>
|
||||
</Card>
|
||||
</Link>
|
||||
|
||||
@@ -9,12 +9,14 @@ export const TagsSummary = () => {
|
||||
return (
|
||||
<Link to="/tags" className="w-full">
|
||||
<Card>
|
||||
<CardHeader className="justify-between">
|
||||
<div>
|
||||
<CardTitle>Tags</CardTitle>
|
||||
<CardDescription>{tags_count} Total</CardDescription>
|
||||
<CardHeader>
|
||||
<div className="flex justify-between">
|
||||
<div>
|
||||
<CardTitle>Tags</CardTitle>
|
||||
<CardDescription>{tags_count} Total</CardDescription>
|
||||
</div>
|
||||
<Tag className="w-4 h-4" />
|
||||
</div>
|
||||
<Tag className="w-4 h-4" />
|
||||
</CardHeader>
|
||||
</Card>
|
||||
</Link>
|
||||
|
||||
@@ -18,7 +18,7 @@ import { Link } from "react-router-dom";
|
||||
import { Card, CardDescription, CardHeader, CardTitle } from "@ui/card";
|
||||
import { AlerterConfig, DeleteAlerter } from "./config";
|
||||
import { CopyResource, ResourceLink } from "@components/util";
|
||||
import { TagsWithBadge } from "@components/tags";
|
||||
import { TagsWithBadge, useTagsFilter } from "@components/tags";
|
||||
|
||||
const useAlerter = (id?: string) =>
|
||||
useRead("ListAlerters", {}).data?.find((d) => d.id === id);
|
||||
@@ -43,11 +43,20 @@ export const AlerterComponents: RequiredResourceComponents = {
|
||||
),
|
||||
},
|
||||
Actions: [],
|
||||
Table: () => {
|
||||
Table: ({ search }) => {
|
||||
const tags = useTagsFilter();
|
||||
const alerters = useRead("ListAlerters", {}).data;
|
||||
const searchSplit = search?.split(" ") || [];
|
||||
return (
|
||||
<DataTable
|
||||
data={alerters ?? []}
|
||||
data={
|
||||
alerters?.filter((resource) =>
|
||||
tags.every((tag) => resource.tags.includes(tag)) &&
|
||||
searchSplit.length > 0
|
||||
? searchSplit.every((search) => resource.name.includes(search))
|
||||
: true
|
||||
) ?? []
|
||||
}
|
||||
columns={[
|
||||
{
|
||||
header: "Name",
|
||||
@@ -76,12 +85,14 @@ export const AlerterComponents: RequiredResourceComponents = {
|
||||
return (
|
||||
<Link to="/alerters/" className="w-full">
|
||||
<Card>
|
||||
<CardHeader className="justify-between">
|
||||
<div>
|
||||
<CardTitle>Alerters</CardTitle>
|
||||
<CardDescription>{alerters_count} Total</CardDescription>
|
||||
<CardHeader>
|
||||
<div className="flex justify-between">
|
||||
<div>
|
||||
<CardTitle>Alerters</CardTitle>
|
||||
<CardDescription>{alerters_count} Total</CardDescription>
|
||||
</div>
|
||||
<AlarmClock className="w-4 h-4" />
|
||||
</div>
|
||||
<AlarmClock className="w-4 h-4" />
|
||||
</CardHeader>
|
||||
</Card>
|
||||
</Link>
|
||||
|
||||
@@ -21,7 +21,8 @@ export const BuildChart = () => {
|
||||
const container_ref = useRef<HTMLDivElement>(null);
|
||||
const line_ref = useRef<IChartApi>();
|
||||
const series_ref = useRef<ISeriesApi<"Histogram">>();
|
||||
const { data } = useRead("GetBuildMonthlyStats", {});
|
||||
const build_stats = useRead("GetBuildMonthlyStats", {}).data;
|
||||
const summary = useRead("GetBuildsSummary", {}).data;
|
||||
|
||||
const handleResize = () =>
|
||||
line_ref.current?.applyOptions({
|
||||
@@ -29,7 +30,7 @@ export const BuildChart = () => {
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!data) return;
|
||||
if (!build_stats) return;
|
||||
if (line_ref.current) line_ref.current.remove();
|
||||
const init = () => {
|
||||
if (!container_ref.current) return;
|
||||
@@ -52,9 +53,9 @@ export const BuildChart = () => {
|
||||
series_ref.current = line_ref.current.addHistogramSeries({
|
||||
priceLineVisible: false,
|
||||
});
|
||||
const max = data.days.reduce((m, c) => Math.max(m, c.time), 0);
|
||||
const max = build_stats.days.reduce((m, c) => Math.max(m, c.time), 0);
|
||||
series_ref.current.setData(
|
||||
data.days.map((d) => ({
|
||||
build_stats.days.map((d) => ({
|
||||
time: (d.ts / 1000) as Time,
|
||||
value: d.count,
|
||||
color:
|
||||
@@ -71,19 +72,22 @@ export const BuildChart = () => {
|
||||
return () => {
|
||||
window.removeEventListener("resize", handleResize);
|
||||
};
|
||||
}, [data]);
|
||||
}, [build_stats]);
|
||||
|
||||
return (
|
||||
<Link to="/builds" className="w-full">
|
||||
<Card>
|
||||
<CardHeader className="justify-between">
|
||||
<div>
|
||||
<CardTitle>Builds</CardTitle>
|
||||
<CardDescription>
|
||||
{data?.total_time.toFixed(2)} Hours
|
||||
</CardDescription>
|
||||
<CardHeader>
|
||||
<div className="flex justify-between">
|
||||
<div>
|
||||
<CardTitle>Builds</CardTitle>
|
||||
<CardDescription className="flex gap-2">
|
||||
<div>{summary?.total} Total</div> |{" "}
|
||||
<div>{build_stats?.total_time.toFixed(2)} Hours</div>
|
||||
</CardDescription>
|
||||
</div>
|
||||
<Hammer className="w-4 h-4" />
|
||||
</div>
|
||||
<Hammer className="w-4 h-4" />
|
||||
</CardHeader>
|
||||
<CardContent className="h-[200px]">
|
||||
<div className="w-full max-w-full h-full" ref={container_ref} />
|
||||
|
||||
@@ -4,14 +4,18 @@ import { DataTable } from "@ui/data-table";
|
||||
import { fmt_date_with_minutes, fmt_version } from "@lib/formatting";
|
||||
import { ResourceComponents } from "..";
|
||||
|
||||
export const BuildTable = () => {
|
||||
export const BuildTable = ({ search }: { search?: string }) => {
|
||||
const builds = useRead("ListBuilds", {}).data;
|
||||
const tags = useTagsFilter();
|
||||
const searchSplit = search?.split(" ") || [];
|
||||
return (
|
||||
<DataTable
|
||||
data={
|
||||
builds?.filter((build) =>
|
||||
tags.every((tag) => build.tags.includes(tag))
|
||||
builds?.filter((resource) =>
|
||||
tags.every((tag) => resource.tags.includes(tag)) &&
|
||||
searchSplit.length > 0
|
||||
? searchSplit.every((search) => resource.name.includes(search))
|
||||
: true
|
||||
) ?? []
|
||||
}
|
||||
columns={[
|
||||
|
||||
@@ -56,14 +56,18 @@ export const BuilderComponents: RequiredResourceComponents = {
|
||||
</Section>
|
||||
),
|
||||
},
|
||||
Table: () => {
|
||||
Table: ({ search }) => {
|
||||
const tags = useTagsFilter();
|
||||
const builders = useRead("ListBuilders", {}).data;
|
||||
const searchSplit = search?.split(" ") || [];
|
||||
return (
|
||||
<DataTable
|
||||
data={
|
||||
builders?.filter((builder) =>
|
||||
tags.every((tag) => builder.tags.includes(tag))
|
||||
builders?.filter((resource) =>
|
||||
tags.every((tag) => resource.tags.includes(tag)) &&
|
||||
searchSplit.length > 0
|
||||
? searchSplit.every((search) => resource.name.includes(search))
|
||||
: true
|
||||
) ?? []
|
||||
}
|
||||
columns={[
|
||||
@@ -142,12 +146,14 @@ export const BuilderComponents: RequiredResourceComponents = {
|
||||
return (
|
||||
<Link to="/builders/" className="w-full">
|
||||
<Card>
|
||||
<CardHeader className="justify-between">
|
||||
<div>
|
||||
<CardTitle>Builders</CardTitle>
|
||||
<CardDescription>{builders_count} Total</CardDescription>
|
||||
<CardHeader>
|
||||
<div className="flex justify-between">
|
||||
<div>
|
||||
<CardTitle>Builders</CardTitle>
|
||||
<CardDescription>{builders_count} Total</CardDescription>
|
||||
</div>
|
||||
<Factory className="w-4 h-4" />
|
||||
</div>
|
||||
<Factory className="w-4 h-4" />
|
||||
</CardHeader>
|
||||
</Card>
|
||||
</Link>
|
||||
|
||||
@@ -21,12 +21,14 @@ export const DeploymentsChart = () => {
|
||||
return (
|
||||
<Link to="/deployments" className="w-full">
|
||||
<Card>
|
||||
<CardHeader className="justify-between">
|
||||
<div>
|
||||
<CardTitle>Deployments</CardTitle>
|
||||
<CardDescription>{summary?.total} Total</CardDescription>
|
||||
<CardHeader>
|
||||
<div className="flex justify-between">
|
||||
<div>
|
||||
<CardTitle>Deployments</CardTitle>
|
||||
<CardDescription>{summary?.total} Total</CardDescription>
|
||||
</div>
|
||||
<Rocket className="w-4 h-4" />
|
||||
</div>
|
||||
<Rocket className="w-4 h-4" />
|
||||
</CardHeader>
|
||||
<CardContent className="flex h-[200px] items-center justify-between">
|
||||
<div className="flex flex-col gap-2 text-muted-foreground w-full">
|
||||
|
||||
@@ -108,9 +108,9 @@ export const DeploymentComponents: RequiredResourceComponents = {
|
||||
</NewResource>
|
||||
);
|
||||
},
|
||||
Table: () => {
|
||||
Table: ({ search }) => {
|
||||
const deployments = useRead("ListDeployments", {}).data;
|
||||
return <DeploymentTable deployments={deployments} />;
|
||||
return <DeploymentTable deployments={deployments} search={search} />;
|
||||
},
|
||||
Dashboard: DeploymentsChart,
|
||||
};
|
||||
|
||||
@@ -11,15 +11,20 @@ import { snake_case_to_upper_space_case } from "@lib/formatting";
|
||||
|
||||
export const DeploymentTable = ({
|
||||
deployments,
|
||||
search,
|
||||
}: {
|
||||
deployments: Types.DeploymentListItem[] | undefined;
|
||||
search: string | undefined;
|
||||
}) => {
|
||||
const tags = useTagsFilter();
|
||||
const searchSplit = search?.split(" ") || [];
|
||||
return (
|
||||
<DataTable
|
||||
data={
|
||||
deployments?.filter((deployment) =>
|
||||
tags.every((tag) => deployment.tags.includes(tag))
|
||||
deployments?.filter((resource) =>
|
||||
tags.every((tag) => resource.tags.includes(tag)) && searchSplit.length > 0
|
||||
? searchSplit.every((search) => resource.name.includes(search))
|
||||
: true
|
||||
) ?? []
|
||||
}
|
||||
columns={[
|
||||
@@ -47,7 +52,7 @@ export const DeploymentTable = ({
|
||||
return undefined;
|
||||
}
|
||||
} else {
|
||||
const [img, _] = image.split(":");
|
||||
const [img] = image.split(":");
|
||||
return img;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -10,8 +10,8 @@ import { ProcedureComponents } from "./procedure/index";
|
||||
export const ResourceComponents: {
|
||||
[key in UsableResource]: RequiredResourceComponents;
|
||||
} = {
|
||||
Deployment: DeploymentComponents,
|
||||
Server: ServerComponents,
|
||||
Deployment: DeploymentComponents,
|
||||
Build: BuildComponents,
|
||||
Repo: RepoComponents,
|
||||
Procedure: ProcedureComponents,
|
||||
|
||||
@@ -87,7 +87,10 @@ export const ProcedureComponents: RequiredResourceComponents = {
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
Procedure Type
|
||||
<Select value={type} onValueChange={setType as any}>
|
||||
<Select
|
||||
value={type}
|
||||
onValueChange={(type) => setType(type as "Sequence" | "Parallel")}
|
||||
>
|
||||
<SelectTrigger className="w-32 capitalize">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
@@ -108,12 +111,14 @@ export const ProcedureComponents: RequiredResourceComponents = {
|
||||
return (
|
||||
<Link to="/procedures/" className="w-full">
|
||||
<Card>
|
||||
<CardHeader className="justify-between">
|
||||
<div>
|
||||
<CardTitle>Procedures</CardTitle>
|
||||
<CardDescription>{procedure_count} Total</CardDescription>
|
||||
<CardHeader>
|
||||
<div className="flex justify-between">
|
||||
<div>
|
||||
<CardTitle>Procedures</CardTitle>
|
||||
<CardDescription>{procedure_count} Total</CardDescription>
|
||||
</div>
|
||||
<Route className="w-4 h-4" />
|
||||
</div>
|
||||
<Route className="w-4 h-4" />
|
||||
</CardHeader>
|
||||
</Card>
|
||||
</Link>
|
||||
|
||||
@@ -1,13 +1,22 @@
|
||||
import { useRead } from "@lib/hooks";
|
||||
import { DataTable } from "@ui/data-table";
|
||||
import { ProcedureComponents } from ".";
|
||||
import { TagsWithBadge } from "@components/tags";
|
||||
import { TagsWithBadge, useTagsFilter } from "@components/tags";
|
||||
|
||||
export const ProcedureTable = () => {
|
||||
export const ProcedureTable = ({ search }: { search: string | undefined }) => {
|
||||
const tags = useTagsFilter();
|
||||
const procedures = useRead("ListProcedures", {}).data;
|
||||
const searchSplit = search?.split(" ") || [];
|
||||
return (
|
||||
<DataTable
|
||||
data={procedures ?? []}
|
||||
data={
|
||||
procedures?.filter((resource) =>
|
||||
tags.every((tag) => resource.tags.includes(tag)) &&
|
||||
searchSplit.length > 0
|
||||
? searchSplit.every((search) => resource.name.includes(search))
|
||||
: true
|
||||
) ?? []
|
||||
}
|
||||
columns={[
|
||||
{
|
||||
accessorKey: "id",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { TagsWithBadge } from "@components/tags";
|
||||
import { TagsWithBadge, useTagsFilter } from "@components/tags";
|
||||
import { useRead, useWrite } from "@lib/hooks";
|
||||
import { RequiredResourceComponents } from "@types";
|
||||
import { Card, CardDescription, CardHeader, CardTitle } from "@ui/card";
|
||||
@@ -35,11 +35,20 @@ export const RepoComponents: RequiredResourceComponents = {
|
||||
</Section>
|
||||
),
|
||||
},
|
||||
Table: () => {
|
||||
Table: ({ search }) => {
|
||||
const tags = useTagsFilter();
|
||||
const repos = useRead("ListRepos", {}).data;
|
||||
const searchSplit = search?.split(" ") || [];
|
||||
return (
|
||||
<DataTable
|
||||
data={repos ?? []}
|
||||
data={
|
||||
repos?.filter((resource) =>
|
||||
tags.every((tag) => resource.tags.includes(tag)) &&
|
||||
searchSplit.length > 0
|
||||
? searchSplit.every((search) => resource.name.includes(search))
|
||||
: true
|
||||
) ?? []
|
||||
}
|
||||
columns={[
|
||||
{
|
||||
accessorKey: "id",
|
||||
@@ -65,12 +74,14 @@ export const RepoComponents: RequiredResourceComponents = {
|
||||
return (
|
||||
<Link to="/repos/" className="w-full">
|
||||
<Card>
|
||||
<CardHeader className="justify-between">
|
||||
<div>
|
||||
<CardTitle>Repos</CardTitle>
|
||||
<CardDescription>{repo_count} Total</CardDescription>
|
||||
<CardHeader>
|
||||
<div className="flex justify-between">
|
||||
<div>
|
||||
<CardTitle>Repos</CardTitle>
|
||||
<CardDescription>{repo_count} Total</CardDescription>
|
||||
</div>
|
||||
<GitBranch className="w-4 h-4" />
|
||||
</div>
|
||||
<GitBranch className="w-4 h-4" />
|
||||
</CardHeader>
|
||||
</Card>
|
||||
</Link>
|
||||
|
||||
@@ -20,12 +20,14 @@ export const ServersChart = () => {
|
||||
return (
|
||||
<Link to="/servers/" className="w-full">
|
||||
<Card>
|
||||
<CardHeader className="justify-between">
|
||||
<div>
|
||||
<CardTitle>Servers</CardTitle>
|
||||
<CardDescription>{data?.total} Total</CardDescription>
|
||||
<CardHeader>
|
||||
<div className="flex justify-between">
|
||||
<div>
|
||||
<CardTitle>Servers</CardTitle>
|
||||
<CardDescription>{data?.total} Total</CardDescription>
|
||||
</div>
|
||||
<Server className="w-4 h-4" />
|
||||
</div>
|
||||
<Server className="w-4 h-4" />
|
||||
</CardHeader>
|
||||
<CardContent className="flex h-[200px] items-center justify-between">
|
||||
<div className="flex flex-col gap-2 text-muted-foreground w-full">
|
||||
|
||||
@@ -4,15 +4,19 @@ import { DataTable } from "@ui/data-table";
|
||||
import { ServerComponents } from ".";
|
||||
import { ResourceComponents } from "..";
|
||||
|
||||
export const ServerTable = () => {
|
||||
export const ServerTable = ({ search }: { search: string | undefined }) => {
|
||||
const servers = useRead("ListServers", {}).data;
|
||||
const tags = useTagsFilter();
|
||||
const searchSplit = search?.split(" ") || [];
|
||||
return (
|
||||
<DataTable
|
||||
// onRowClick={({ id }) => nav(`/servers/${id}`)}
|
||||
data={
|
||||
servers?.filter((server) =>
|
||||
tags.every((tag) => server.tags.includes(tag))
|
||||
servers?.filter((resource) =>
|
||||
tags.every((tag) => resource.tags.includes(tag)) &&
|
||||
searchSplit.length > 0
|
||||
? searchSplit.every((search) => resource.name.includes(search))
|
||||
: true
|
||||
) ?? []
|
||||
}
|
||||
columns={[
|
||||
|
||||
@@ -22,7 +22,7 @@ type TargetExcludingSystem = Exclude<Types.ResourceTarget, { type: "System" }>;
|
||||
const tagsAtom = atomWithStorage<string[]>("tags-v0", []);
|
||||
|
||||
export const useTagsFilter = () => {
|
||||
const [tags, _] = useAtom(tagsAtom);
|
||||
const [tags] = useAtom(tagsAtom);
|
||||
return tags;
|
||||
};
|
||||
|
||||
|
||||
@@ -2,13 +2,12 @@ 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 { RequiredResourceComponents, 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"
|
||||
@@ -26,22 +25,32 @@ export const AllResources = () => {
|
||||
</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 key={type} title={type + "s"} actions={<Components.New />}>
|
||||
<Components.Table />
|
||||
</Section>
|
||||
);
|
||||
})}
|
||||
{Object.entries(ResourceComponents).map(([type, Components]) => (
|
||||
<TableSection type={type} Components={Components} search={search} />
|
||||
))}
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
const TableSection = ({
|
||||
type,
|
||||
Components,
|
||||
search,
|
||||
}: {
|
||||
type: string;
|
||||
Components: RequiredResourceComponents;
|
||||
search?: string;
|
||||
}) => {
|
||||
const tags = useTagsFilter();
|
||||
const count = useRead(`List${type as UsableResource}s`, {}).data?.filter(
|
||||
(resource) => tags.every((tag) => resource.tags.includes(tag))
|
||||
).length;
|
||||
|
||||
if (!count) return;
|
||||
|
||||
return (
|
||||
<Section key={type} title={type + "s"} actions={<Components.New />}>
|
||||
<Components.Table search={search} />
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -40,14 +40,16 @@ const Resources = () => (
|
||||
<div className="w-full lg:max-w-[50%]">
|
||||
<Link to="/tree" className="w-full">
|
||||
<Card>
|
||||
<CardHeader className="justify-between">
|
||||
<div>
|
||||
<CardTitle>Tree</CardTitle>
|
||||
<CardDescription>
|
||||
Visualize your servers / deployments
|
||||
</CardDescription>
|
||||
<CardHeader>
|
||||
<div className="flex justify-between">
|
||||
<div>
|
||||
<CardTitle>Tree</CardTitle>
|
||||
<CardDescription>
|
||||
Visualize your servers / deployments
|
||||
</CardDescription>
|
||||
</div>
|
||||
<FolderTree className="w-4 h-4" />
|
||||
</div>
|
||||
<FolderTree className="w-4 h-4" />
|
||||
</CardHeader>
|
||||
</Card>
|
||||
</Link>
|
||||
|
||||
5
frontend/src/types.d.ts
vendored
5
frontend/src/types.d.ts
vendored
@@ -1,5 +1,4 @@
|
||||
import { Types } from "@monitor/client";
|
||||
import { ReactNode } from "react";
|
||||
|
||||
export type UsableResource = Exclude<Types.ResourceTarget["type"], "System">;
|
||||
|
||||
@@ -18,11 +17,11 @@ export interface RequiredResourceComponents {
|
||||
Description: IdComponent;
|
||||
Status: IdComponent;
|
||||
Link: IdComponent;
|
||||
|
||||
|
||||
Info: IdComponent[];
|
||||
Actions: IdComponent[];
|
||||
|
||||
Table: React.FC;
|
||||
Table: React.FC<{ search?: string }>;
|
||||
|
||||
Page: { [section: string]: IdComponent };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user