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",
|
"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.",
|
"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",
|
"userServer": "Server Users",
|
||||||
"userSearch": "Search server users...",
|
"userSearch": "Search users...",
|
||||||
"userErrorDelete": "Error deleting user",
|
"userErrorDelete": "Error deleting user",
|
||||||
"userDeleteConfirm": "Confirm Delete User",
|
"userDeleteConfirm": "Confirm Delete User",
|
||||||
"userDeleteServer": "Delete User from Server",
|
"userDeleteServer": "Delete User from Server",
|
||||||
@@ -1429,17 +1429,17 @@
|
|||||||
"promoteServerAdmin": "Promote to Server admin",
|
"promoteServerAdmin": "Promote to Server admin",
|
||||||
"promoteServerAdminTitle": "Promote to Server Admin",
|
"promoteServerAdminTitle": "Promote to Server Admin",
|
||||||
"promoteServerAdminQuestion": "Are you sure you want to promote {selectedUser} 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.",
|
"promoteServerAdminMessage": "Server admins have the highest privileges and can manage the server.",
|
||||||
"promoteServerAdminWarning": "This can be undone at any time by demoting the user from this page.",
|
"promoteServerAdminWarning": "This can be undone at any time by demoting the user.",
|
||||||
"promoteServerAdminConfirm": "Promote to server admin",
|
"promoteServerAdminConfirm": "Promote to Server Admin",
|
||||||
"promoteServerAdminSuccess": "User promoted",
|
"promoteServerAdminSuccess": "User Promoted",
|
||||||
"promoteServerAdminSuccessDescription": "{selectedUser} is now a server admin.",
|
"promoteServerAdminSuccessDescription": "{selectedUser} is now a server admin.",
|
||||||
"promoteServerAdminError": "Failed to promote user",
|
"promoteServerAdminError": "Failed to promote user",
|
||||||
"demoteServerAdmin": "Demote from Server admin",
|
"demoteServerAdmin": "Demote from Server admin",
|
||||||
"demoteServerAdminTitle": "Demote from Server Admin",
|
"demoteServerAdminTitle": "Demote from Server Admin",
|
||||||
"demoteServerAdminQuestion": "Are you sure you want to demote {selectedUser} 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.",
|
"demoteServerAdminMessage": "{selectedUser} will lose all server admin privileges.",
|
||||||
"demoteServerAdminWarning": "This can be undone at any time by promoting the user from this page.",
|
"demoteServerAdminWarning": "This can be undone at any time by promoting the user.",
|
||||||
"demoteServerAdminConfirm": "Demote from server admin",
|
"demoteServerAdminConfirm": "Demote from server admin",
|
||||||
"demoteServerAdminSuccess": "User demoted",
|
"demoteServerAdminSuccess": "User demoted",
|
||||||
"demoteServerAdminSuccessDescription": "{selectedUser} is no longer a server admin.",
|
"demoteServerAdminSuccessDescription": "{selectedUser} is no longer a server admin.",
|
||||||
|
|||||||
@@ -88,6 +88,15 @@ const listUsersSchema = z.strictObject({
|
|||||||
type: "boolean",
|
type: "boolean",
|
||||||
description:
|
description:
|
||||||
"Filter by 2FA state matching: enabled if twoFactorEnabled or twoFactorSetupRequested"
|
"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,
|
sort_by,
|
||||||
order,
|
order,
|
||||||
idp_id,
|
idp_id,
|
||||||
two_factor: twoFactorFilter
|
two_factor: twoFactorFilter,
|
||||||
|
server_admin: serverAdminFilter
|
||||||
} = parsedQuery.data;
|
} = parsedQuery.data;
|
||||||
|
|
||||||
if (typeof idp_id === "number") {
|
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 whereClause = and(...conditions);
|
||||||
|
|
||||||
const countQuery = db.$count(
|
const countQuery = db.$count(
|
||||||
|
|||||||
@@ -31,7 +31,8 @@ const AdminSetServerAdminResponseDataSchema = z.object({
|
|||||||
registry.registerPath({
|
registry.registerPath({
|
||||||
method: "post",
|
method: "post",
|
||||||
path: "/user/{userId}/server-admin",
|
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],
|
tags: [OpenAPITags.User],
|
||||||
request: {
|
request: {
|
||||||
params: setServerAdminParamsSchema,
|
params: setServerAdminParamsSchema,
|
||||||
@@ -63,9 +64,7 @@ export async function adminSetServerAdmin(
|
|||||||
next: NextFunction
|
next: NextFunction
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
try {
|
try {
|
||||||
const parsedParams = setServerAdminParamsSchema.safeParse(
|
const parsedParams = setServerAdminParamsSchema.safeParse(req.params);
|
||||||
req.params
|
|
||||||
);
|
|
||||||
if (!parsedParams.success) {
|
if (!parsedParams.success) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
@@ -91,7 +90,8 @@ export async function adminSetServerAdmin(
|
|||||||
const [existingUser] = await db
|
const [existingUser] = await db
|
||||||
.select({
|
.select({
|
||||||
userId: users.userId,
|
userId: users.userId,
|
||||||
serverAdmin: users.serverAdmin
|
serverAdmin: users.serverAdmin,
|
||||||
|
type: users.type
|
||||||
})
|
})
|
||||||
.from(users)
|
.from(users)
|
||||||
.where(eq(users.userId, userId))
|
.where(eq(users.userId, userId))
|
||||||
@@ -101,6 +101,15 @@ export async function adminSetServerAdmin(
|
|||||||
return next(createHttpError(HttpCode.NOT_FOUND, "User not found"));
|
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) {
|
if (!serverAdmin && req.user?.userId === userId) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ import {
|
|||||||
CredenzaClose
|
CredenzaClose
|
||||||
} from "@app/components/Credenza";
|
} from "@app/components/Credenza";
|
||||||
import CopyToClipboard from "@app/components/CopyToClipboard";
|
import CopyToClipboard from "@app/components/CopyToClipboard";
|
||||||
import { Badge } from "./ui/badge";
|
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
TooltipContent,
|
TooltipContent,
|
||||||
@@ -123,6 +122,11 @@ export default function UsersTable({
|
|||||||
.optional()
|
.optional()
|
||||||
.catch(undefined);
|
.catch(undefined);
|
||||||
|
|
||||||
|
const serverAdminFilterSchema = z
|
||||||
|
.enum(["true", "false"])
|
||||||
|
.optional()
|
||||||
|
.catch(undefined);
|
||||||
|
|
||||||
function handleFilterChange(
|
function handleFilterChange(
|
||||||
column: string,
|
column: string,
|
||||||
value: string | undefined | null
|
value: string | undefined | null
|
||||||
@@ -310,19 +314,16 @@ export default function UsersTable({
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{row.original.serverAdmin && (
|
{row.original.serverAdmin && (
|
||||||
<>
|
<TooltipProvider>
|
||||||
<TooltipProvider>
|
<Tooltip>
|
||||||
<Tooltip>
|
<TooltipTrigger asChild>
|
||||||
<TooltipTrigger asChild>
|
<ShieldUserIcon className="text-primary size-4 flex-none" />
|
||||||
<ShieldUserIcon className="text-primary size-5 flex-none" />
|
</TooltipTrigger>
|
||||||
</TooltipTrigger>
|
<TooltipContent>
|
||||||
<TooltipContent>
|
{t("serverAdmin")}
|
||||||
{t("serverAdmin")}
|
</TooltipContent>
|
||||||
</TooltipContent>
|
</Tooltip>
|
||||||
</Tooltip>
|
</TooltipProvider>
|
||||||
</TooltipProvider>
|
|
||||||
{/* <Badge>{t("serverAdmin")}</Badge> */}
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
</span>
|
</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",
|
id: "actions",
|
||||||
enableHiding: false,
|
enableHiding: false,
|
||||||
@@ -459,7 +491,7 @@ export default function UsersTable({
|
|||||||
{t("generatePasswordResetCode")}
|
{t("generatePasswordResetCode")}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
)}
|
)}
|
||||||
{!r.serverAdmin && (
|
{r.type === "internal" && !r.serverAdmin && (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setPromoting(r);
|
setPromoting(r);
|
||||||
@@ -469,16 +501,18 @@ export default function UsersTable({
|
|||||||
{t("promoteServerAdmin")}
|
{t("promoteServerAdmin")}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
)}
|
)}
|
||||||
{r.serverAdmin && r.id !== user.user.userId && (
|
{r.type === "internal" &&
|
||||||
<DropdownMenuItem
|
r.serverAdmin &&
|
||||||
onClick={() => {
|
r.id !== user.user.userId && (
|
||||||
setDemoting(r);
|
<DropdownMenuItem
|
||||||
setIsDemoteModalOpen(true);
|
onClick={() => {
|
||||||
}}
|
setDemoting(r);
|
||||||
>
|
setIsDemoteModalOpen(true);
|
||||||
{t("demoteServerAdmin")}
|
}}
|
||||||
</DropdownMenuItem>
|
>
|
||||||
)}
|
{t("demoteServerAdmin")}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)}
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSelected(r);
|
setSelected(r);
|
||||||
|
|||||||
Reference in New Issue
Block a user