service user detection

This commit is contained in:
mbecker20
2024-04-07 01:51:22 -07:00
parent 79620030bc
commit 592af39550
22 changed files with 196 additions and 109 deletions

View File

@@ -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,

View File

@@ -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!(

View File

@@ -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> {

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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} />

View File

@@ -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={[

View File

@@ -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>

View File

@@ -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">

View File

@@ -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,
};

View File

@@ -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;
}
},

View File

@@ -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,

View File

@@ -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>

View File

@@ -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",

View File

@@ -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>

View File

@@ -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">

View File

@@ -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={[

View File

@@ -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;
};

View File

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

View File

@@ -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>

View File

@@ -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 };
}