mirror of
https://github.com/fosrl/pangolin.git
synced 2026-09-12 14:01:18 +02:00
add server side filter for server admin
This commit is contained in:
+7
-7
@@ -527,7 +527,7 @@
|
||||
"userAbount": "About User Management",
|
||||
"userAbountDescription": "This table displays all base user objects in the system. Each user may belong to multiple organizations. Removing a user from an organization does not delete their base user object. They will remain in the system. To completely remove a user from the system, you must delete their base user object using the delete action in this table.",
|
||||
"userServer": "Server Users",
|
||||
"userSearch": "Search server users...",
|
||||
"userSearch": "Search users...",
|
||||
"userErrorDelete": "Error deleting user",
|
||||
"userDeleteConfirm": "Confirm Delete User",
|
||||
"userDeleteServer": "Delete User from Server",
|
||||
@@ -1429,17 +1429,17 @@
|
||||
"promoteServerAdmin": "Promote to Server admin",
|
||||
"promoteServerAdminTitle": "Promote to Server Admin",
|
||||
"promoteServerAdminQuestion": "Are you sure you want to promote {selectedUser} to server admin?",
|
||||
"promoteServerAdminMessage": "Server admins have full access to every organization, user, and setting on this instance.",
|
||||
"promoteServerAdminWarning": "This can be undone at any time by demoting the user from this page.",
|
||||
"promoteServerAdminConfirm": "Promote to server admin",
|
||||
"promoteServerAdminSuccess": "User promoted",
|
||||
"promoteServerAdminMessage": "Server admins have the highest privileges and can manage the server.",
|
||||
"promoteServerAdminWarning": "This can be undone at any time by demoting the user.",
|
||||
"promoteServerAdminConfirm": "Promote to Server Admin",
|
||||
"promoteServerAdminSuccess": "User Promoted",
|
||||
"promoteServerAdminSuccessDescription": "{selectedUser} is now a server admin.",
|
||||
"promoteServerAdminError": "Failed to promote user",
|
||||
"demoteServerAdmin": "Demote from Server admin",
|
||||
"demoteServerAdminTitle": "Demote from Server Admin",
|
||||
"demoteServerAdminQuestion": "Are you sure you want to demote {selectedUser} from server admin?",
|
||||
"demoteServerAdminMessage": "{selectedUser} will lose full access to every organization, user, and setting on this instance.",
|
||||
"demoteServerAdminWarning": "This can be undone at any time by promoting the user from this page.",
|
||||
"demoteServerAdminMessage": "{selectedUser} will lose all server admin privileges.",
|
||||
"demoteServerAdminWarning": "This can be undone at any time by promoting the user.",
|
||||
"demoteServerAdminConfirm": "Demote from server admin",
|
||||
"demoteServerAdminSuccess": "User demoted",
|
||||
"demoteServerAdminSuccessDescription": "{selectedUser} is no longer a server admin.",
|
||||
|
||||
@@ -88,6 +88,15 @@ const listUsersSchema = z.strictObject({
|
||||
type: "boolean",
|
||||
description:
|
||||
"Filter by 2FA state matching: enabled if twoFactorEnabled or twoFactorSetupRequested"
|
||||
}),
|
||||
server_admin: z
|
||||
.enum(["true", "false"])
|
||||
.transform((v) => v === "true")
|
||||
.optional()
|
||||
.catch(undefined)
|
||||
.openapi({
|
||||
type: "boolean",
|
||||
description: "Filter by server admin status"
|
||||
})
|
||||
});
|
||||
|
||||
@@ -177,7 +186,8 @@ export async function adminListUsers(
|
||||
sort_by,
|
||||
order,
|
||||
idp_id,
|
||||
two_factor: twoFactorFilter
|
||||
two_factor: twoFactorFilter,
|
||||
server_admin: serverAdminFilter
|
||||
} = parsedQuery.data;
|
||||
|
||||
if (typeof idp_id === "number") {
|
||||
@@ -233,6 +243,10 @@ export async function adminListUsers(
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof serverAdminFilter === "boolean") {
|
||||
conditions.push(eq(users.serverAdmin, serverAdminFilter));
|
||||
}
|
||||
|
||||
const whereClause = and(...conditions);
|
||||
|
||||
const countQuery = db.$count(
|
||||
|
||||
@@ -31,7 +31,8 @@ const AdminSetServerAdminResponseDataSchema = z.object({
|
||||
registry.registerPath({
|
||||
method: "post",
|
||||
path: "/user/{userId}/server-admin",
|
||||
description: "Promote or demote a user's server admin status (server admin).",
|
||||
description:
|
||||
"Promote or demote a user's server admin status (server admin).",
|
||||
tags: [OpenAPITags.User],
|
||||
request: {
|
||||
params: setServerAdminParamsSchema,
|
||||
@@ -63,9 +64,7 @@ export async function adminSetServerAdmin(
|
||||
next: NextFunction
|
||||
): Promise<any> {
|
||||
try {
|
||||
const parsedParams = setServerAdminParamsSchema.safeParse(
|
||||
req.params
|
||||
);
|
||||
const parsedParams = setServerAdminParamsSchema.safeParse(req.params);
|
||||
if (!parsedParams.success) {
|
||||
return next(
|
||||
createHttpError(
|
||||
@@ -91,7 +90,8 @@ export async function adminSetServerAdmin(
|
||||
const [existingUser] = await db
|
||||
.select({
|
||||
userId: users.userId,
|
||||
serverAdmin: users.serverAdmin
|
||||
serverAdmin: users.serverAdmin,
|
||||
type: users.type
|
||||
})
|
||||
.from(users)
|
||||
.where(eq(users.userId, userId))
|
||||
@@ -101,6 +101,15 @@ export async function adminSetServerAdmin(
|
||||
return next(createHttpError(HttpCode.NOT_FOUND, "User not found"));
|
||||
}
|
||||
|
||||
if (existingUser.type !== "internal") {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
"Server admin status can only be changed for internal users"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (!serverAdmin && req.user?.userId === userId) {
|
||||
return next(
|
||||
createHttpError(
|
||||
|
||||
@@ -44,7 +44,6 @@ import {
|
||||
CredenzaClose
|
||||
} from "@app/components/Credenza";
|
||||
import CopyToClipboard from "@app/components/CopyToClipboard";
|
||||
import { Badge } from "./ui/badge";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
@@ -123,6 +122,11 @@ export default function UsersTable({
|
||||
.optional()
|
||||
.catch(undefined);
|
||||
|
||||
const serverAdminFilterSchema = z
|
||||
.enum(["true", "false"])
|
||||
.optional()
|
||||
.catch(undefined);
|
||||
|
||||
function handleFilterChange(
|
||||
column: string,
|
||||
value: string | undefined | null
|
||||
@@ -310,19 +314,16 @@ export default function UsersTable({
|
||||
</>
|
||||
)}
|
||||
{row.original.serverAdmin && (
|
||||
<>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<ShieldUserIcon className="text-primary size-5 flex-none" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{t("serverAdmin")}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
{/* <Badge>{t("serverAdmin")}</Badge> */}
|
||||
</>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<ShieldUserIcon className="text-primary size-4 flex-none" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{t("serverAdmin")}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
)}
|
||||
</span>
|
||||
)
|
||||
@@ -431,6 +432,37 @@ export default function UsersTable({
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorKey: "serverAdmin",
|
||||
friendlyName: t("serverAdmin"),
|
||||
header: () => (
|
||||
<ColumnFilterButton
|
||||
options={[
|
||||
{ value: "true", label: t("yes") },
|
||||
{ value: "false", label: t("no") }
|
||||
]}
|
||||
selectedValue={serverAdminFilterSchema.parse(
|
||||
searchParams.get("server_admin") ?? undefined
|
||||
)}
|
||||
onValueChange={(value) =>
|
||||
handleFilterChange("server_admin", value)
|
||||
}
|
||||
searchPlaceholder={t("searchPlaceholder")}
|
||||
emptyMessage={t("emptySearchOptions")}
|
||||
label={t("serverAdmin")}
|
||||
className="p-3"
|
||||
/>
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
<span>
|
||||
{row.original.serverAdmin ? (
|
||||
<span>{t("yes")}</span>
|
||||
) : (
|
||||
<span>{t("no")}</span>
|
||||
)}
|
||||
</span>
|
||||
)
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
enableHiding: false,
|
||||
@@ -459,7 +491,7 @@ export default function UsersTable({
|
||||
{t("generatePasswordResetCode")}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{!r.serverAdmin && (
|
||||
{r.type === "internal" && !r.serverAdmin && (
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
setPromoting(r);
|
||||
@@ -469,16 +501,18 @@ export default function UsersTable({
|
||||
{t("promoteServerAdmin")}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{r.serverAdmin && r.id !== user.user.userId && (
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
setDemoting(r);
|
||||
setIsDemoteModalOpen(true);
|
||||
}}
|
||||
>
|
||||
{t("demoteServerAdmin")}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{r.type === "internal" &&
|
||||
r.serverAdmin &&
|
||||
r.id !== user.user.userId && (
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
setDemoting(r);
|
||||
setIsDemoteModalOpen(true);
|
||||
}}
|
||||
>
|
||||
{t("demoteServerAdmin")}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
setSelected(r);
|
||||
|
||||
Reference in New Issue
Block a user