Merge branch 'dev' into feat/login-page-customization

This commit is contained in:
miloschwartz
2025-12-17 11:41:17 -05:00
660 changed files with 19695 additions and 12803 deletions

View File

@@ -34,14 +34,16 @@ export default async function SettingsLayout(props: SettingsLayoutProps) {
const navItems = [
{
title: t('apiKeysPermissionsTitle'),
title: t("apiKeysPermissionsTitle"),
href: "/admin/api-keys/{apiKeyId}/permissions"
}
];
return (
<>
<SettingsSectionTitle title={t('apiKeysSettings', {apiKeyName: apiKey?.name})} />
<SettingsSectionTitle
title={t("apiKeysSettings", { apiKeyName: apiKey?.name })}
/>
<ApiKeyProvider apiKey={apiKey}>
<HorizontalTabs items={navItems}>{children}</HorizontalTabs>

View File

@@ -45,10 +45,10 @@ export default function Page() {
.catch((e) => {
toast({
variant: "destructive",
title: t('apiKeysPermissionsErrorLoadingActions'),
title: t("apiKeysPermissionsErrorLoadingActions"),
description: formatAxiosError(
e,
t('apiKeysPermissionsErrorLoadingActions')
t("apiKeysPermissionsErrorLoadingActions")
)
});
});
@@ -79,18 +79,18 @@ export default function Page() {
)
})
.catch((e) => {
console.error(t('apiKeysErrorSetPermission'), e);
console.error(t("apiKeysErrorSetPermission"), e);
toast({
variant: "destructive",
title: t('apiKeysErrorSetPermission'),
title: t("apiKeysErrorSetPermission"),
description: formatAxiosError(e)
});
});
if (actionsRes && actionsRes.status === 200) {
toast({
title: t('apiKeysPermissionsUpdated'),
description: t('apiKeysPermissionsUpdatedDescription')
title: t("apiKeysPermissionsUpdated"),
description: t("apiKeysPermissionsUpdatedDescription")
});
}
@@ -104,10 +104,12 @@ export default function Page() {
<SettingsSection>
<SettingsSectionHeader>
<SettingsSectionTitle>
{t('apiKeysPermissionsTitle')}
{t("apiKeysPermissionsTitle")}
</SettingsSectionTitle>
<SettingsSectionDescription>
{t('apiKeysPermissionsGeneralSettingsDescription')}
{t(
"apiKeysPermissionsGeneralSettingsDescription"
)}
</SettingsSectionDescription>
</SettingsSectionHeader>
<SettingsSectionBody>
@@ -125,7 +127,7 @@ export default function Page() {
loading={loadingSavePermissions}
disabled={loadingSavePermissions}
>
{t('apiKeysPermissionsSave')}
{t("apiKeysPermissionsSave")}
</Button>
</SettingsSectionFooter>
</SettingsSectionBody>

View File

@@ -30,7 +30,7 @@ import { createApiClient, formatAxiosError } from "@app/lib/api";
import { useEnvContext } from "@app/hooks/useEnvContext";
import { toast } from "@app/hooks/useToast";
import { AxiosResponse } from "axios";
import { useRouter } from "next/navigation";
import { useRouter } from "next/navigation";
import {
CreateOrgApiKeyBody,
CreateOrgApiKeyResponse
@@ -64,10 +64,10 @@ export default function Page() {
name: z
.string()
.min(2, {
message: t('nameMin', {len: 2})
message: t("nameMin", { len: 2 })
})
.max(255, {
message: t('nameMax', {len: 255})
message: t("nameMax", { len: 255 })
})
});
@@ -82,7 +82,7 @@ export default function Page() {
return data.copied;
},
{
message: t('apiKeysConfirmCopy2'),
message: t("apiKeysConfirmCopy2"),
path: ["copied"]
}
);
@@ -115,7 +115,7 @@ export default function Page() {
.catch((e) => {
toast({
variant: "destructive",
title: t('apiKeysErrorCreate'),
title: t("apiKeysErrorCreate"),
description: formatAxiosError(e)
});
});
@@ -136,10 +136,10 @@ export default function Page() {
)
})
.catch((e) => {
console.error(t('apiKeysErrorSetPermission'), e);
console.error(t("apiKeysErrorSetPermission"), e);
toast({
variant: "destructive",
title: t('apiKeysErrorSetPermission'),
title: t("apiKeysErrorSetPermission"),
description: formatAxiosError(e)
});
});
@@ -172,8 +172,8 @@ export default function Page() {
<>
<div className="flex justify-between">
<HeaderTitle
title={t('apiKeysCreate')}
description={t('apiKeysCreateDescription')}
title={t("apiKeysCreate")}
description={t("apiKeysCreateDescription")}
/>
<Button
variant="outline"
@@ -181,7 +181,7 @@ export default function Page() {
router.push(`/admin/api-keys`);
}}
>
{t('apiKeysSeeAll')}
{t("apiKeysSeeAll")}
</Button>
</div>
@@ -193,7 +193,7 @@ export default function Page() {
<SettingsSection>
<SettingsSectionHeader>
<SettingsSectionTitle>
{t('apiKeysTitle')}
{t("apiKeysTitle")}
</SettingsSectionTitle>
</SettingsSectionHeader>
<SettingsSectionBody>
@@ -214,7 +214,7 @@ export default function Page() {
render={({ field }) => (
<FormItem>
<FormLabel>
{t('name')}
{t("name")}
</FormLabel>
<FormControl>
<Input
@@ -235,10 +235,12 @@ export default function Page() {
<SettingsSection>
<SettingsSectionHeader>
<SettingsSectionTitle>
{t('apiKeysGeneralSettings')}
{t("apiKeysGeneralSettings")}
</SettingsSectionTitle>
<SettingsSectionDescription>
{t('apiKeysGeneralSettingsDescription')}
{t(
"apiKeysGeneralSettingsDescription"
)}
</SettingsSectionDescription>
</SettingsSectionHeader>
<SettingsSectionBody>
@@ -258,14 +260,14 @@ export default function Page() {
<SettingsSection>
<SettingsSectionHeader>
<SettingsSectionTitle>
{t('apiKeysList')}
{t("apiKeysList")}
</SettingsSectionTitle>
</SettingsSectionHeader>
<SettingsSectionBody>
<InfoSections cols={2}>
<InfoSection>
<InfoSectionTitle>
{t('name')}
{t("name")}
</InfoSectionTitle>
<InfoSectionContent>
<CopyToClipboard
@@ -275,7 +277,7 @@ export default function Page() {
</InfoSection>
<InfoSection>
<InfoSectionTitle>
{t('created')}
{t("created")}
</InfoSectionTitle>
<InfoSectionContent>
{moment(
@@ -288,10 +290,10 @@ export default function Page() {
<Alert variant="neutral">
<InfoIcon className="h-4 w-4" />
<AlertTitle className="font-semibold">
{t('apiKeysSave')}
{t("apiKeysSave")}
</AlertTitle>
<AlertDescription>
{t('apiKeysSaveDescription')}
{t("apiKeysSaveDescription")}
</AlertDescription>
</Alert>
@@ -358,7 +360,7 @@ export default function Page() {
router.push(`/admin/api-keys`);
}}
>
{t('cancel')}
{t("cancel")}
</Button>
)}
{!apiKey && (
@@ -370,7 +372,7 @@ export default function Page() {
form.handleSubmit(onSubmit)();
}}
>
{t('generate')}
{t("generate")}
</Button>
)}
@@ -381,7 +383,7 @@ export default function Page() {
copiedForm.handleSubmit(onCopiedSubmit)();
}}
>
{t('done')}
{t("done")}
</Button>
)}
</div>

View File

@@ -34,8 +34,8 @@ export default async function ApiKeysPage(props: ApiKeyPageProps) {
return (
<>
<SettingsSectionTitle
title={t('apiKeysManage')}
description={t('apiKeysDescription')}
title={t("apiKeysManage")}
description={t("apiKeysDescription")}
/>
<ApiKeysTable apiKeys={rows} />

View File

@@ -58,17 +58,17 @@ export default function GeneralPage() {
const t = useTranslations();
const GeneralFormSchema = z.object({
name: z.string().min(2, { message: t('nameMin', {len: 2}) }),
clientId: z.string().min(1, { message: t('idpClientIdRequired') }),
clientSecret: z.string().min(1, { message: t('idpClientSecretRequired') }),
authUrl: z.url({ message: t('idpErrorAuthUrlInvalid') }),
tokenUrl: z.url({ message: t('idpErrorTokenUrlInvalid') }),
identifierPath: z
name: z.string().min(2, { message: t("nameMin", { len: 2 }) }),
clientId: z.string().min(1, { message: t("idpClientIdRequired") }),
clientSecret: z
.string()
.min(1, { message: t('idpPathRequired') }),
.min(1, { message: t("idpClientSecretRequired") }),
authUrl: z.url({ message: t("idpErrorAuthUrlInvalid") }),
tokenUrl: z.url({ message: t("idpErrorTokenUrlInvalid") }),
identifierPath: z.string().min(1, { message: t("idpPathRequired") }),
emailPath: z.string().optional(),
namePath: z.string().optional(),
scopes: z.string().min(1, { message: t('idpScopeRequired') }),
scopes: z.string().min(1, { message: t("idpScopeRequired") }),
autoProvision: z.boolean().default(false)
});
@@ -111,7 +111,7 @@ export default function GeneralPage() {
}
} catch (e) {
toast({
title: t('error'),
title: t("error"),
description: formatAxiosError(e),
variant: "destructive"
});
@@ -145,14 +145,14 @@ export default function GeneralPage() {
if (res.status === 200) {
toast({
title: t('success'),
description: t('idpUpdatedDescription')
title: t("success"),
description: t("idpUpdatedDescription")
});
router.refresh();
}
} catch (e) {
toast({
title: t('error'),
title: t("error"),
description: formatAxiosError(e),
variant: "destructive"
});
@@ -171,17 +171,17 @@ export default function GeneralPage() {
<SettingsSection>
<SettingsSectionHeader>
<SettingsSectionTitle>
{t('idpTitle')}
{t("idpTitle")}
</SettingsSectionTitle>
<SettingsSectionDescription>
{t('idpSettingsDescription')}
{t("idpSettingsDescription")}
</SettingsSectionDescription>
</SettingsSectionHeader>
<SettingsSectionBody>
<InfoSections cols={3}>
<InfoSection>
<InfoSectionTitle>
{t('redirectUrl')}
{t("redirectUrl")}
</InfoSectionTitle>
<InfoSectionContent>
<CopyToClipboard text={redirectUrl} />
@@ -192,10 +192,10 @@ export default function GeneralPage() {
<Alert variant="neutral" className="">
<InfoIcon className="h-4 w-4" />
<AlertTitle className="font-semibold">
{t('redirectUrlAbout')}
{t("redirectUrlAbout")}
</AlertTitle>
<AlertDescription>
{t('redirectUrlAboutDescription')}
{t("redirectUrlAboutDescription")}
</AlertDescription>
</Alert>
<SettingsSectionForm>
@@ -210,12 +210,14 @@ export default function GeneralPage() {
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>{t('name')}</FormLabel>
<FormLabel>
{t("name")}
</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormDescription>
{t('idpDisplayName')}
{t("idpDisplayName")}
</FormDescription>
<FormMessage />
</FormItem>
@@ -225,7 +227,7 @@ export default function GeneralPage() {
<div className="flex items-start mb-0">
<SwitchInput
id="auto-provision-toggle"
label={t('idpAutoProvisionUsers')}
label={t("idpAutoProvisionUsers")}
defaultChecked={form.getValues(
"autoProvision"
)}
@@ -238,7 +240,7 @@ export default function GeneralPage() {
/>
</div>
<span className="text-sm text-muted-foreground">
{t('idpAutoProvisionUsersDescription')}
{t("idpAutoProvisionUsersDescription")}
</span>
</form>
</Form>
@@ -250,10 +252,10 @@ export default function GeneralPage() {
<SettingsSection>
<SettingsSectionHeader>
<SettingsSectionTitle>
{t('idpOidcConfigure')}
{t("idpOidcConfigure")}
</SettingsSectionTitle>
<SettingsSectionDescription>
{t('idpOidcConfigureDescription')}
{t("idpOidcConfigureDescription")}
</SettingsSectionDescription>
</SettingsSectionHeader>
<SettingsSectionBody>
@@ -270,13 +272,15 @@ export default function GeneralPage() {
render={({ field }) => (
<FormItem>
<FormLabel>
{t('idpClientId')}
{t("idpClientId")}
</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormDescription>
{t('idpClientIdDescription')}
{t(
"idpClientIdDescription"
)}
</FormDescription>
<FormMessage />
</FormItem>
@@ -289,7 +293,7 @@ export default function GeneralPage() {
render={({ field }) => (
<FormItem>
<FormLabel>
{t('idpClientSecret')}
{t("idpClientSecret")}
</FormLabel>
<FormControl>
<Input
@@ -298,7 +302,9 @@ export default function GeneralPage() {
/>
</FormControl>
<FormDescription>
{t('idpClientSecretDescription')}
{t(
"idpClientSecretDescription"
)}
</FormDescription>
<FormMessage />
</FormItem>
@@ -311,13 +317,15 @@ export default function GeneralPage() {
render={({ field }) => (
<FormItem>
<FormLabel>
{t('idpAuthUrl')}
{t("idpAuthUrl")}
</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormDescription>
{t('idpAuthUrlDescription')}
{t(
"idpAuthUrlDescription"
)}
</FormDescription>
<FormMessage />
</FormItem>
@@ -330,13 +338,15 @@ export default function GeneralPage() {
render={({ field }) => (
<FormItem>
<FormLabel>
{t('idpTokenUrl')}
{t("idpTokenUrl")}
</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormDescription>
{t('idpTokenUrlDescription')}
{t(
"idpTokenUrlDescription"
)}
</FormDescription>
<FormMessage />
</FormItem>
@@ -351,10 +361,10 @@ export default function GeneralPage() {
<SettingsSection>
<SettingsSectionHeader>
<SettingsSectionTitle>
{t('idpToken')}
{t("idpToken")}
</SettingsSectionTitle>
<SettingsSectionDescription>
{t('idpTokenDescription')}
{t("idpTokenDescription")}
</SettingsSectionDescription>
</SettingsSectionHeader>
<SettingsSectionBody>
@@ -368,17 +378,21 @@ export default function GeneralPage() {
<Alert variant="neutral">
<InfoIcon className="h-4 w-4" />
<AlertTitle className="font-semibold">
{t('idpJmespathAbout')}
{t("idpJmespathAbout")}
</AlertTitle>
<AlertDescription>
{t('idpJmespathAboutDescription')}
{t(
"idpJmespathAboutDescription"
)}
<a
href="https://jmespath.org"
target="_blank"
rel="noopener noreferrer"
className="text-primary hover:underline inline-flex items-center"
>
{t('idpJmespathAboutDescriptionLink')}{" "}
{t(
"idpJmespathAboutDescriptionLink"
)}{" "}
<ExternalLink className="ml-1 h-4 w-4" />
</a>
</AlertDescription>
@@ -390,13 +404,15 @@ export default function GeneralPage() {
render={({ field }) => (
<FormItem>
<FormLabel>
{t('idpJmespathLabel')}
{t("idpJmespathLabel")}
</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormDescription>
{t('idpJmespathLabelDescription')}
{t(
"idpJmespathLabelDescription"
)}
</FormDescription>
<FormMessage />
</FormItem>
@@ -409,13 +425,17 @@ export default function GeneralPage() {
render={({ field }) => (
<FormItem>
<FormLabel>
{t('idpJmespathEmailPathOptional')}
{t(
"idpJmespathEmailPathOptional"
)}
</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormDescription>
{t('idpJmespathEmailPathOptionalDescription')}
{t(
"idpJmespathEmailPathOptionalDescription"
)}
</FormDescription>
<FormMessage />
</FormItem>
@@ -428,13 +448,17 @@ export default function GeneralPage() {
render={({ field }) => (
<FormItem>
<FormLabel>
{t('idpJmespathNamePathOptional')}
{t(
"idpJmespathNamePathOptional"
)}
</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormDescription>
{t('idpJmespathNamePathOptionalDescription')}
{t(
"idpJmespathNamePathOptionalDescription"
)}
</FormDescription>
<FormMessage />
</FormItem>
@@ -447,13 +471,17 @@ export default function GeneralPage() {
render={({ field }) => (
<FormItem>
<FormLabel>
{t('idpOidcConfigureScopes')}
{t(
"idpOidcConfigureScopes"
)}
</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormDescription>
{t('idpOidcConfigureScopesDescription')}
{t(
"idpOidcConfigureScopesDescription"
)}
</FormDescription>
<FormMessage />
</FormItem>
@@ -474,7 +502,7 @@ export default function GeneralPage() {
loading={loading}
disabled={loading}
>
{t('saveGeneralSettings')}
{t("saveGeneralSettings")}
</Button>
</div>
</>

View File

@@ -89,7 +89,7 @@ export default function PoliciesPage() {
const [editingPolicy, setEditingPolicy] = useState<PolicyRow | null>(null);
const policyFormSchema = z.object({
orgId: z.string().min(1, { message: t('orgRequired') }),
orgId: z.string().min(1, { message: t("orgRequired") }),
roleMapping: z.string().optional(),
orgMapping: z.string().optional()
});
@@ -133,7 +133,7 @@ export default function PoliciesPage() {
}
} catch (e) {
toast({
title: t('error'),
title: t("error"),
description: formatAxiosError(e),
variant: "destructive"
});
@@ -148,7 +148,7 @@ export default function PoliciesPage() {
}
} catch (e) {
toast({
title: t('error'),
title: t("error"),
description: formatAxiosError(e),
variant: "destructive"
});
@@ -167,7 +167,7 @@ export default function PoliciesPage() {
}
} catch (e) {
toast({
title: t('error'),
title: t("error"),
description: formatAxiosError(e),
variant: "destructive"
});
@@ -202,15 +202,15 @@ export default function PoliciesPage() {
};
setPolicies([...policies, newPolicy]);
toast({
title: t('success'),
description: t('orgPolicyAddedDescription')
title: t("success"),
description: t("orgPolicyAddedDescription")
});
setShowAddDialog(false);
form.reset();
}
} catch (e) {
toast({
title: t('error'),
title: t("error"),
description: formatAxiosError(e),
variant: "destructive"
});
@@ -244,8 +244,8 @@ export default function PoliciesPage() {
)
);
toast({
title: t('success'),
description: t('orgPolicyUpdatedDescription')
title: t("success"),
description: t("orgPolicyUpdatedDescription")
});
setShowAddDialog(false);
setEditingPolicy(null);
@@ -253,7 +253,7 @@ export default function PoliciesPage() {
}
} catch (e) {
toast({
title: t('error'),
title: t("error"),
description: formatAxiosError(e),
variant: "destructive"
});
@@ -271,13 +271,13 @@ export default function PoliciesPage() {
policies.filter((policy) => policy.orgId !== orgId)
);
toast({
title: t('success'),
description: t('orgPolicyDeletedDescription')
title: t("success"),
description: t("orgPolicyDeletedDescription")
});
}
} catch (e) {
toast({
title: t('error'),
title: t("error"),
description: formatAxiosError(e),
variant: "destructive"
});
@@ -295,13 +295,13 @@ export default function PoliciesPage() {
});
if (res.status === 200) {
toast({
title: t('success'),
description: t('defaultMappingsUpdatedDescription')
title: t("success"),
description: t("defaultMappingsUpdatedDescription")
});
}
} catch (e) {
toast({
title: t('error'),
title: t("error"),
description: formatAxiosError(e),
variant: "destructive"
});
@@ -320,18 +320,18 @@ export default function PoliciesPage() {
<Alert variant="neutral" className="mb-6">
<InfoIcon className="h-4 w-4" />
<AlertTitle className="font-semibold">
{t('orgPoliciesAbout')}
{t("orgPoliciesAbout")}
</AlertTitle>
<AlertDescription>
{/*TODO(vlalx): Validate replacing */}
{t('orgPoliciesAboutDescription')}{" "}
{t("orgPoliciesAboutDescription")}{" "}
<Link
href="https://docs.pangolin.net/manage/identity-providers/auto-provisioning"
target="_blank"
rel="noopener noreferrer"
className="text-primary hover:underline"
>
{t('orgPoliciesAboutDescriptionLink')}
{t("orgPoliciesAboutDescriptionLink")}
<ExternalLink className="ml-1 h-4 w-4 inline" />
</Link>
</AlertDescription>
@@ -340,10 +340,10 @@ export default function PoliciesPage() {
<SettingsSection>
<SettingsSectionHeader>
<SettingsSectionTitle>
{t('defaultMappingsOptional')}
{t("defaultMappingsOptional")}
</SettingsSectionTitle>
<SettingsSectionDescription>
{t('defaultMappingsOptionalDescription')}
{t("defaultMappingsOptionalDescription")}
</SettingsSectionDescription>
</SettingsSectionHeader>
<SettingsSectionBody>
@@ -362,13 +362,15 @@ export default function PoliciesPage() {
render={({ field }) => (
<FormItem>
<FormLabel>
{t('defaultMappingsRole')}
{t("defaultMappingsRole")}
</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormDescription>
{t('defaultMappingsRoleDescription')}
{t(
"defaultMappingsRoleDescription"
)}
</FormDescription>
<FormMessage />
</FormItem>
@@ -381,13 +383,15 @@ export default function PoliciesPage() {
render={({ field }) => (
<FormItem>
<FormLabel>
{t('defaultMappingsOrg')}
{t("defaultMappingsOrg")}
</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormDescription>
{t('defaultMappingsOrgDescription')}
{t(
"defaultMappingsOrgDescription"
)}
</FormDescription>
<FormMessage />
</FormItem>
@@ -402,7 +406,7 @@ export default function PoliciesPage() {
form="policy-default-mappings-form"
loading={updateDefaultMappingsLoading}
>
{t('defaultMappingsSubmit')}
{t("defaultMappingsSubmit")}
</Button>
</SettingsSectionFooter>
</SettingsSectionBody>
@@ -445,11 +449,11 @@ export default function PoliciesPage() {
<CredenzaHeader>
<CredenzaTitle>
{editingPolicy
? t('orgPoliciesEdit')
: t('orgPoliciesAdd')}
? t("orgPoliciesEdit")
: t("orgPoliciesAdd")}
</CredenzaTitle>
<CredenzaDescription>
{t('orgPolicyConfig')}
{t("orgPolicyConfig")}
</CredenzaDescription>
</CredenzaHeader>
<CredenzaBody>
@@ -466,7 +470,7 @@ export default function PoliciesPage() {
name="orgId"
render={({ field }) => (
<FormItem className="flex flex-col">
<FormLabel>{t('org')}</FormLabel>
<FormLabel>{t("org")}</FormLabel>
{editingPolicy ? (
<Input {...field} disabled />
) : (
@@ -490,17 +494,25 @@ export default function PoliciesPage() {
org.orgId ===
field.value
)?.name
: t('orgSelect')}
: t(
"orgSelect"
)}
<CaretSortIcon className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</FormControl>
</PopoverTrigger>
<PopoverContent className="p-0">
<Command>
<CommandInput placeholder={t('orgSearch')} />
<CommandInput
placeholder={t(
"orgSearch"
)}
/>
<CommandList>
<CommandEmpty>
{t('orgNotFound')}
{t(
"orgNotFound"
)}
</CommandEmpty>
<CommandGroup>
{organizations.map(
@@ -551,13 +563,15 @@ export default function PoliciesPage() {
render={({ field }) => (
<FormItem>
<FormLabel>
{t('roleMappingPathOptional')}
{t("roleMappingPathOptional")}
</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormDescription>
{t('defaultMappingsRoleDescription')}
{t(
"defaultMappingsRoleDescription"
)}
</FormDescription>
<FormMessage />
</FormItem>
@@ -570,13 +584,15 @@ export default function PoliciesPage() {
render={({ field }) => (
<FormItem>
<FormLabel>
{t('orgMappingPathOptional')}
{t("orgMappingPathOptional")}
</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormDescription>
{t('defaultMappingsOrgDescription')}
{t(
"defaultMappingsOrgDescription"
)}
</FormDescription>
<FormMessage />
</FormItem>
@@ -603,7 +619,9 @@ export default function PoliciesPage() {
: addPolicyLoading
}
>
{editingPolicy ? t('orgPolicyUpdate') : t('orgPolicyAdd')}
{editingPolicy
? t("orgPolicyUpdate")
: t("orgPolicyAdd")}
</Button>
</CredenzaFooter>
</CredenzaContent>

View File

@@ -48,18 +48,18 @@ export default function Page() {
const t = useTranslations();
const createIdpFormSchema = z.object({
name: z.string().min(2, { message: t('nameMin', {len: 2}) }),
name: z.string().min(2, { message: t("nameMin", { len: 2 }) }),
type: z.enum(["oidc"]),
clientId: z.string().min(1, { message: t('idpClientIdRequired') }),
clientSecret: z.string().min(1, { message: t('idpClientSecretRequired') }),
authUrl: z.url({ message: t('idpErrorAuthUrlInvalid') }),
tokenUrl: z.url({ message: t('idpErrorTokenUrlInvalid') }),
identifierPath: z
clientId: z.string().min(1, { message: t("idpClientIdRequired") }),
clientSecret: z
.string()
.min(1, { message: t('idpPathRequired') }),
.min(1, { message: t("idpClientSecretRequired") }),
authUrl: z.url({ message: t("idpErrorAuthUrlInvalid") }),
tokenUrl: z.url({ message: t("idpErrorTokenUrlInvalid") }),
identifierPath: z.string().min(1, { message: t("idpPathRequired") }),
emailPath: z.string().optional(),
namePath: z.string().optional(),
scopes: z.string().min(1, { message: t('idpScopeRequired') }),
scopes: z.string().min(1, { message: t("idpScopeRequired") }),
autoProvision: z.boolean().default(false)
});
@@ -75,7 +75,7 @@ export default function Page() {
{
id: "oidc",
title: "OAuth2/OIDC",
description: t('idpOidcDescription')
description: t("idpOidcDescription")
}
];
@@ -117,14 +117,14 @@ export default function Page() {
if (res.status === 201) {
toast({
title: t('success'),
description: t('idpCreatedDescription')
title: t("success"),
description: t("idpCreatedDescription")
});
router.push(`/admin/idp/${res.data.data.idpId}`);
}
} catch (e) {
toast({
title: t('error'),
title: t("error"),
description: formatAxiosError(e),
variant: "destructive"
});
@@ -137,8 +137,8 @@ export default function Page() {
<>
<div className="flex justify-between">
<HeaderTitle
title={t('idpCreate')}
description={t('idpCreateDescription')}
title={t("idpCreate")}
description={t("idpCreateDescription")}
/>
<Button
variant="outline"
@@ -146,7 +146,7 @@ export default function Page() {
router.push("/admin/idp");
}}
>
{t('idpSeeAll')}
{t("idpSeeAll")}
</Button>
</div>
@@ -154,10 +154,10 @@ export default function Page() {
<SettingsSection>
<SettingsSectionHeader>
<SettingsSectionTitle>
{t('idpTitle')}
{t("idpTitle")}
</SettingsSectionTitle>
<SettingsSectionDescription>
{t('idpCreateSettingsDescription')}
{t("idpCreateSettingsDescription")}
</SettingsSectionDescription>
</SettingsSectionHeader>
<SettingsSectionBody>
@@ -173,12 +173,14 @@ export default function Page() {
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>{t('name')}</FormLabel>
<FormLabel>
{t("name")}
</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormDescription>
{t('idpDisplayName')}
{t("idpDisplayName")}
</FormDescription>
<FormMessage />
</FormItem>
@@ -188,7 +190,7 @@ export default function Page() {
<div className="flex items-start mb-0">
<SwitchInput
id="auto-provision-toggle"
label={t('idpAutoProvisionUsers')}
label={t("idpAutoProvisionUsers")}
defaultChecked={form.getValues(
"autoProvision"
)}
@@ -201,7 +203,7 @@ export default function Page() {
/>
</div>
<span className="text-sm text-muted-foreground">
{t('idpAutoProvisionUsersDescription')}
{t("idpAutoProvisionUsersDescription")}
</span>
</form>
</Form>
@@ -212,10 +214,10 @@ export default function Page() {
<SettingsSection>
<SettingsSectionHeader>
<SettingsSectionTitle>
{t('idpType')}
{t("idpType")}
</SettingsSectionTitle>
<SettingsSectionDescription>
{t('idpTypeDescription')}
{t("idpTypeDescription")}
</SettingsSectionDescription>
</SettingsSectionHeader>
<SettingsSectionBody>
@@ -235,10 +237,10 @@ export default function Page() {
<SettingsSection>
<SettingsSectionHeader>
<SettingsSectionTitle>
{t('idpOidcConfigure')}
{t("idpOidcConfigure")}
</SettingsSectionTitle>
<SettingsSectionDescription>
{t('idpOidcConfigureDescription')}
{t("idpOidcConfigureDescription")}
</SettingsSectionDescription>
</SettingsSectionHeader>
<SettingsSectionBody>
@@ -254,13 +256,15 @@ export default function Page() {
render={({ field }) => (
<FormItem>
<FormLabel>
{t('idpClientId')}
{t("idpClientId")}
</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormDescription>
{t('idpClientIdDescription')}
{t(
"idpClientIdDescription"
)}
</FormDescription>
<FormMessage />
</FormItem>
@@ -273,7 +277,7 @@ export default function Page() {
render={({ field }) => (
<FormItem>
<FormLabel>
{t('idpClientSecret')}
{t("idpClientSecret")}
</FormLabel>
<FormControl>
<Input
@@ -282,7 +286,9 @@ export default function Page() {
/>
</FormControl>
<FormDescription>
{t('idpClientSecretDescription')}
{t(
"idpClientSecretDescription"
)}
</FormDescription>
<FormMessage />
</FormItem>
@@ -295,7 +301,7 @@ export default function Page() {
render={({ field }) => (
<FormItem>
<FormLabel>
{t('idpAuthUrl')}
{t("idpAuthUrl")}
</FormLabel>
<FormControl>
<Input
@@ -304,7 +310,9 @@ export default function Page() {
/>
</FormControl>
<FormDescription>
{t('idpAuthUrlDescription')}
{t(
"idpAuthUrlDescription"
)}
</FormDescription>
<FormMessage />
</FormItem>
@@ -317,7 +325,7 @@ export default function Page() {
render={({ field }) => (
<FormItem>
<FormLabel>
{t('idpTokenUrl')}
{t("idpTokenUrl")}
</FormLabel>
<FormControl>
<Input
@@ -326,7 +334,9 @@ export default function Page() {
/>
</FormControl>
<FormDescription>
{t('idpTokenUrlDescription')}
{t(
"idpTokenUrlDescription"
)}
</FormDescription>
<FormMessage />
</FormItem>
@@ -338,10 +348,10 @@ export default function Page() {
<Alert variant="neutral">
<InfoIcon className="h-4 w-4" />
<AlertTitle className="font-semibold">
{t('idpOidcConfigureAlert')}
{t("idpOidcConfigureAlert")}
</AlertTitle>
<AlertDescription>
{t('idpOidcConfigureAlertDescription')}
{t("idpOidcConfigureAlertDescription")}
</AlertDescription>
</Alert>
</SettingsSectionBody>
@@ -350,10 +360,10 @@ export default function Page() {
<SettingsSection>
<SettingsSectionHeader>
<SettingsSectionTitle>
{t('idpToken')}
{t("idpToken")}
</SettingsSectionTitle>
<SettingsSectionDescription>
{t('idpTokenDescription')}
{t("idpTokenDescription")}
</SettingsSectionDescription>
</SettingsSectionHeader>
<SettingsSectionBody>
@@ -366,17 +376,21 @@ export default function Page() {
<Alert variant="neutral">
<InfoIcon className="h-4 w-4" />
<AlertTitle className="font-semibold">
{t('idpJmespathAbout')}
{t("idpJmespathAbout")}
</AlertTitle>
<AlertDescription>
{t('idpJmespathAboutDescription')}{" "}
{t(
"idpJmespathAboutDescription"
)}{" "}
<a
href="https://jmespath.org"
target="_blank"
rel="noopener noreferrer"
className="text-primary hover:underline inline-flex items-center"
>
{t('idpJmespathAboutDescriptionLink')}{" "}
{t(
"idpJmespathAboutDescriptionLink"
)}{" "}
<ExternalLink className="ml-1 h-4 w-4" />
</a>
</AlertDescription>
@@ -388,13 +402,15 @@ export default function Page() {
render={({ field }) => (
<FormItem>
<FormLabel>
{t('idpJmespathLabel')}
{t("idpJmespathLabel")}
</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormDescription>
{t('idpJmespathLabelDescription')}
{t(
"idpJmespathLabelDescription"
)}
</FormDescription>
<FormMessage />
</FormItem>
@@ -407,13 +423,17 @@ export default function Page() {
render={({ field }) => (
<FormItem>
<FormLabel>
{t('idpJmespathEmailPathOptional')}
{t(
"idpJmespathEmailPathOptional"
)}
</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormDescription>
{t('idpJmespathEmailPathOptionalDescription')}
{t(
"idpJmespathEmailPathOptionalDescription"
)}
</FormDescription>
<FormMessage />
</FormItem>
@@ -426,13 +446,17 @@ export default function Page() {
render={({ field }) => (
<FormItem>
<FormLabel>
{t('idpJmespathNamePathOptional')}
{t(
"idpJmespathNamePathOptional"
)}
</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormDescription>
{t('idpJmespathNamePathOptionalDescription')}
{t(
"idpJmespathNamePathOptionalDescription"
)}
</FormDescription>
<FormMessage />
</FormItem>
@@ -445,13 +469,17 @@ export default function Page() {
render={({ field }) => (
<FormItem>
<FormLabel>
{t('idpOidcConfigureScopes')}
{t(
"idpOidcConfigureScopes"
)}
</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormDescription>
{t('idpOidcConfigureScopesDescription')}
{t(
"idpOidcConfigureScopesDescription"
)}
</FormDescription>
<FormMessage />
</FormItem>
@@ -473,7 +501,7 @@ export default function Page() {
router.push("/admin/idp");
}}
>
{t('cancel')}
{t("cancel")}
</Button>
<Button
type="submit"
@@ -481,7 +509,7 @@ export default function Page() {
loading={createLoading}
onClick={form.handleSubmit(onSubmit)}
>
{t('idpSubmit')}
{t("idpSubmit")}
</Button>
</div>
</>

View File

@@ -22,8 +22,8 @@ export default async function IdpPage() {
return (
<>
<SettingsSectionTitle
title={t('idpManage')}
description={t('idpManageDescription')}
title={t("idpManage")}
description={t("idpManageDescription")}
/>
<IdpTable idps={idps} />
</>

View File

@@ -14,4 +14,3 @@ export default async function AdminLicenseLayout(props: LayoutProps) {
return props.children;
}

View File

@@ -315,10 +315,8 @@ export default function LicensePage() {
setSelectedLicenseKey(null);
}}
dialog={
<div>
<p>
{t("licenseQuestionRemove")}
</p>
<div className="space-y-2">
<p>{t("licenseQuestionRemove")}</p>
<p>
<b>{t("licenseMessageRemove")}</b>
</p>
@@ -362,7 +360,8 @@ export default function LicensePage() {
<div className="space-y-2 text-green-500">
<div className="text-2xl flex items-center gap-2">
<Check />
{t("licensed")}
{t("licensed") +
`${licenseStatus?.tier === "personal" ? ` (${t("personalUseOnly")})` : ""}`}
</div>
</div>
) : (

View File

@@ -188,7 +188,7 @@ export default function UsersTable({ users }: Props) {
},
{
id: "actions",
header: () => (<span className="p-3">{t("actions")}</span>),
header: () => <span className="p-3">{t("actions")}</span>,
cell: ({ row }) => {
const r = row.original;
return (
@@ -243,7 +243,7 @@ export default function UsersTable({ users }: Props) {
setSelected(null);
}}
dialog={
<div>
<div className="space-y-2">
<p>{t("userQuestionRemove")}</p>
<p>{t("userMessageRemove")}</p>

View File

@@ -6,7 +6,7 @@ import { AdminGetUserResponse } from "@server/routers/user/adminGetUser";
import { HorizontalTabs } from "@app/components/HorizontalTabs";
import { cache } from "react";
import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
import { getTranslations } from 'next-intl/server';
import { getTranslations } from "next-intl/server";
interface UserLayoutProps {
children: React.ReactNode;
@@ -36,7 +36,7 @@ export default async function UserLayoutProps(props: UserLayoutProps) {
const navItems = [
{
title: t('general'),
title: t("general"),
href: "/admin/users/{userId}/general"
}
];
@@ -45,11 +45,9 @@ export default async function UserLayoutProps(props: UserLayoutProps) {
<>
<SettingsSectionTitle
title={`${user?.email || user?.name || user?.username}`}
description={t('userDescription2')}
description={t("userDescription2")}
/>
<HorizontalTabs items={navItems}>
{children}
</HorizontalTabs>
<HorizontalTabs items={navItems}>{children}</HorizontalTabs>
</>
);
}
}

View File

@@ -5,4 +5,4 @@ export default async function UserPage(props: {
}) {
const { userId } = await props.params;
redirect(`/admin/users/${userId}/general`);
}
}

View File

@@ -36,7 +36,7 @@ export default async function UsersPage(props: PageProps) {
username: row.username,
type: row.type,
idpId: row.idpId,
idpName: row.idpName || t('idpNameInternal'),
idpName: row.idpName || t("idpNameInternal"),
dateCreated: row.dateCreated,
serverAdmin: row.serverAdmin,
twoFactorEnabled: row.twoFactorEnabled,
@@ -47,14 +47,16 @@ export default async function UsersPage(props: PageProps) {
return (
<>
<SettingsSectionTitle
title={t('userTitle')}
description={t('userDescription')}
title={t("userTitle")}
description={t("userDescription")}
/>
<Alert variant="neutral" className="mb-6">
<InfoIcon className="h-4 w-4" />
<AlertTitle className="font-semibold">{t('userAbount')}</AlertTitle>
<AlertTitle className="font-semibold">
{t("userAbount")}
</AlertTitle>
<AlertDescription>
{t('userAbountDescription')}
{t("userAbountDescription")}
</AlertDescription>
</Alert>
<UsersTable users={userRows} />