ListUserPermissions

This commit is contained in:
mbecker20
2024-03-30 15:45:07 -07:00
parent 65700ad70e
commit 1f143b814e
10 changed files with 200 additions and 30 deletions
+5 -1
View File
@@ -19,6 +19,7 @@ mod alerter;
mod build;
mod builder;
mod deployment;
mod permission;
mod procedure;
mod repo;
mod search;
@@ -34,10 +35,13 @@ mod user;
#[serde(tag = "type", content = "params")]
enum ReadRequest {
GetVersion(GetVersion),
GetCoreInfo(GetCoreInfo),
// ==== USER ====
GetUsers(GetUsers),
GetUsername(GetUsername),
GetCoreInfo(GetCoreInfo),
ListApiKeys(ListApiKeys),
ListUserPermissions(ListUserPermissions),
// ==== SEARCH ====
FindResources(FindResources),
+30
View File
@@ -0,0 +1,30 @@
use anyhow::{anyhow, Context};
use axum::async_trait;
use monitor_client::{
api::read::{ListUserPermissions, ListUserPermissionsResponse},
entities::user::User,
};
use mungos::{find::find_collect, mongodb::bson::doc};
use resolver_api::Resolve;
use crate::{db::db_client, state::State};
#[async_trait]
impl Resolve<ListUserPermissions, User> for State {
async fn resolve(
&self,
ListUserPermissions { user_id }: ListUserPermissions,
user: User,
) -> anyhow::Result<ListUserPermissionsResponse> {
if !user.admin {
return Err(anyhow!("this method is admin only"));
}
find_collect(
&db_client().await.permissions,
doc! { "user_id": user_id },
None,
)
.await
.context("failed to query db for permissions")
}
}
+2
View File
@@ -8,6 +8,7 @@ mod alerter;
mod build;
mod builder;
mod deployment;
mod permission;
mod procedure;
mod repo;
mod search;
@@ -20,6 +21,7 @@ pub use alerter::*;
pub use build::*;
pub use builder::*;
pub use deployment::*;
pub use permission::*;
pub use procedure::*;
pub use repo::*;
pub use search::*;
+21
View File
@@ -0,0 +1,21 @@
use derive_empty_traits::EmptyTraits;
use resolver_api::derive::Request;
use serde::{Deserialize, Serialize};
use typeshare::typeshare;
use crate::entities::permission::Permission;
use super::MonitorReadRequest;
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Request, EmptyTraits,
)]
#[empty_traits(MonitorReadRequest)]
#[response(ListUserPermissionsResponse)]
pub struct ListUserPermissions {
pub user_id: String,
}
#[typeshare]
pub type ListUserPermissionsResponse = Vec<Permission>;
+4 -1
View File
@@ -10,10 +10,13 @@ export type AuthResponses = {
export type ReadResponses = {
GetVersion: Types.GetVersionResponse;
GetCoreInfo: Types.GetCoreInfoResponse;
// ==== USER ====
GetUsers: Types.GetUsersResponse;
GetUsername: Types.GetUsernameResponse;
GetCoreInfo: Types.GetCoreInfoResponse;
ListApiKeys: Types.ListApiKeysResponse;
ListUserPermissions: Types.ListUserPermissionsResponse;
// ==== SEARCH ====
FindResources: Types.FindResourcesResponse;
+22 -15
View File
@@ -316,6 +316,22 @@ export type ListApiKeysResponse = ApiKey[];
export type GetUsersResponse = User[];
export enum PermissionLevel {
None = "None",
Read = "Read",
Execute = "Execute",
Write = "Write",
}
export interface Permission {
_id?: MongoId;
user_id: string;
target: ResourceTarget;
level?: PermissionLevel;
}
export type ListUserPermissionsResponse = Permission[];
export type ProcedureConfig =
| { type: "Sequence", data: EnabledExecution[] }
| { type: "Parallel", data: EnabledExecution[] };
@@ -1186,6 +1202,10 @@ export interface GetCoreInfoResponse {
monitoring_interval: Timelength;
}
export interface ListUserPermissions {
user_id: string;
}
export interface GetProcedure {
/** Id or name */
procedure: string;
@@ -1565,13 +1585,6 @@ export interface LaunchAwsServerConfig {
use_public_ip: boolean;
}
export enum PermissionLevel {
None = "None",
Read = "Read",
Execute = "Execute",
Write = "Write",
}
export interface UpdateUserPermissionsOnTarget {
user_id: string;
permission: PermissionLevel;
@@ -1736,13 +1749,6 @@ export interface CloneArgs {
github_account?: string;
}
export interface Permission {
_id?: MongoId;
user_id: string;
target: ResourceTarget;
level?: PermissionLevel;
}
export type Execution =
/** For new executions upon instantiation */
| { type: "None", params: None }
@@ -1797,10 +1803,11 @@ export type ExecuteRequest =
export type ReadRequest =
| { type: "GetVersion", params: GetVersion }
| { type: "GetCoreInfo", params: GetCoreInfo }
| { type: "GetUsers", params: GetUsers }
| { type: "GetUsername", params: GetUsername }
| { type: "GetCoreInfo", params: GetCoreInfo }
| { type: "ListApiKeys", params: ListApiKeys }
| { type: "ListUserPermissions", params: ListUserPermissions }
| { type: "FindResources", params: FindResources }
| { type: "GetProceduresSummary", params: GetProceduresSummary }
| { type: "GetProcedure", params: GetProcedure }
+4 -4
View File
@@ -94,8 +94,8 @@ export const Page = ({
);
interface SectionProps {
title: ReactNode;
children: ReactNode;
title?: ReactNode;
children?: ReactNode;
icon?: ReactNode;
actions?: ReactNode;
}
@@ -103,10 +103,10 @@ interface SectionProps {
export const Section = ({ title, icon, actions, children }: SectionProps) => (
<div className="flex flex-col gap-4">
<div className="flex items-start justify-between">
<div className="flex items-center gap-2 text-muted-foreground">
{(title || icon) && <div className="flex items-center gap-2 text-muted-foreground">
{icon}
<h2 className="text-xl">{title}</h2>
</div>
</div>}
{actions}
</div>
{children}
+100 -3
View File
@@ -1,8 +1,15 @@
import { Page } from "@components/layouts";
import { useRead } from "@lib/hooks";
import { Page, Section } from "@components/layouts";
import { ConfirmButton } from "@components/util";
import { useInvalidate, useRead, useWrite } from "@lib/hooks";
import { DataTable } from "@ui/data-table";
import { Label } from "@ui/label";
import { Switch } from "@ui/switch";
import { useToast } from "@ui/use-toast";
import { UserCheck, UserMinus } from "lucide-react";
import { useNavigate, useParams } from "react-router-dom";
export const UsersPage = () => {
const nav = useNavigate();
const users = useRead("GetUsers", {}).data;
return (
<Page title="Users">
@@ -10,10 +17,100 @@ export const UsersPage = () => {
data={users ?? []}
columns={[
{ header: "Username", accessorKey: "username" },
{ header: "Admin", accessorKey: "admin" },
{ header: "Type", accessorKey: "config.type" },
{
header: "Level",
accessorFn: (user) => (user.admin ? "Admin" : "User"),
},
{ header: "Enabled", accessorKey: "enabled" },
]}
onRowClick={(user) => nav(`/users/${user._id!.$oid}`)}
/>
</Page>
);
};
export const UserPage = () => {
const { toast } = useToast();
const inv = useInvalidate();
const user_id = useParams().id as string;
const user = useRead("GetUsers", {}).data?.find(
(user) => user._id?.$oid === user_id
);
const { mutate } = useWrite("UpdateUserPerimissions", {
onSuccess: () => inv(["GetUsers"]),
onError: (e) => {
console.log(e);
toast({ title: "Failed to update user permissions" });
},
});
const enabledClass = user?.enabled ? "text-green-500" : "text-red-500";
return (
user && (
<Page
title={user?.username}
subtitle={
<div className="text-sm text-muted-foreground flex gap-2">
<div className={enabledClass}>
{user?.enabled ? "Enabled" : "Disabled"}
</div>
|<div className="">Level: {user?.admin ? "Admin" : "User"}</div>|
<div className="">Type: {user?.config.type}</div>
</div>
}
actions={
!user.admin && (
<div className="flex gap-4 items-center">
{(["Server", "Build"] as Array<"Server" | "Build">).map(
(item) => {
const key = `create_${item.toLowerCase()}_permissions` as
| "create_server_permissions"
| "create_build_permissions";
const req_key = `create_${item.toLowerCase()}s`;
return (
<div key={key} className="flex gap-2 items-center">
<Label htmlFor={key}>Create {item}</Label>
<Switch
id={key}
className="flex gap-4"
checked={user[key]}
onClick={() =>
mutate({ user_id, [req_key]: !user[key] })
}
/>
</div>
);
}
)}
<ConfirmButton
title={user.enabled ? "Disable User" : "Enable User"}
icon={
user.enabled ? (
<UserMinus className="w-4 h-4" />
) : (
<UserCheck className="w-4 h-4" />
)
}
variant={user.enabled ? "destructive" : "outline"}
onClick={() => mutate({ user_id, enabled: !user.enabled })}
/>
</div>
)
}
>
<PermissionsTable />
</Page>
)
);
};
const PermissionsTable = () => {
const inv = useInvalidate();
const user_id = useParams().id as string;
// const permissions = useRead("")
return (
<Section title="Permissions">
<DataTable data={[]} columns={[]} />
</Section>
);
}
+8 -2
View File
@@ -9,7 +9,7 @@ import { RouterProvider, createBrowserRouter } from "react-router-dom";
import { Tree } from "@pages/tree";
import { Tags } from "@pages/tags";
import { ResourceUpdates } from "@pages/resource_update";
import { UsersPage } from "@pages/users";
import { UserPage, UsersPage } from "@pages/users";
import { AllResources } from "@pages/all_resources";
import { UserDisabled } from "@pages/user_disabled";
@@ -19,7 +19,6 @@ const router = createBrowserRouter([
element: <Layout />,
children: [
{ path: "", element: <Dashboard /> },
{ path: "users", element: <UsersPage /> },
{ path: "keys", element: <Keys /> },
{ path: "tags", element: <Tags /> },
{ path: "tree", element: <Tree /> },
@@ -32,6 +31,13 @@ const router = createBrowserRouter([
{ path: ":id/updates", element: <ResourceUpdates /> },
],
},
{
path: "users",
children: [
{ path: "", element: <UsersPage /> },
{ path: ":id", element: <UserPage /> },
],
},
],
},
]);
+4 -4
View File
@@ -19,15 +19,15 @@ import {
import { ReactNode } from "react";
interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[];
data: TData[];
columns?: ColumnDef<TData, TValue>[];
data?: TData[];
onRowClick?: (row: TData) => void;
noResults?: ReactNode;
}
export function DataTable<TData, TValue>({
columns,
data,
columns = [],
data = [],
onRowClick,
noResults,
}: DataTableProps<TData, TValue>) {