mirror of
https://github.com/fosrl/pangolin.git
synced 2025-07-29 23:25:58 +02:00
add info box to admin users table
This commit is contained in:
parent
189b739997
commit
3e94384cde
4 changed files with 310 additions and 271 deletions
|
@ -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>
|
||||
|
||||
|
|
|
@ -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} />
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue