mirror of
https://github.com/moghtech/komodo.git
synced 2026-10-29 00:00:40 +00:00
ListUserPermissions
This commit is contained in:
@@ -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),
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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::*;
|
||||
|
||||
@@ -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>;
|
||||
@@ -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
@@ -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 }
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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 /> },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -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>) {
|
||||
|
||||
Reference in New Issue
Block a user