add ListPermissions route

This commit is contained in:
mbecker20
2024-04-21 01:46:01 -07:00
parent 79b4bae40a
commit c46b2cf59d
13 changed files with 174 additions and 68 deletions

View File

@@ -43,6 +43,7 @@ enum ReadRequest {
GetUsers(GetUsers),
GetUsername(GetUsername),
ListApiKeys(ListApiKeys),
ListPermissions(ListPermissions),
ListUserPermissions(ListUserPermissions),
// ==== USER GROUP ====

View File

@@ -1,7 +1,10 @@
use anyhow::{anyhow, Context};
use axum::async_trait;
use monitor_client::{
api::read::{ListUserPermissions, ListUserPermissionsResponse},
api::read::{
ListPermissions, ListPermissionsResponse, ListUserPermissions,
ListUserPermissionsResponse,
},
entities::user::User,
};
use mungos::{find::find_collect, mongodb::bson::doc};
@@ -9,6 +12,26 @@ use resolver_api::Resolve;
use crate::{db::db_client, state::State};
#[async_trait]
impl Resolve<ListPermissions, User> for State {
async fn resolve(
&self,
ListPermissions {}: ListPermissions,
user: User,
) -> anyhow::Result<ListPermissionsResponse> {
find_collect(
&db_client().await.permissions,
doc! {
"user_target.type": "User",
"user_target.id": &user.id
},
None,
)
.await
.context("failed to query db for permissions")
}
}
#[async_trait]
impl Resolve<ListUserPermissions, User> for State {
async fn resolve(

View File

@@ -7,6 +7,21 @@ use crate::entities::permission::Permission;
use super::MonitorReadRequest;
/// List permissions for the calling user. Response: [ListPermissionsResponse]
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Request, EmptyTraits,
)]
#[empty_traits(MonitorReadRequest)]
#[response(ListPermissionsResponse)]
pub struct ListPermissions {}
#[typeshare]
pub type ListPermissionsResponse = Vec<Permission>;
//
/// List permissions for a specific user. Admin only. Response: [ListUserPermissionsResponse]
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Request, EmptyTraits,

View File

@@ -16,6 +16,7 @@ export type ReadResponses = {
GetUsers: Types.GetUsersResponse;
GetUsername: Types.GetUsernameResponse;
ListApiKeys: Types.ListApiKeysResponse;
ListPermissions: Types.ListPermissionsResponse;
ListUserPermissions: Types.ListUserPermissionsResponse;
// ==== USER GROUP ====

View File

@@ -549,6 +549,8 @@ export interface Permission {
level?: PermissionLevel;
}
export type ListPermissionsResponse = Permission[];
export type ListUserPermissionsResponse = Permission[];
export enum ProcedureType {
@@ -1457,6 +1459,11 @@ export interface GetCoreInfoResponse {
github_webhook_base_url: string;
}
/** List permissions for the calling user. Response: [ListPermissionsResponse] */
export interface ListPermissions {
}
/** List permissions for a specific user. Admin only. Response: [ListUserPermissionsResponse] */
export interface ListUserPermissions {
user_id: string;
}
@@ -2108,6 +2115,7 @@ export type ReadRequest =
| { type: "GetUsers", params: GetUsers }
| { type: "GetUsername", params: GetUsername }
| { type: "ListApiKeys", params: ListApiKeys }
| { type: "ListPermissions", params: ListPermissions }
| { type: "ListUserPermissions", params: ListUserPermissions }
| { type: "GetUserGroup", params: GetUserGroup }
| { type: "ListUserGroups", params: ListUserGroups }

View File

@@ -26,12 +26,14 @@ export const ConfigLayout = <
>({
config,
children,
disabled,
onConfirm,
onReset,
selector,
}: {
config: Partial<T>;
children: ReactNode;
disabled: boolean;
onConfirm: () => void;
onReset: () => void;
selector?: ReactNode;
@@ -45,7 +47,7 @@ export const ConfigLayout = <
<Button
variant="outline"
onClick={onReset}
disabled={config ? !Object.keys(config).length : true}
disabled={disabled || (config ? !Object.keys(config).length : true)}
>
<History className="w-4 h-4" />
</Button>
@@ -53,6 +55,7 @@ export const ConfigLayout = <
<ConfirmUpdate
content={JSON.stringify(config, null, 2)}
onConfirm={onConfirm}
disabled={disabled}
/>
) : null}
</div>
@@ -65,6 +68,7 @@ export const ConfigLayout = <
export const Config = <T,>({
config,
update,
disabled,
set,
onSave,
components,
@@ -72,6 +76,7 @@ export const Config = <T,>({
}: {
config: T;
update: Partial<T>;
disabled: boolean;
set: React.Dispatch<SetStateAction<Partial<T>>>;
onSave: () => void;
selector?: ReactNode;
@@ -93,11 +98,14 @@ export const Config = <T,>({
return (
<ConfigLayout
config={update}
disabled={disabled}
onConfirm={onSave}
onReset={() => set({})}
selector={
<div className="flex gap-4 items-center">
{selector}
{/* Add the config page selector when view is small / md (lg:hidden) */}
<Select value={show} onValueChange={setShow}>
<SelectTrigger className="w-32 capitalize lg:hidden">
<SelectValue />
@@ -140,6 +148,7 @@ export const Config = <T,>({
update={update}
set={(u) => set((p) => ({ ...p, ...u }))}
components={v}
disabled={disabled}
/>
</CardContent>
</Card>
@@ -155,11 +164,13 @@ export const ConfigAgain = <
>({
config,
update,
disabled,
components,
set,
}: {
config: T;
update: Partial<T>;
disabled: boolean;
components: Partial<{
[K in keyof T extends string ? keyof T : never]:
| boolean
@@ -181,6 +192,7 @@ export const ConfigAgain = <
label={key.toString()}
value={value}
onChange={(value) => set({ [key]: value } as Partial<T>)}
disabled={disabled}
/>
);
case "number":
@@ -192,6 +204,7 @@ export const ConfigAgain = <
onChange={(value) =>
set({ [key]: Number(value) } as Partial<T>)
}
disabled={disabled}
/>
);
case "boolean":
@@ -201,6 +214,7 @@ export const ConfigAgain = <
label={key.toString()}
value={value}
onChange={(value) => set({ [key]: value } as Partial<T>)}
disabled={disabled}
/>
);
default:

View File

@@ -30,7 +30,7 @@ export const ConfigItem = ({
children,
className,
}: {
label: string;
label?: string;
children: ReactNode;
className?: string;
}) => (
@@ -40,7 +40,7 @@ export const ConfigItem = ({
className
)}
>
<div>{snake_case_to_upper_space_case(label)}</div>
{label && <div>{snake_case_to_upper_space_case(label)}</div>}
{children}
</div>
);
@@ -48,17 +48,17 @@ export const ConfigItem = ({
export const ConfigInput = ({
label,
value,
placeholder,
disabled,
placeholder,
onChange,
onBlur,
}: {
label: string;
value: string | number | undefined;
disabled?: boolean;
placeholder?: string;
onChange?: (value: string) => void;
onBlur?: (value: string) => void;
placeholder?: string;
disabled?: boolean;
}) => (
<ConfigItem label={label}>
<Input
@@ -76,14 +76,16 @@ export const ConfigInput = ({
export const ConfigSwitch = ({
label,
value,
disabled,
onChange,
}: {
label: string;
value: boolean | undefined;
disabled: boolean;
onChange: (value: boolean) => void;
}) => (
<ConfigItem label={label}>
<Switch checked={value} onCheckedChange={onChange} />
<Switch checked={value} onCheckedChange={onChange} disabled={disabled} />
</ConfigItem>
);
@@ -93,6 +95,7 @@ export const DoubleInput = <
L extends T[K] extends string | number | undefined ? K : never,
R extends T[K] extends string | number | undefined ? K : never
>({
disabled,
values,
leftval,
leftpl,
@@ -105,6 +108,7 @@ export const DoubleInput = <
onRemove,
inputClassName,
}: {
disabled: boolean;
values: T[] | undefined;
leftval: L;
leftpl: string;
@@ -126,6 +130,7 @@ export const DoubleInput = <
value={value[leftval] as any}
placeholder={leftpl}
onChange={(e) => onLeftChange(e.target.value as T[L], i)}
disabled={disabled}
/>
:
<Input
@@ -133,34 +138,38 @@ export const DoubleInput = <
value={value[rightval] as any}
placeholder={rightpl}
onChange={(e) => onRightChange(e.target.value as T[R], i)}
disabled={disabled}
/>
<Button
variant="secondary"
onClick={() => onRemove(i)}
>
<MinusCircle className="w-4 h-4" />
</Button>
{!disabled && (
<Button variant="secondary" onClick={() => onRemove(i)}>
<MinusCircle className="w-4 h-4" />
</Button>
)}
</div>
))}
<Button
variant="secondary"
className="flex items-center gap-2 w-[200px] place-self-end"
onClick={onAdd}
>
<PlusCircle className="w-4 h-4" />
Add {addName}
</Button>
{!disabled && (
<Button
variant="secondary"
className="flex items-center gap-2 w-[200px] place-self-end"
onClick={onAdd}
>
<PlusCircle className="w-4 h-4" />
Add {addName}
</Button>
)}
</div>
);
};
export const AccountSelector = ({
disabled,
id,
type,
account_type,
selected,
onSelect,
}: {
disabled: boolean;
id: string | undefined;
type: "Server" | "Builder";
account_type: keyof Types.GetBuilderAvailableAccountsResponse;
@@ -177,10 +186,11 @@ export const AccountSelector = ({
<Select
value={type === "Builder" ? selected || undefined : selected}
onValueChange={onSelect}
disabled={disabled}
>
<SelectTrigger
className="w-full lg:w-[300px] max-w-[50%]"
disabled={!id}
disabled={disabled || !id}
>
<SelectValue
placeholder={type === "Server" ? "Same as build" : "Select Account"}
@@ -204,10 +214,12 @@ export const AccountSelector = ({
export const InputList = <T extends { [key: string]: unknown }>({
field,
values,
disabled,
set,
}: {
field: keyof T;
values: string[];
disabled: boolean;
set: (update: Partial<T>) => void;
}) => (
<ConfigItem label={field as string} className="items-start">
@@ -221,28 +233,31 @@ export const InputList = <T extends { [key: string]: unknown }>({
values[i] = e.target.value;
set({ [field]: [...values] } as Partial<T>);
}}
disabled={disabled}
/>
<Button
variant="outline"
// intent="warning"
onClick={() =>
set({
[field]: [...values.filter((_, idx) => idx !== i)],
} as Partial<T>)
}
>
<MinusCircle className="w-4 h-4" />
</Button>
{!disabled && (
<Button
variant="outline"
onClick={() =>
set({
[field]: [...values.filter((_, idx) => idx !== i)],
} as Partial<T>)
}
>
<MinusCircle className="w-4 h-4" />
</Button>
)}
</div>
))}
<Button
variant="outline"
// intent="success"
onClick={() => set({ [field]: [...values, ""] } as Partial<T>)}
>
Add {snake_case_to_upper_space_case(field as string).slice(0, -1)}
</Button>
{!disabled && (
<Button
variant="outline"
onClick={() => set({ [field]: [...values, ""] } as Partial<T>)}
>
Add {snake_case_to_upper_space_case(field as string).slice(0, -1)}
</Button>
)}
</div>
</ConfigItem>
);
@@ -250,14 +265,19 @@ export const InputList = <T extends { [key: string]: unknown }>({
interface ConfirmUpdateProps {
content: string;
onConfirm: () => void;
disabled: boolean;
}
export const ConfirmUpdate = ({ content, onConfirm }: ConfirmUpdateProps) => {
export const ConfirmUpdate = ({
content,
onConfirm,
disabled,
}: ConfirmUpdateProps) => {
const [open, set] = useState(false);
return (
<Dialog open={open} onOpenChange={set}>
<DialogTrigger asChild>
<Button onClick={() => set(true)}>
<Button onClick={() => set(true)} disabled={disabled}>
<Save className="w-4 h-4" />
</Button>
</DialogTrigger>
@@ -287,10 +307,12 @@ export const ConfirmUpdate = ({ content, onConfirm }: ConfirmUpdateProps) => {
export const SystemCommand = ({
label,
value,
disabled,
set,
}: {
label: string;
value?: Types.SystemCommand;
disabled: boolean;
set: (value: Types.SystemCommand) => void;
}) => {
return (
@@ -303,6 +325,7 @@ export const SystemCommand = ({
value={value?.path}
className="w-[300px]"
onChange={(e) => set({ ...(value || {}), path: e.target.value })}
disabled={disabled}
/>
</div>
<div className="flex gap-4 items-center justify-end">
@@ -312,6 +335,7 @@ export const SystemCommand = ({
value={value?.command}
className="w-[300px]"
onChange={(e) => set({ ...(value || {}), command: e.target.value })}
disabled={disabled}
/>
</div>
</div>

View File

@@ -158,7 +158,7 @@ const BuildArgs = ({
}, [args, set]);
return (
<ConfigItem label="Build Args" className="flex-col gap-4 items-start">
<ConfigItem className="flex-col gap-4 items-start">
<Textarea
className="min-h-[300px]"
placeholder="VARIABLE=value"

View File

@@ -9,10 +9,12 @@ import { RefObject, createRef, useEffect, useState } from "react";
export const EnvVars = ({
vars,
set,
disabled,
server,
}: {
vars: Types.EnvironmentVar[];
set: (input: Partial<Types.DeploymentConfig>) => void;
disabled: boolean;
/// eg server id
server?: string;
}) => {
@@ -23,11 +25,8 @@ export const EnvVars = ({
}, [env, set]);
return (
<ConfigItem
label="Environment Variables"
className="flex-col gap-4 items-start"
>
{server && (
<ConfigItem className="flex-col gap-4 items-start">
{!disabled && server && (
<Secrets server={server} env={env} setEnv={setEnv} envRef={ref} />
)}
<Textarea
@@ -58,10 +57,11 @@ const Secrets = ({
return (
secrets &&
secrets.length > 0 && (
<div className="w-full flex gap-4 justify-end items-center">
<div className="flex gap-4 items-center">
<div className="text-muted-foreground">secrets:</div>
{secrets?.map((secret) => (
<Button
variant="secondary"
key={secret}
onClick={() =>
setEnv(

View File

@@ -7,9 +7,11 @@ import { MinusCircle, PlusCircle } from "lucide-react";
export const ExtraArgs = ({
args,
set,
disabled,
}: {
args: string[];
set: (update: Partial<Types.DeploymentConfig>) => void;
disabled: boolean;
}) => {
return (
<ConfigItem label="Extra Args" className="items-start">
@@ -23,25 +25,30 @@ export const ExtraArgs = ({
args[i] = e.target.value;
set({ extra_args: [...args] });
}}
disabled={disabled}
/>
<Button
variant="secondary"
onClick={() =>
set({ extra_args: [...args.filter((_, idx) => idx !== i)] })
}
>
<MinusCircle className="w-4 h-4" />
</Button>
{!disabled && (
<Button
variant="secondary"
onClick={() =>
set({ extra_args: [...args.filter((_, idx) => idx !== i)] })
}
>
<MinusCircle className="w-4 h-4" />
</Button>
)}
</div>
))}
<Button
variant="secondary"
className="flex items-center gap-2 w-[200px] place-self-end"
onClick={() => set({ extra_args: [...args, ""] })}
>
<PlusCircle className="w-4 h-4" /> Add Extra Arg
</Button>
{!disabled && (
<Button
variant="secondary"
className="flex items-center gap-2 w-[200px] place-self-end"
onClick={() => set({ extra_args: [...args, ""] })}
>
<PlusCircle className="w-4 h-4" /> Add Extra Arg
</Button>
)}
</div>
</ConfigItem>
);

View File

@@ -14,10 +14,12 @@ import {
} from "@ui/select";
const BuildVersionSelector = ({
disabled,
buildId,
selected,
onSelect,
}: {
disabled: boolean;
buildId: string | undefined;
selected: string | undefined;
onSelect: (version: string) => void;
@@ -28,8 +30,12 @@ const BuildVersionSelector = ({
{ enabled: !!buildId }
).data;
return (
<Select value={selected || undefined} onValueChange={onSelect}>
<SelectTrigger className="w-full lg:w-[150px]">
<Select
value={selected || undefined}
onValueChange={onSelect}
disabled={disabled}
>
<SelectTrigger className="w-full lg:w-[150px]" disabled={disabled}>
<SelectValue placeholder="Select Version" />
</SelectTrigger>
<SelectContent>
@@ -70,9 +76,11 @@ const ImageTypeSelector = ({
export const ImageConfig = ({
image,
set,
disabled,
}: {
image: Types.DeploymentImage | undefined;
set: (input: Partial<Types.DeploymentConfig>) => void;
disabled: boolean;
}) => (
<ConfigItem label="Image">
<div className="flex gap-4 w-full justify-end">
@@ -121,6 +129,7 @@ export const ImageConfig = ({
},
})
}
disabled={disabled}
/>
</div>
)}

View File

@@ -14,16 +14,19 @@ const format_mode = (m: string) => m.split("-").join(" ");
export const RestartModeSelector = ({
selected,
set,
disabled,
}: {
selected: Types.RestartMode | undefined;
set: (input: Partial<Types.DeploymentConfig>) => void;
disabled: boolean;
}) => (
<ConfigItem label="Restart Mode">
<Select
value={selected || undefined}
onValueChange={(restart: Types.RestartMode) => set({ restart })}
disabled={disabled}
>
<SelectTrigger className="max-w-[150px] capitalize">
<SelectTrigger className="max-w-[150px] capitalize" disabled={disabled}>
<SelectValue placeholder="Select Type" />
</SelectTrigger>
<SelectContent>

View File

@@ -38,6 +38,7 @@ export const ServerSelector = ({
);
export const DeploymentConfig = ({ id }: { id: string }) => {
// const perms = useRead("ListPerm")
const config = useRead("GetDeployment", { deployment: id }).data?.config;
const [update, set] = useState<Partial<Types.DeploymentConfig>>({});
const { mutate } = useWrite("UpdateDeployment");