add info box to admin users table

This commit is contained in:
miloschwartz 2025-04-17 21:21:41 -04:00
parent 189b739997
commit 3e94384cde
No known key found for this signature in database
4 changed files with 310 additions and 271 deletions

View file

@ -6,6 +6,7 @@ import {
SettingsSectionBody,
SettingsSectionDescription,
SettingsSectionForm,
SettingsSectionGrid,
SettingsSectionHeader,
SettingsSectionTitle
} from "@app/components/Settings";
@ -229,16 +230,15 @@ export default function Page() {
</SettingsSection>
{form.watch("type") === "oidc" && (
<>
<div className="grid md:grid-cols-2 gap-6">
<SettingsSectionGrid cols={2}>
<SettingsSection>
<SettingsSectionHeader>
<SettingsSectionTitle>
OAuth2/OIDC Configuration
</SettingsSectionTitle>
<SettingsSectionDescription>
Configure the OAuth2/OIDC provider
endpoints and credentials
Configure the OAuth2/OIDC provider endpoints
and credentials
</SettingsSectionDescription>
</SettingsSectionHeader>
<SettingsSectionBody>
@ -246,9 +246,7 @@ export default function Page() {
<form
className="space-y-4"
id="create-idp-form"
onSubmit={form.handleSubmit(
onSubmit
)}
onSubmit={form.handleSubmit(onSubmit)}
>
<FormField
control={form.control}
@ -286,9 +284,9 @@ export default function Page() {
/>
</FormControl>
<FormDescription>
The OAuth2 client
secret from your
identity provider
The OAuth2 client secret
from your identity
provider
</FormDescription>
<FormMessage />
</FormItem>
@ -310,8 +308,7 @@ export default function Page() {
/>
</FormControl>
<FormDescription>
The OAuth2
authorization
The OAuth2 authorization
endpoint URL
</FormDescription>
<FormMessage />
@ -350,12 +347,11 @@ export default function Page() {
Important Information
</AlertTitle>
<AlertDescription>
After creating the identity
provider, you will need to configure
the callback URL in your identity
provider's settings. The callback
URL will be provided after
successful creation.
After creating the identity provider,
you will need to configure the callback
URL in your identity provider's
settings. The callback URL will be
provided after successful creation.
</AlertDescription>
</Alert>
</SettingsSectionBody>
@ -367,8 +363,8 @@ export default function Page() {
Token Configuration
</SettingsSectionTitle>
<SettingsSectionDescription>
Configure how to extract user
information from the ID token
Configure how to extract user information
from the ID token
</SettingsSectionDescription>
</SettingsSectionHeader>
<SettingsSectionBody>
@ -376,9 +372,7 @@ export default function Page() {
<form
className="space-y-4"
id="create-idp-form"
onSubmit={form.handleSubmit(
onSubmit
)}
onSubmit={form.handleSubmit(onSubmit)}
>
<Alert variant="neutral">
<InfoIcon className="h-4 w-4" />
@ -387,16 +381,15 @@ export default function Page() {
</AlertTitle>
<AlertDescription>
The paths below use JMESPath
syntax to extract values
from the ID token.
syntax to extract values from
the ID token.
<a
href="https://jmespath.org"
target="_blank"
rel="noopener noreferrer"
className="text-primary hover:underline inline-flex items-center"
>
Learn more about
JMESPath{" "}
Learn more about JMESPath{" "}
<ExternalLink className="ml-1 h-4 w-4" />
</a>
</AlertDescription>
@ -414,9 +407,9 @@ export default function Page() {
<Input {...field} />
</FormControl>
<FormDescription>
The JMESPath to the
user identifier in
the ID token
The JMESPath to the user
identifier in the ID
token
</FormDescription>
<FormMessage />
</FormItem>
@ -429,16 +422,15 @@ export default function Page() {
render={({ field }) => (
<FormItem>
<FormLabel>
Email Path
(Optional)
Email Path (Optional)
</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormDescription>
The JMESPath to the
user's email in the
ID token
user's email in the ID
token
</FormDescription>
<FormMessage />
</FormItem>
@ -458,8 +450,8 @@ export default function Page() {
</FormControl>
<FormDescription>
The JMESPath to the
user's name in the
ID token
user's name in the ID
token
</FormDescription>
<FormMessage />
</FormItem>
@ -478,9 +470,8 @@ export default function Page() {
<Input {...field} />
</FormControl>
<FormDescription>
Space-separated list
of OAuth2 scopes to
request
Space-separated list of
OAuth2 scopes to request
</FormDescription>
<FormMessage />
</FormItem>
@ -490,8 +481,7 @@ export default function Page() {
</Form>
</SettingsSectionBody>
</SettingsSection>
</div>
</>
</SettingsSectionGrid>
)}
</SettingsContainer>

View file

@ -4,6 +4,8 @@ import { AxiosResponse } from "axios";
import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
import { AdminListUsersResponse } from "@server/routers/user/adminListUsers";
import UsersTable, { GlobalUserRow } from "./AdminUsersTable";
import { Alert, AlertDescription, AlertTitle } from "@app/components/ui/alert";
import { InfoIcon } from "lucide-react";
type PageProps = {
params: Promise<{ orgId: string }>;
@ -43,6 +45,13 @@ export default async function UsersPage(props: PageProps) {
title="Manage All Users"
description="View and manage all users in the system"
/>
<Alert variant="neutral" className="mb-6">
<InfoIcon className="h-4 w-4" />
<AlertTitle className="font-semibold">About User Management</AlertTitle>
<AlertDescription>
This table displays all root user objects in the system. Each user may belong to multiple organizations. Removing a user from an organization does not delete their root user object - they will remain in the system. To completely remove a user from the system, you must delete their root user object using the delete action in this table.
</AlertDescription>
</Alert>
<UsersTable users={userRows} />
</>
);

View file

@ -22,6 +22,7 @@ import { useEnvContext } from "@app/hooks/useEnvContext";
import { Breadcrumbs } from "@app/components/Breadcrumbs";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { useUserContext } from "@app/hooks/useUserContext";
interface LayoutProps {
children: React.ReactNode;
@ -57,6 +58,7 @@ export function Layout({
const { env } = useEnvContext();
const pathname = usePathname();
const isAdminPage = pathname?.startsWith("/admin");
const { user } = useUserContext();
return (
<div className="flex h-screen overflow-hidden">
@ -135,7 +137,7 @@ export function Layout({
<div className="flex-1">
<SidebarNav items={navItems} />
</div>
{!isAdminPage && (
{!isAdminPage && user.serverAdmin && (
<div className="mt-8 pt-4 border-t">
<Link
href="/admin"

View file

@ -1,31 +1,69 @@
export function SettingsContainer({ children }: { children: React.ReactNode }) {
return <div className="space-y-6">{children}</div>
return <div className="space-y-6">{children}</div>;
}
export function SettingsSection({ children }: { children: React.ReactNode }) {
return <div className="border rounded-lg bg-card p-5">{children}</div>
return <div className="border rounded-lg bg-card p-5">{children}</div>;
}
export function SettingsSectionHeader({ children }: { children: React.ReactNode }) {
return <div className="text-lg space-y-0.5 pb-6">{children}</div>
export function SettingsSectionHeader({
children
}: {
children: React.ReactNode;
}) {
return <div className="text-lg space-y-0.5 pb-6">{children}</div>;
}
export function SettingsSectionForm({ children }: { children: React.ReactNode }) {
return <div className="max-w-xl">{children}</div>
export function SettingsSectionForm({
children
}: {
children: React.ReactNode;
}) {
return <div className="max-w-xl">{children}</div>;
}
export function SettingsSectionTitle({ children }: { children: React.ReactNode }) {
return <h2 className="text-1xl font-bold tracking-tight flex items-center gap-2">{children}</h2>
export function SettingsSectionTitle({
children
}: {
children: React.ReactNode;
}) {
return (
<h2 className="text-1xl font-bold tracking-tight flex items-center gap-2">
{children}
</h2>
);
}
export function SettingsSectionDescription({ children }: { children: React.ReactNode }) {
return <p className="text-muted-foreground text-sm">{children}</p>
export function SettingsSectionDescription({
children
}: {
children: React.ReactNode;
}) {
return <p className="text-muted-foreground text-sm">{children}</p>;
}
export function SettingsSectionBody({ children }: { children: React.ReactNode }) {
return <div className="space-y-5">{children}</div>
export function SettingsSectionBody({
children
}: {
children: React.ReactNode;
}) {
return <div className="space-y-5">{children}</div>;
}
export function SettingsSectionFooter({ children }: { children: React.ReactNode }) {
return <div className="flex justify-end space-x-4 mt-8">{children}</div>
export function SettingsSectionFooter({
children
}: {
children: React.ReactNode;
}) {
return <div className="flex justify-end space-x-4 mt-8">{children}</div>;
}
export function SettingsSectionGrid({
children,
cols
}: {
children: React.ReactNode;
cols: number;
}) {
return <div className={`grid md:grid-cols-${cols} gap-6`}>{children}</div>;
}