mirror of
https://github.com/fosrl/pangolin.git
synced 2026-02-24 05:46:39 +00:00
clean up a few save buttons
This commit is contained in:
@@ -206,6 +206,7 @@
|
|||||||
"orgGeneralSettings": "Organization Settings",
|
"orgGeneralSettings": "Organization Settings",
|
||||||
"orgGeneralSettingsDescription": "Manage your organization details and configuration",
|
"orgGeneralSettingsDescription": "Manage your organization details and configuration",
|
||||||
"saveGeneralSettings": "Save General Settings",
|
"saveGeneralSettings": "Save General Settings",
|
||||||
|
"saveSettings": "Save Settings",
|
||||||
"orgDangerZone": "Danger Zone",
|
"orgDangerZone": "Danger Zone",
|
||||||
"orgDangerZoneDescription": "Once you delete this org, there is no going back. Please be certain.",
|
"orgDangerZoneDescription": "Once you delete this org, there is no going back. Please be certain.",
|
||||||
"orgDelete": "Delete Organization",
|
"orgDelete": "Delete Organization",
|
||||||
|
|||||||
@@ -147,6 +147,14 @@ export default function Page() {
|
|||||||
}
|
}
|
||||||
}, [userType, env.email.emailEnabled, internalForm, externalForm]);
|
}, [userType, env.email.emailEnabled, internalForm, externalForm]);
|
||||||
|
|
||||||
|
const userTypes: UserTypeOption[] = [
|
||||||
|
{
|
||||||
|
id: "internal",
|
||||||
|
title: t("userTypeInternal"),
|
||||||
|
description: t("userTypeInternalDescription")
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!userType) {
|
if (!userType) {
|
||||||
return;
|
return;
|
||||||
@@ -193,6 +201,14 @@ export default function Page() {
|
|||||||
if (res?.status === 200) {
|
if (res?.status === 200) {
|
||||||
setIdps(res.data.data.idps);
|
setIdps(res.data.data.idps);
|
||||||
setDataLoaded(true);
|
setDataLoaded(true);
|
||||||
|
|
||||||
|
if (res.data.data.idps.length) {
|
||||||
|
userTypes.push({
|
||||||
|
id: "oidc",
|
||||||
|
title: t("userTypeExternal"),
|
||||||
|
description: t("userTypeExternalDescription")
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -288,19 +304,6 @@ export default function Page() {
|
|||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
const userTypes: ReadonlyArray<UserTypeOption> = [
|
|
||||||
{
|
|
||||||
id: "internal",
|
|
||||||
title: t("userTypeInternal"),
|
|
||||||
description: t("userTypeInternalDescription")
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "oidc",
|
|
||||||
title: t("userTypeExternal"),
|
|
||||||
description: t("userTypeExternalDescription")
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
@@ -320,7 +323,7 @@ export default function Page() {
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<SettingsContainer>
|
<SettingsContainer>
|
||||||
{!inviteLink && (
|
{!inviteLink && userTypes.length > 1 ? (
|
||||||
<SettingsSection>
|
<SettingsSection>
|
||||||
<SettingsSectionHeader>
|
<SettingsSectionHeader>
|
||||||
<SettingsSectionTitle>
|
<SettingsSectionTitle>
|
||||||
@@ -347,7 +350,7 @@ export default function Page() {
|
|||||||
/>
|
/>
|
||||||
</SettingsSectionBody>
|
</SettingsSectionBody>
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
)}
|
) : null}
|
||||||
|
|
||||||
{userType === "internal" && dataLoaded && (
|
{userType === "internal" && dataLoaded && (
|
||||||
<>
|
<>
|
||||||
@@ -496,7 +499,9 @@ export default function Page() {
|
|||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id="send-email"
|
id="send-email"
|
||||||
checked={sendEmail}
|
checked={
|
||||||
|
sendEmail
|
||||||
|
}
|
||||||
onCheckedChange={(
|
onCheckedChange={(
|
||||||
e
|
e
|
||||||
) =>
|
) =>
|
||||||
@@ -528,7 +533,9 @@ export default function Page() {
|
|||||||
</SettingsSectionTitle>
|
</SettingsSectionTitle>
|
||||||
<SettingsSectionDescription>
|
<SettingsSectionDescription>
|
||||||
{sendEmail
|
{sendEmail
|
||||||
? t("inviteEmailSentDescription")
|
? t(
|
||||||
|
"inviteEmailSentDescription"
|
||||||
|
)
|
||||||
: t("inviteSentDescription")}
|
: t("inviteSentDescription")}
|
||||||
</SettingsSectionDescription>
|
</SettingsSectionDescription>
|
||||||
</SettingsSectionHeader>
|
</SettingsSectionHeader>
|
||||||
@@ -778,7 +785,14 @@ export default function Page() {
|
|||||||
form={inviteLink ? undefined : "create-user-form"}
|
form={inviteLink ? undefined : "create-user-form"}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
onClick={inviteLink ? () => router.push(`/${orgId}/settings/access/users`) : undefined}
|
onClick={
|
||||||
|
inviteLink
|
||||||
|
? () =>
|
||||||
|
router.push(
|
||||||
|
`/${orgId}/settings/access/users`
|
||||||
|
)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{inviteLink ? t("done") : t("accessUserCreate")}
|
{inviteLink ? t("done") : t("accessUserCreate")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -102,6 +102,7 @@ export default function GeneralForm() {
|
|||||||
|
|
||||||
const GeneralFormSchema = z
|
const GeneralFormSchema = z
|
||||||
.object({
|
.object({
|
||||||
|
enabled: z.boolean(),
|
||||||
subdomain: z.string().optional(),
|
subdomain: z.string().optional(),
|
||||||
name: z.string().min(1).max(255),
|
name: z.string().min(1).max(255),
|
||||||
proxyPort: z.number().optional(),
|
proxyPort: z.number().optional(),
|
||||||
@@ -144,6 +145,7 @@ export default function GeneralForm() {
|
|||||||
const form = useForm<GeneralFormValues>({
|
const form = useForm<GeneralFormValues>({
|
||||||
resolver: zodResolver(GeneralFormSchema),
|
resolver: zodResolver(GeneralFormSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
|
enabled: resource.enabled,
|
||||||
name: resource.name,
|
name: resource.name,
|
||||||
subdomain: resource.subdomain ? resource.subdomain : undefined,
|
subdomain: resource.subdomain ? resource.subdomain : undefined,
|
||||||
proxyPort: resource.proxyPort ? resource.proxyPort : undefined,
|
proxyPort: resource.proxyPort ? resource.proxyPort : undefined,
|
||||||
@@ -209,6 +211,7 @@ export default function GeneralForm() {
|
|||||||
.post<AxiosResponse<UpdateResourceResponse>>(
|
.post<AxiosResponse<UpdateResourceResponse>>(
|
||||||
`resource/${resource?.resourceId}`,
|
`resource/${resource?.resourceId}`,
|
||||||
{
|
{
|
||||||
|
enabled: data.enabled,
|
||||||
name: data.name,
|
name: data.name,
|
||||||
subdomain: data.http ? data.subdomain : undefined,
|
subdomain: data.http ? data.subdomain : undefined,
|
||||||
proxyPort: data.proxyPort,
|
proxyPort: data.proxyPort,
|
||||||
@@ -236,6 +239,7 @@ export default function GeneralForm() {
|
|||||||
const resource = res.data.data;
|
const resource = res.data.data;
|
||||||
|
|
||||||
updateResource({
|
updateResource({
|
||||||
|
enabled: data.enabled,
|
||||||
name: data.name,
|
name: data.name,
|
||||||
subdomain: data.subdomain,
|
subdomain: data.subdomain,
|
||||||
proxyPort: data.proxyPort,
|
proxyPort: data.proxyPort,
|
||||||
@@ -282,54 +286,9 @@ export default function GeneralForm() {
|
|||||||
setTransferLoading(false);
|
setTransferLoading(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function toggleResourceEnabled(val: boolean) {
|
|
||||||
const res = await api
|
|
||||||
.post<AxiosResponse<UpdateResourceResponse>>(
|
|
||||||
`resource/${resource.resourceId}`,
|
|
||||||
{
|
|
||||||
enabled: val
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.catch((e) => {
|
|
||||||
toast({
|
|
||||||
variant: "destructive",
|
|
||||||
title: t("resourceErrorToggle"),
|
|
||||||
description: formatAxiosError(
|
|
||||||
e,
|
|
||||||
t("resourceErrorToggleDescription")
|
|
||||||
)
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
updateResource({
|
|
||||||
enabled: val
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
!loadingPage && (
|
!loadingPage && (
|
||||||
<SettingsContainer>
|
<SettingsContainer>
|
||||||
<SettingsSection>
|
|
||||||
<SettingsSectionHeader>
|
|
||||||
<SettingsSectionTitle>
|
|
||||||
{t("resourceVisibilityTitle")}
|
|
||||||
</SettingsSectionTitle>
|
|
||||||
<SettingsSectionDescription>
|
|
||||||
{t("resourceVisibilityTitleDescription")}
|
|
||||||
</SettingsSectionDescription>
|
|
||||||
</SettingsSectionHeader>
|
|
||||||
<SettingsSectionBody>
|
|
||||||
<SwitchInput
|
|
||||||
id="enable-resource"
|
|
||||||
label={t("resourceEnable")}
|
|
||||||
defaultChecked={resource.enabled}
|
|
||||||
onCheckedChange={async (val) => {
|
|
||||||
await toggleResourceEnabled(val);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</SettingsSectionBody>
|
|
||||||
</SettingsSection>
|
|
||||||
|
|
||||||
<SettingsSection>
|
<SettingsSection>
|
||||||
<SettingsSectionHeader>
|
<SettingsSectionHeader>
|
||||||
<SettingsSectionTitle>
|
<SettingsSectionTitle>
|
||||||
@@ -348,6 +307,33 @@ export default function GeneralForm() {
|
|||||||
className="grid grid-cols-1 md:grid-cols-2 gap-4"
|
className="grid grid-cols-1 md:grid-cols-2 gap-4"
|
||||||
id="general-settings-form"
|
id="general-settings-form"
|
||||||
>
|
>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="enabled"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="col-span-2">
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<FormControl>
|
||||||
|
<SwitchInput
|
||||||
|
id="enable-resource"
|
||||||
|
defaultChecked={resource.enabled}
|
||||||
|
onCheckedChange={(val) => form.setValue("enabled", val)}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<div className="space-y-1">
|
||||||
|
<FormLabel className="text-base">
|
||||||
|
{t("resourceEnable")}
|
||||||
|
</FormLabel>
|
||||||
|
<FormDescription>
|
||||||
|
{t("resourceVisibilityTitleDescription")}
|
||||||
|
</FormDescription>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="name"
|
name="name"
|
||||||
@@ -612,7 +598,7 @@ export default function GeneralForm() {
|
|||||||
disabled={saveLoading}
|
disabled={saveLoading}
|
||||||
form="general-settings-form"
|
form="general-settings-form"
|
||||||
>
|
>
|
||||||
{t("saveGeneralSettings")}
|
{t("saveSettings")}
|
||||||
</Button>
|
</Button>
|
||||||
</SettingsSectionFooter>
|
</SettingsSectionFooter>
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
|
|||||||
@@ -233,35 +233,6 @@ export default function ResourceRules(props: {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveApplyRules(val: boolean) {
|
|
||||||
const res = await api
|
|
||||||
.post(`/resource/${params.resourceId}`, {
|
|
||||||
applyRules: val
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.error(err);
|
|
||||||
toast({
|
|
||||||
variant: "destructive",
|
|
||||||
title: t('rulesErrorUpdate'),
|
|
||||||
description: formatAxiosError(
|
|
||||||
err,
|
|
||||||
t('rulesErrorUpdateDescription')
|
|
||||||
)
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if (res && res.status === 200) {
|
|
||||||
setRulesEnabled(val);
|
|
||||||
updateResource({ applyRules: val });
|
|
||||||
|
|
||||||
toast({
|
|
||||||
title: t('rulesUpdated'),
|
|
||||||
description: t('rulesUpdatedDescription')
|
|
||||||
});
|
|
||||||
router.refresh();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getValueHelpText(type: string) {
|
function getValueHelpText(type: string) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "CIDR":
|
case "CIDR":
|
||||||
@@ -273,9 +244,33 @@ export default function ResourceRules(props: {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveRules() {
|
async function saveAllSettings() {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
|
// Save rules enabled state
|
||||||
|
const res = await api
|
||||||
|
.post(`/resource/${params.resourceId}`, {
|
||||||
|
applyRules: rulesEnabled
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error(err);
|
||||||
|
toast({
|
||||||
|
variant: "destructive",
|
||||||
|
title: t('rulesErrorUpdate'),
|
||||||
|
description: formatAxiosError(
|
||||||
|
err,
|
||||||
|
t('rulesErrorUpdateDescription')
|
||||||
|
)
|
||||||
|
});
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res && res.status === 200) {
|
||||||
|
updateResource({ applyRules: rulesEnabled });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save rules
|
||||||
for (let rule of rules) {
|
for (let rule of rules) {
|
||||||
const data = {
|
const data = {
|
||||||
action: rule.action,
|
action: rule.action,
|
||||||
@@ -585,25 +580,6 @@ export default function ResourceRules(props: {
|
|||||||
{/* </AlertDescription> */}
|
{/* </AlertDescription> */}
|
||||||
{/* </Alert> */}
|
{/* </Alert> */}
|
||||||
|
|
||||||
<SettingsSection>
|
|
||||||
<SettingsSectionHeader>
|
|
||||||
<SettingsSectionTitle>{t('rulesEnable')}</SettingsSectionTitle>
|
|
||||||
<SettingsSectionDescription>
|
|
||||||
{t('rulesEnableDescription')}
|
|
||||||
</SettingsSectionDescription>
|
|
||||||
</SettingsSectionHeader>
|
|
||||||
<SettingsSectionBody>
|
|
||||||
<SwitchInput
|
|
||||||
id="rules-toggle"
|
|
||||||
label={t('rulesEnable')}
|
|
||||||
defaultChecked={rulesEnabled}
|
|
||||||
onCheckedChange={async (val) => {
|
|
||||||
await saveApplyRules(val);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</SettingsSectionBody>
|
|
||||||
</SettingsSection>
|
|
||||||
|
|
||||||
<SettingsSection>
|
<SettingsSection>
|
||||||
<SettingsSectionHeader>
|
<SettingsSectionHeader>
|
||||||
<SettingsSectionTitle>
|
<SettingsSectionTitle>
|
||||||
@@ -614,167 +590,186 @@ export default function ResourceRules(props: {
|
|||||||
</SettingsSectionDescription>
|
</SettingsSectionDescription>
|
||||||
</SettingsSectionHeader>
|
</SettingsSectionHeader>
|
||||||
<SettingsSectionBody>
|
<SettingsSectionBody>
|
||||||
<Form {...addRuleForm}>
|
<div className="space-y-6">
|
||||||
<form
|
<div className="flex items-center space-x-2">
|
||||||
onSubmit={addRuleForm.handleSubmit(addRule)}
|
<SwitchInput
|
||||||
className="space-y-4"
|
id="rules-toggle"
|
||||||
>
|
defaultChecked={rulesEnabled}
|
||||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 items-end">
|
onCheckedChange={(val) => setRulesEnabled(val)}
|
||||||
<FormField
|
/>
|
||||||
control={addRuleForm.control}
|
<div className="space-y-1">
|
||||||
name="action"
|
<label className="text-base font-medium">
|
||||||
render={({ field }) => (
|
{t('rulesEnable')}
|
||||||
<FormItem>
|
</label>
|
||||||
<FormLabel>{t('rulesAction')}</FormLabel>
|
<p className="text-sm text-muted-foreground">
|
||||||
<FormControl>
|
{t('rulesEnableDescription')}
|
||||||
<Select
|
</p>
|
||||||
value={field.value}
|
|
||||||
onValueChange={
|
|
||||||
field.onChange
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<SelectTrigger className="w-full">
|
|
||||||
<SelectValue />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="ACCEPT">
|
|
||||||
{RuleAction.ACCEPT}
|
|
||||||
</SelectItem>
|
|
||||||
<SelectItem value="DROP">
|
|
||||||
{RuleAction.DROP}
|
|
||||||
</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={addRuleForm.control}
|
|
||||||
name="match"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>{t('rulesMatchType')}</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Select
|
|
||||||
value={field.value}
|
|
||||||
onValueChange={
|
|
||||||
field.onChange
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<SelectTrigger className="w-full">
|
|
||||||
<SelectValue />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{resource.http && (
|
|
||||||
<SelectItem value="PATH">
|
|
||||||
{RuleMatch.PATH}
|
|
||||||
</SelectItem>
|
|
||||||
)}
|
|
||||||
<SelectItem value="IP">
|
|
||||||
{RuleMatch.IP}
|
|
||||||
</SelectItem>
|
|
||||||
<SelectItem value="CIDR">
|
|
||||||
{RuleMatch.CIDR}
|
|
||||||
</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={addRuleForm.control}
|
|
||||||
name="value"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="gap-1">
|
|
||||||
<InfoPopup
|
|
||||||
text={t('value')}
|
|
||||||
info={
|
|
||||||
getValueHelpText(
|
|
||||||
addRuleForm.watch(
|
|
||||||
"match"
|
|
||||||
)
|
|
||||||
) || ""
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<FormControl>
|
|
||||||
<Input {...field}/>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
variant="secondary"
|
|
||||||
disabled={!rulesEnabled}
|
|
||||||
>
|
|
||||||
{t('ruleSubmit')}
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</div>
|
||||||
</Form>
|
|
||||||
<Table>
|
<Form {...addRuleForm}>
|
||||||
<TableHeader>
|
<form
|
||||||
{table.getHeaderGroups().map((headerGroup) => (
|
onSubmit={addRuleForm.handleSubmit(addRule)}
|
||||||
<TableRow key={headerGroup.id}>
|
className="space-y-4"
|
||||||
{headerGroup.headers.map((header) => (
|
>
|
||||||
<TableHead key={header.id}>
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 items-end">
|
||||||
{header.isPlaceholder
|
<FormField
|
||||||
? null
|
control={addRuleForm.control}
|
||||||
: flexRender(
|
name="action"
|
||||||
header.column.columnDef
|
render={({ field }) => (
|
||||||
.header,
|
<FormItem>
|
||||||
header.getContext()
|
<FormLabel>{t('rulesAction')}</FormLabel>
|
||||||
)}
|
<FormControl>
|
||||||
</TableHead>
|
<Select
|
||||||
))}
|
value={field.value}
|
||||||
</TableRow>
|
onValueChange={
|
||||||
))}
|
field.onChange
|
||||||
</TableHeader>
|
}
|
||||||
<TableBody>
|
>
|
||||||
{table.getRowModel().rows?.length ? (
|
<SelectTrigger className="w-full">
|
||||||
table.getRowModel().rows.map((row) => (
|
<SelectValue />
|
||||||
<TableRow key={row.id}>
|
</SelectTrigger>
|
||||||
{row.getVisibleCells().map((cell) => (
|
<SelectContent>
|
||||||
<TableCell key={cell.id}>
|
<SelectItem value="ACCEPT">
|
||||||
{flexRender(
|
{RuleAction.ACCEPT}
|
||||||
cell.column.columnDef.cell,
|
</SelectItem>
|
||||||
cell.getContext()
|
<SelectItem value="DROP">
|
||||||
)}
|
{RuleAction.DROP}
|
||||||
</TableCell>
|
</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={addRuleForm.control}
|
||||||
|
name="match"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>{t('rulesMatchType')}</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Select
|
||||||
|
value={field.value}
|
||||||
|
onValueChange={
|
||||||
|
field.onChange
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="w-full">
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{resource.http && (
|
||||||
|
<SelectItem value="PATH">
|
||||||
|
{RuleMatch.PATH}
|
||||||
|
</SelectItem>
|
||||||
|
)}
|
||||||
|
<SelectItem value="IP">
|
||||||
|
{RuleMatch.IP}
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="CIDR">
|
||||||
|
{RuleMatch.CIDR}
|
||||||
|
</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={addRuleForm.control}
|
||||||
|
name="value"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="gap-1">
|
||||||
|
<InfoPopup
|
||||||
|
text={t('value')}
|
||||||
|
info={
|
||||||
|
getValueHelpText(
|
||||||
|
addRuleForm.watch(
|
||||||
|
"match"
|
||||||
|
)
|
||||||
|
) || ""
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<FormControl>
|
||||||
|
<Input {...field}/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
variant="secondary"
|
||||||
|
disabled={!rulesEnabled}
|
||||||
|
>
|
||||||
|
{t('ruleSubmit')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
|
<TableRow key={headerGroup.id}>
|
||||||
|
{headerGroup.headers.map((header) => (
|
||||||
|
<TableHead key={header.id}>
|
||||||
|
{header.isPlaceholder
|
||||||
|
? null
|
||||||
|
: flexRender(
|
||||||
|
header.column.columnDef
|
||||||
|
.header,
|
||||||
|
header.getContext()
|
||||||
|
)}
|
||||||
|
</TableHead>
|
||||||
))}
|
))}
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))
|
))}
|
||||||
) : (
|
</TableHeader>
|
||||||
<TableRow>
|
<TableBody>
|
||||||
<TableCell
|
{table.getRowModel().rows?.length ? (
|
||||||
colSpan={columns.length}
|
table.getRowModel().rows.map((row) => (
|
||||||
className="h-24 text-center"
|
<TableRow key={row.id}>
|
||||||
>
|
{row.getVisibleCells().map((cell) => (
|
||||||
{t('rulesNoOne')}
|
<TableCell key={cell.id}>
|
||||||
</TableCell>
|
{flexRender(
|
||||||
</TableRow>
|
cell.column.columnDef.cell,
|
||||||
)}
|
cell.getContext()
|
||||||
</TableBody>
|
)}
|
||||||
{/* <TableCaption> */}
|
</TableCell>
|
||||||
{/* {t('rulesOrder')} */}
|
))}
|
||||||
{/* </TableCaption> */}
|
</TableRow>
|
||||||
</Table>
|
))
|
||||||
|
) : (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell
|
||||||
|
colSpan={columns.length}
|
||||||
|
className="h-24 text-center"
|
||||||
|
>
|
||||||
|
{t('rulesNoOne')}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
{/* <TableCaption> */}
|
||||||
|
{/* {t('rulesOrder')} */}
|
||||||
|
{/* </TableCaption> */}
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
</SettingsSectionBody>
|
</SettingsSectionBody>
|
||||||
<SettingsSectionFooter>
|
|
||||||
<Button
|
|
||||||
onClick={saveRules}
|
|
||||||
loading={loading}
|
|
||||||
disabled={loading}
|
|
||||||
>
|
|
||||||
{t('rulesSubmit')}
|
|
||||||
</Button>
|
|
||||||
</SettingsSectionFooter>
|
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
|
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<Button
|
||||||
|
onClick={saveAllSettings}
|
||||||
|
loading={loading}
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
{t('saveAllSettings')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</SettingsContainer>
|
</SettingsContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,8 +24,7 @@ import {
|
|||||||
SettingsSectionTitle,
|
SettingsSectionTitle,
|
||||||
SettingsSectionDescription,
|
SettingsSectionDescription,
|
||||||
SettingsSectionBody,
|
SettingsSectionBody,
|
||||||
SettingsSectionForm,
|
SettingsSectionForm
|
||||||
SettingsSectionFooter
|
|
||||||
} from "@app/components/Settings";
|
} from "@app/components/Settings";
|
||||||
import { formatAxiosError } from "@app/lib/api";
|
import { formatAxiosError } from "@app/lib/api";
|
||||||
import { createApiClient } from "@app/lib/api";
|
import { createApiClient } from "@app/lib/api";
|
||||||
@@ -177,18 +176,18 @@ export default function GeneralPage() {
|
|||||||
</Form>
|
</Form>
|
||||||
</SettingsSectionForm>
|
</SettingsSectionForm>
|
||||||
</SettingsSectionBody>
|
</SettingsSectionBody>
|
||||||
|
|
||||||
<SettingsSectionFooter>
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
form="general-settings-form"
|
|
||||||
loading={loading}
|
|
||||||
disabled={loading}
|
|
||||||
>
|
|
||||||
{t("saveGeneralSettings")}
|
|
||||||
</Button>
|
|
||||||
</SettingsSectionFooter>
|
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
|
|
||||||
|
<div className="flex justify-end mt-6">
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
form="general-settings-form"
|
||||||
|
loading={loading}
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
Save All Settings
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</SettingsContainer>
|
</SettingsContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { Label } from "./ui/label";
|
|||||||
|
|
||||||
interface SwitchComponentProps {
|
interface SwitchComponentProps {
|
||||||
id: string;
|
id: string;
|
||||||
label: string;
|
label?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
defaultChecked?: boolean;
|
defaultChecked?: boolean;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
@@ -28,7 +28,7 @@ export function SwitchInput({
|
|||||||
onCheckedChange={onCheckedChange}
|
onCheckedChange={onCheckedChange}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
/>
|
/>
|
||||||
<Label htmlFor={id}>{label}</Label>
|
{label && <Label htmlFor={id}>{label}</Label>}
|
||||||
</div>
|
</div>
|
||||||
{description && (
|
{description && (
|
||||||
<span className="text-muted-foreground text-sm">
|
<span className="text-muted-foreground text-sm">
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|||||||
type={showPassword ? "text" : "password"}
|
type={showPassword ? "text" : "password"}
|
||||||
data-slot="input"
|
data-slot="input"
|
||||||
className={cn(
|
className={cn(
|
||||||
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base inset-shadow-2xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-2xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||||
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
|
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
|
||||||
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
||||||
className
|
className
|
||||||
@@ -43,7 +43,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|||||||
type={type}
|
type={type}
|
||||||
data-slot="input"
|
data-slot="input"
|
||||||
className={cn(
|
className={cn(
|
||||||
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base inset-shadow-2xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-2xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||||
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
|
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
|
||||||
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
||||||
className
|
className
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ const toastVariants = cva(
|
|||||||
variant: {
|
variant: {
|
||||||
default: "border bg-card text-foreground",
|
default: "border bg-card text-foreground",
|
||||||
destructive:
|
destructive:
|
||||||
"destructive group border-destructive bg-destructive text-destructive-foreground"
|
"destructive group border-destructive bg-destructive text-white dark:text-destructive-foreground"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
|
|||||||
Reference in New Issue
Block a user