mirror of
https://github.com/fosrl/pangolin.git
synced 2026-03-08 03:36:37 +00:00
I18n admin (#22)
* New translation keys in en-US locale * New translation keys in de-DE locale * New translation keys in fr-FR locale * New translation keys in it-IT locale * New translation keys in pl-PL locale * New translation keys in pt-PT locale * New translation keys in tr-TR locale * Add translation keys in app/admin * Fix build --------- Co-authored-by: Lokowitz <marvinlokowitz@gmail.com>
This commit is contained in:
@@ -45,10 +45,8 @@ import { Badge } from "@app/components/ui/badge";
|
||||
import { useLicenseStatusContext } from "@app/hooks/useLicenseStatusContext";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
const t = useTranslations();
|
||||
|
||||
const GeneralFormSchema = z.object({
|
||||
name: z.string().min(2, { message: t('nameMin', {len: 2}) }),
|
||||
name: z.string().min(2, "Name must be at least 2 characters."),
|
||||
clientId: z.string().min(1, { message: "Client ID is required." }),
|
||||
clientSecret: z.string().min(1, { message: "Client Secret is required." }),
|
||||
authUrl: z.string().url({ message: "Auth URL must be a valid URL." }),
|
||||
@@ -91,6 +89,8 @@ export default function GeneralPage() {
|
||||
}
|
||||
});
|
||||
|
||||
const t = useTranslations();
|
||||
|
||||
useEffect(() => {
|
||||
const loadIdp = async () => {
|
||||
try {
|
||||
@@ -112,7 +112,7 @@ export default function GeneralPage() {
|
||||
}
|
||||
} catch (e) {
|
||||
toast({
|
||||
title: "Error",
|
||||
title: t('error'),
|
||||
description: formatAxiosError(e),
|
||||
variant: "destructive"
|
||||
});
|
||||
@@ -172,18 +172,17 @@ export default function GeneralPage() {
|
||||
<SettingsSection>
|
||||
<SettingsSectionHeader>
|
||||
<SettingsSectionTitle>
|
||||
General Information
|
||||
{t('idpTitle')}
|
||||
</SettingsSectionTitle>
|
||||
<SettingsSectionDescription>
|
||||
Configure the basic information for your identity
|
||||
provider
|
||||
{t('idpSettingsDescription')}
|
||||
</SettingsSectionDescription>
|
||||
</SettingsSectionHeader>
|
||||
<SettingsSectionBody>
|
||||
<InfoSections cols={3}>
|
||||
<InfoSection>
|
||||
<InfoSectionTitle>
|
||||
Redirect URL
|
||||
{t('redirectUrl')}
|
||||
</InfoSectionTitle>
|
||||
<InfoSectionContent>
|
||||
<CopyToClipboard text={redirectUrl} />
|
||||
@@ -194,13 +193,10 @@ export default function GeneralPage() {
|
||||
<Alert variant="neutral" className="">
|
||||
<InfoIcon className="h-4 w-4" />
|
||||
<AlertTitle className="font-semibold">
|
||||
About Redirect URL
|
||||
{t('redirectUrlAbout')}
|
||||
</AlertTitle>
|
||||
<AlertDescription>
|
||||
This is the URL to which users will be
|
||||
redirected after authentication. You need to
|
||||
configure this URL in your identity provider
|
||||
settings.
|
||||
{t('redirectUrlAboutDescription')}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
<SettingsSectionForm>
|
||||
@@ -215,13 +211,12 @@ export default function GeneralPage() {
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormLabel>{t('name')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
A display name for this
|
||||
identity provider
|
||||
{t('idpDisplayName')}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -231,7 +226,7 @@ export default function GeneralPage() {
|
||||
<div className="flex items-start mb-0">
|
||||
<SwitchInput
|
||||
id="auto-provision-toggle"
|
||||
label="Auto Provision Users"
|
||||
label={t('idpAutoProvisionUsers')}
|
||||
defaultChecked={form.getValues(
|
||||
"autoProvision"
|
||||
)}
|
||||
@@ -244,10 +239,7 @@ export default function GeneralPage() {
|
||||
/>
|
||||
</div>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
When enabled, users will be
|
||||
automatically created in the system upon
|
||||
first login with the ability to map
|
||||
users to roles and organizations.
|
||||
{t('idpAutoProvisionUsersDescription')}
|
||||
</span>
|
||||
</form>
|
||||
</Form>
|
||||
@@ -259,11 +251,10 @@ export default function GeneralPage() {
|
||||
<SettingsSection>
|
||||
<SettingsSectionHeader>
|
||||
<SettingsSectionTitle>
|
||||
OAuth2/OIDC Configuration
|
||||
{t('idpOidcConfigure')}
|
||||
</SettingsSectionTitle>
|
||||
<SettingsSectionDescription>
|
||||
Configure the OAuth2/OIDC provider endpoints and
|
||||
credentials
|
||||
{t('idpOidcConfigureDescription')}
|
||||
</SettingsSectionDescription>
|
||||
</SettingsSectionHeader>
|
||||
<SettingsSectionBody>
|
||||
@@ -280,15 +271,13 @@ export default function GeneralPage() {
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
Client ID
|
||||
{t('idpClientId')}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
The OAuth2 client ID
|
||||
from your identity
|
||||
provider
|
||||
{t('idpClientIdDescription')}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -301,7 +290,7 @@ export default function GeneralPage() {
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
Client Secret
|
||||
{t('idpClientSecret')}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
@@ -310,9 +299,7 @@ export default function GeneralPage() {
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
The OAuth2 client secret
|
||||
from your identity
|
||||
provider
|
||||
{t('idpClientSecretDescription')}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -325,14 +312,13 @@ export default function GeneralPage() {
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
Authorization URL
|
||||
{t('idpAuthUrl')}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
The OAuth2 authorization
|
||||
endpoint URL
|
||||
{t('idpAuthUrlDescription')}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -345,14 +331,13 @@ export default function GeneralPage() {
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
Token URL
|
||||
{t('idpTokenUrl')}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
The OAuth2 token
|
||||
endpoint URL
|
||||
{t('idpTokenUrlDescription')}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -367,11 +352,10 @@ export default function GeneralPage() {
|
||||
<SettingsSection>
|
||||
<SettingsSectionHeader>
|
||||
<SettingsSectionTitle>
|
||||
Token Configuration
|
||||
{t('idpToken')}
|
||||
</SettingsSectionTitle>
|
||||
<SettingsSectionDescription>
|
||||
Configure how to extract user information from
|
||||
the ID token
|
||||
{t('idpTokenDescription')}
|
||||
</SettingsSectionDescription>
|
||||
</SettingsSectionHeader>
|
||||
<SettingsSectionBody>
|
||||
@@ -385,19 +369,18 @@ export default function GeneralPage() {
|
||||
<Alert variant="neutral">
|
||||
<InfoIcon className="h-4 w-4" />
|
||||
<AlertTitle className="font-semibold">
|
||||
About JMESPath
|
||||
{t('idpJmespathAbout')}
|
||||
</AlertTitle>
|
||||
<AlertDescription>
|
||||
The paths below use JMESPath
|
||||
syntax to extract values from
|
||||
the ID token.
|
||||
{/*TODO(vlalx): Validate replacing */}
|
||||
{t('idpJmespathAboutDescription')}
|
||||
<a
|
||||
href="https://jmespath.org"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-primary hover:underline inline-flex items-center"
|
||||
>
|
||||
Learn more about JMESPath{" "}
|
||||
{t('idpJmespathAboutDescriptionLink')}{" "}
|
||||
<ExternalLink className="ml-1 h-4 w-4" />
|
||||
</a>
|
||||
</AlertDescription>
|
||||
@@ -409,15 +392,13 @@ export default function GeneralPage() {
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
Identifier Path
|
||||
{t('idpJmespathLabel')}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
The JMESPath to the user
|
||||
identifier in the ID
|
||||
token
|
||||
{t('idpJmespathLabelDescription')}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -430,15 +411,13 @@ export default function GeneralPage() {
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
Email Path (Optional)
|
||||
{t('idpJmespathEmailPathOptional')}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
The JMESPath to the
|
||||
user's email in the ID
|
||||
token
|
||||
{t('idpJmespathEmailPathOptionalDescription')}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -451,15 +430,13 @@ export default function GeneralPage() {
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
Name Path (Optional)
|
||||
{t('idpJmespathNamePathOptional')}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
The JMESPath to the
|
||||
user's name in the ID
|
||||
token
|
||||
{t('idpJmespathNamePathOptionalDescription')}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -472,14 +449,13 @@ export default function GeneralPage() {
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
Scopes
|
||||
{t('idpOidcConfigureScopes')}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
Space-separated list of
|
||||
OAuth2 scopes to request
|
||||
{t('idpOidcConfigureScopesDescription')}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -500,7 +476,7 @@ export default function GeneralPage() {
|
||||
loading={loading}
|
||||
disabled={loading}
|
||||
>
|
||||
Save General Settings
|
||||
{t('saveGeneralSettings')}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator
|
||||
} from "@app/components/ui/breadcrumb";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
interface SettingsLayoutProps {
|
||||
children: React.ReactNode;
|
||||
@@ -36,13 +37,15 @@ export default async function SettingsLayout(props: SettingsLayoutProps) {
|
||||
redirect("/admin/idp");
|
||||
}
|
||||
|
||||
const t = useTranslations();
|
||||
|
||||
const navItems: HorizontalTabs = [
|
||||
{
|
||||
title: "General",
|
||||
title: t('general'),
|
||||
href: `/admin/idp/${params.idpId}/general`
|
||||
},
|
||||
{
|
||||
title: "Organization Policies",
|
||||
title: t('orgPolicies'),
|
||||
href: `/admin/idp/${params.idpId}/policies`
|
||||
}
|
||||
];
|
||||
@@ -50,8 +53,8 @@ export default async function SettingsLayout(props: SettingsLayoutProps) {
|
||||
return (
|
||||
<>
|
||||
<SettingsSectionTitle
|
||||
title={`${idp.idp.name} Settings`}
|
||||
description="Configure the settings for your identity provider"
|
||||
title={t('idpSettings', { idpName: idp?.idp.name })}
|
||||
description={t('idpSettingsDescription')}
|
||||
/>
|
||||
|
||||
<div className="space-y-6">
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { ColumnDef } from "@tanstack/react-table";
|
||||
import { DataTable } from "@app/components/ui/data-table";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
interface DataTableProps<TData, TValue> {
|
||||
columns: ColumnDef<TData, TValue>[];
|
||||
@@ -14,14 +15,15 @@ export function PolicyDataTable<TData, TValue>({
|
||||
data,
|
||||
onAdd
|
||||
}: DataTableProps<TData, TValue>) {
|
||||
const t = useTranslations();
|
||||
return (
|
||||
<DataTable
|
||||
columns={columns}
|
||||
data={data}
|
||||
title="Organization Policies"
|
||||
searchPlaceholder="Search organization policies..."
|
||||
title={t('orgPolicies')}
|
||||
searchPlaceholder={t('orgPoliciesSearch')}
|
||||
searchColumn="orgId"
|
||||
addButtonText="Add Organization Policy"
|
||||
addButtonText={t('orgPoliciesAdd')}
|
||||
onAdd={onAdd}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
} from "@app/components/ui/dropdown-menu";
|
||||
import Link from "next/link";
|
||||
import { InfoPopup } from "@app/components/ui/info-popup";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
export interface PolicyRow {
|
||||
orgId: string;
|
||||
@@ -34,6 +35,7 @@ interface Props {
|
||||
}
|
||||
|
||||
export default function PolicyTable({ policies, onDelete, onAdd, onEdit }: Props) {
|
||||
const t = useTranslations();
|
||||
const columns: ColumnDef<PolicyRow>[] = [
|
||||
{
|
||||
id: "dots",
|
||||
@@ -44,7 +46,7 @@ export default function PolicyTable({ policies, onDelete, onAdd, onEdit }: Props
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" className="h-8 w-8 p-0">
|
||||
<span className="sr-only">Open menu</span>
|
||||
<span className="sr-only">{t('openMenu')}</span>
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
@@ -54,7 +56,7 @@ export default function PolicyTable({ policies, onDelete, onAdd, onEdit }: Props
|
||||
onDelete(r.orgId);
|
||||
}}
|
||||
>
|
||||
<span className="text-red-500">Delete</span>
|
||||
<span className="text-red-500">{t('delete')}</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
@@ -71,7 +73,7 @@ export default function PolicyTable({ policies, onDelete, onAdd, onEdit }: Props
|
||||
column.toggleSorting(column.getIsSorted() === "asc")
|
||||
}
|
||||
>
|
||||
Organization ID
|
||||
{t('orgId')}
|
||||
<ArrowUpDown className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
);
|
||||
@@ -87,7 +89,7 @@ export default function PolicyTable({ policies, onDelete, onAdd, onEdit }: Props
|
||||
column.toggleSorting(column.getIsSorted() === "asc")
|
||||
}
|
||||
>
|
||||
Role Mapping
|
||||
{t('roleMapping')}
|
||||
<ArrowUpDown className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
);
|
||||
@@ -114,7 +116,7 @@ export default function PolicyTable({ policies, onDelete, onAdd, onEdit }: Props
|
||||
column.toggleSorting(column.getIsSorted() === "asc")
|
||||
}
|
||||
>
|
||||
Organization Mapping
|
||||
{t('orgMapping')}
|
||||
<ArrowUpDown className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
);
|
||||
@@ -142,7 +144,7 @@ export default function PolicyTable({ policies, onDelete, onAdd, onEdit }: Props
|
||||
className="ml-2"
|
||||
onClick={() => onEdit(policy)}
|
||||
>
|
||||
Edit
|
||||
{t('edit')}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -63,6 +63,7 @@ import {
|
||||
SettingsSectionFooter,
|
||||
SettingsSectionForm
|
||||
} from "@app/components/Settings";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
type Organization = {
|
||||
orgId: string;
|
||||
@@ -117,6 +118,8 @@ export default function PoliciesPage() {
|
||||
}
|
||||
});
|
||||
|
||||
const t = useTranslations();
|
||||
|
||||
const loadIdp = async () => {
|
||||
try {
|
||||
const res = await api.get<AxiosResponse<GetIdpResponse>>(
|
||||
@@ -131,7 +134,7 @@ export default function PoliciesPage() {
|
||||
}
|
||||
} catch (e) {
|
||||
toast({
|
||||
title: "Error",
|
||||
title: t('error'),
|
||||
description: formatAxiosError(e),
|
||||
variant: "destructive"
|
||||
});
|
||||
@@ -146,7 +149,7 @@ export default function PoliciesPage() {
|
||||
}
|
||||
} catch (e) {
|
||||
toast({
|
||||
title: "Error",
|
||||
title: t('error'),
|
||||
description: formatAxiosError(e),
|
||||
variant: "destructive"
|
||||
});
|
||||
@@ -165,7 +168,7 @@ export default function PoliciesPage() {
|
||||
}
|
||||
} catch (e) {
|
||||
toast({
|
||||
title: "Error",
|
||||
title: t('error'),
|
||||
description: formatAxiosError(e),
|
||||
variant: "destructive"
|
||||
});
|
||||
@@ -200,15 +203,15 @@ export default function PoliciesPage() {
|
||||
};
|
||||
setPolicies([...policies, newPolicy]);
|
||||
toast({
|
||||
title: "Success",
|
||||
description: "Policy added successfully"
|
||||
title: t('success'),
|
||||
description: t('orgPolicyAddedDescription')
|
||||
});
|
||||
setShowAddDialog(false);
|
||||
form.reset();
|
||||
}
|
||||
} catch (e) {
|
||||
toast({
|
||||
title: "Error",
|
||||
title: t('error'),
|
||||
description: formatAxiosError(e),
|
||||
variant: "destructive"
|
||||
});
|
||||
@@ -242,8 +245,8 @@ export default function PoliciesPage() {
|
||||
)
|
||||
);
|
||||
toast({
|
||||
title: "Success",
|
||||
description: "Policy updated successfully"
|
||||
title: t('success'),
|
||||
description: t('orgPolicyUpdatedDescription')
|
||||
});
|
||||
setShowAddDialog(false);
|
||||
setEditingPolicy(null);
|
||||
@@ -251,7 +254,7 @@ export default function PoliciesPage() {
|
||||
}
|
||||
} catch (e) {
|
||||
toast({
|
||||
title: "Error",
|
||||
title: t('error'),
|
||||
description: formatAxiosError(e),
|
||||
variant: "destructive"
|
||||
});
|
||||
@@ -269,13 +272,13 @@ export default function PoliciesPage() {
|
||||
policies.filter((policy) => policy.orgId !== orgId)
|
||||
);
|
||||
toast({
|
||||
title: "Success",
|
||||
description: "Policy deleted successfully"
|
||||
title: t('success'),
|
||||
description: t('orgPolicyDeletedDescription')
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
toast({
|
||||
title: "Error",
|
||||
title: t('error'),
|
||||
description: formatAxiosError(e),
|
||||
variant: "destructive"
|
||||
});
|
||||
@@ -293,13 +296,13 @@ export default function PoliciesPage() {
|
||||
});
|
||||
if (res.status === 200) {
|
||||
toast({
|
||||
title: "Success",
|
||||
description: "Default mappings updated successfully"
|
||||
title: t('success'),
|
||||
description: t('defaultMappingsUpdatedDescription')
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
toast({
|
||||
title: "Error",
|
||||
title: t('error'),
|
||||
description: formatAxiosError(e),
|
||||
variant: "destructive"
|
||||
});
|
||||
@@ -318,21 +321,18 @@ export default function PoliciesPage() {
|
||||
<Alert variant="neutral" className="mb-6">
|
||||
<InfoIcon className="h-4 w-4" />
|
||||
<AlertTitle className="font-semibold">
|
||||
About Organization Policies
|
||||
{t('orgPoliciesAbout')}
|
||||
</AlertTitle>
|
||||
<AlertDescription>
|
||||
Organization policies are used to control access to
|
||||
organizations based on the user's ID token. You can
|
||||
specify JMESPath expressions to extract role and
|
||||
organization information from the ID token. For more
|
||||
information, see{" "}
|
||||
{/*TODO(vlalx): Validate replacing */}
|
||||
{t('orgPoliciesAboutDescription')}{" "}
|
||||
<Link
|
||||
href="https://docs.fossorial.io/Pangolin/Identity%20Providers/auto-provision"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-primary hover:underline"
|
||||
>
|
||||
the documentation
|
||||
{t('orgPoliciesAboutDescriptionLink')}
|
||||
<ExternalLink className="ml-1 h-4 w-4 inline" />
|
||||
</Link>
|
||||
</AlertDescription>
|
||||
@@ -341,13 +341,10 @@ export default function PoliciesPage() {
|
||||
<SettingsSection>
|
||||
<SettingsSectionHeader>
|
||||
<SettingsSectionTitle>
|
||||
Default Mappings (Optional)
|
||||
{t('defaultMappingsOptional')}
|
||||
</SettingsSectionTitle>
|
||||
<SettingsSectionDescription>
|
||||
The default mappings are used when when there is not
|
||||
an organization policy defined for an organization.
|
||||
You can specify the default role and organization
|
||||
mappings to fall back to here.
|
||||
{t('defaultMappingsOptionalDescription')}
|
||||
</SettingsSectionDescription>
|
||||
</SettingsSectionHeader>
|
||||
<SettingsSectionBody>
|
||||
@@ -366,16 +363,13 @@ export default function PoliciesPage() {
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
Default Role Mapping
|
||||
{t('defaultMappingsRole')}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
The result of this
|
||||
expression must return the
|
||||
role name as defined in the
|
||||
organization as a string.
|
||||
{t('defaultMappingsRoleDescription')}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -388,16 +382,13 @@ export default function PoliciesPage() {
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
Default Organization Mapping
|
||||
{t('defaultMappingsOrg')}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
This expression must return
|
||||
thr org ID or true for the
|
||||
user to be allowed to access
|
||||
the organization.
|
||||
{t('defaultMappingsOrgDescription')}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -412,7 +403,7 @@ export default function PoliciesPage() {
|
||||
form="policy-default-mappings-form"
|
||||
loading={updateDefaultMappingsLoading}
|
||||
>
|
||||
Save Default Mappings
|
||||
{t('defaultMappingsSubmit')}
|
||||
</Button>
|
||||
</SettingsSectionFooter>
|
||||
</SettingsSectionBody>
|
||||
@@ -455,8 +446,8 @@ export default function PoliciesPage() {
|
||||
<CredenzaHeader>
|
||||
<CredenzaTitle>
|
||||
{editingPolicy
|
||||
? "Edit Organization Policy"
|
||||
: "Add Organization Policy"}
|
||||
? t('orgPoliciesEdit')
|
||||
: t('orgPoliciesAdd')}
|
||||
</CredenzaTitle>
|
||||
<CredenzaDescription>
|
||||
Configure access for an organization
|
||||
@@ -476,7 +467,7 @@ export default function PoliciesPage() {
|
||||
name="orgId"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-col">
|
||||
<FormLabel>Organization</FormLabel>
|
||||
<FormLabel>{t('org')}</FormLabel>
|
||||
{editingPolicy ? (
|
||||
<Input {...field} disabled />
|
||||
) : (
|
||||
@@ -500,18 +491,17 @@ export default function PoliciesPage() {
|
||||
org.orgId ===
|
||||
field.value
|
||||
)?.name
|
||||
: "Select organization"}
|
||||
: t('orgSelect')}
|
||||
<CaretSortIcon className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</FormControl>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="p-0">
|
||||
<Command>
|
||||
<CommandInput placeholder="Search org" />
|
||||
<CommandInput placeholder={t('orgSearch')} />
|
||||
<CommandList>
|
||||
<CommandEmpty>
|
||||
No org
|
||||
found.
|
||||
{t('orgNotFound')}
|
||||
</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{organizations.map(
|
||||
@@ -562,16 +552,13 @@ export default function PoliciesPage() {
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
Role Mapping Path (Optional)
|
||||
{t('roleMappingPathOptional')}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
The result of this expression
|
||||
must return the role name as
|
||||
defined in the organization as a
|
||||
string.
|
||||
{t('defaultMappingsRoleDescription')}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -584,17 +571,13 @@ export default function PoliciesPage() {
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
Organization Mapping Path
|
||||
(Optional)
|
||||
{t('orgMappingPathOptional')}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
This expression must return the
|
||||
org ID or true for the user to
|
||||
be allowed to access the
|
||||
organization.
|
||||
{t('defaultMappingsOrgDescription')}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -605,7 +588,7 @@ export default function PoliciesPage() {
|
||||
</CredenzaBody>
|
||||
<CredenzaFooter>
|
||||
<CredenzaClose asChild>
|
||||
<Button variant="outline">Cancel</Button>
|
||||
<Button variant="outline">{t("cancel")}</Button>
|
||||
</CredenzaClose>
|
||||
<Button
|
||||
type="submit"
|
||||
@@ -621,7 +604,7 @@ export default function PoliciesPage() {
|
||||
: addPolicyLoading
|
||||
}
|
||||
>
|
||||
{editingPolicy ? "Update Policy" : "Add Policy"}
|
||||
{editingPolicy ? t('orgPolicyUpdate') : t('orgPolicyAdd')}
|
||||
</Button>
|
||||
</CredenzaFooter>
|
||||
</CredenzaContent>
|
||||
|
||||
Reference in New Issue
Block a user