Add enable rules toggle

This commit is contained in:
Owen 2025-02-09 11:02:40 -05:00
parent 874c67345e
commit 9694261f3e
No known key found for this signature in database
GPG key ID: 8271FDFFD9E0CCBD
3 changed files with 121 additions and 44 deletions

View file

@ -29,7 +29,8 @@ const updateResourceBodySchema = z
blockAccess: z.boolean().optional(), blockAccess: z.boolean().optional(),
proxyPort: z.number().int().min(1).max(65535).optional(), proxyPort: z.number().int().min(1).max(65535).optional(),
emailWhitelistEnabled: z.boolean().optional(), emailWhitelistEnabled: z.boolean().optional(),
isBaseDomain: z.boolean().optional() isBaseDomain: z.boolean().optional(),
applyRules: z.boolean().optional(),
}) })
.strict() .strict()
.refine((data) => Object.keys(data).length > 0, { .refine((data) => Object.keys(data).length > 0, {

View file

@ -131,7 +131,7 @@ export default function ReverseProxyTargets(props: {
resolver: zodResolver(addTargetSchema), resolver: zodResolver(addTargetSchema),
defaultValues: { defaultValues: {
ip: "", ip: "",
method: resource.http ? "http" : null, method: resource.http ? "http" : null
// protocol: "TCP", // protocol: "TCP",
} as z.infer<typeof addTargetSchema> } as z.infer<typeof addTargetSchema>
}); });
@ -316,10 +316,23 @@ export default function ReverseProxyTargets(props: {
} }
async function saveSsl(val: boolean) { async function saveSsl(val: boolean) {
const res = await api.post(`/resource/${params.resourceId}`, { const res = await api
.post(`/resource/${params.resourceId}`, {
ssl: val ssl: val
})
.catch((err) => {
console.error(err);
toast({
variant: "destructive",
title: "Failed to update SSL configuration",
description: formatAxiosError(
err,
"An error occurred while updating the SSL configuration"
)
});
}); });
if (res && res.status === 200) {
setSslEnabled(val); setSslEnabled(val);
updateResource({ ssl: val }); updateResource({ ssl: val });
@ -328,6 +341,7 @@ export default function ReverseProxyTargets(props: {
description: "SSL configuration updated successfully" description: "SSL configuration updated successfully"
}); });
} }
}
const columns: ColumnDef<LocalTarget>[] = [ const columns: ColumnDef<LocalTarget>[] = [
{ {
@ -652,7 +666,8 @@ export default function ReverseProxyTargets(props: {
</Table> </Table>
</TableContainer> </TableContainer>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
Adding more than one target above will enable load balancing. Adding more than one target above will enable load
balancing.
</p> </p>
</SettingsSectionBody> </SettingsSectionBody>
<SettingsSectionFooter> <SettingsSectionFooter>

View file

@ -56,6 +56,7 @@ import {
SettingsSectionFooter SettingsSectionFooter
} from "@app/components/Settings"; } from "@app/components/Settings";
import { ListResourceRulesResponse } from "@server/routers/resource/listResourceRules"; import { ListResourceRulesResponse } from "@server/routers/resource/listResourceRules";
import { SwitchInput } from "@app/components/SwitchInput";
// Schema for rule validation // Schema for rule validation
const addRuleSchema = z.object({ const addRuleSchema = z.object({
@ -74,12 +75,13 @@ export default function ResourceRules(props: {
}) { }) {
const params = use(props.params); const params = use(props.params);
const { toast } = useToast(); const { toast } = useToast();
const { resource } = useResourceContext(); const { resource, updateResource } = useResourceContext();
const api = createApiClient(useEnvContext()); const api = createApiClient(useEnvContext());
const [rules, setRules] = useState<LocalRule[]>([]); const [rules, setRules] = useState<LocalRule[]>([]);
const [rulesToRemove, setRulesToRemove] = useState<number[]>([]); const [rulesToRemove, setRulesToRemove] = useState<number[]>([]);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [pageLoading, setPageLoading] = useState(true); const [pageLoading, setPageLoading] = useState(true);
const [rulesEnabled, setRulesEnabled] = useState(resource.applyRules);
const addRuleForm = useForm({ const addRuleForm = useForm({
resolver: zodResolver(addRuleSchema), resolver: zodResolver(addRuleSchema),
@ -180,6 +182,34 @@ 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: "Failed to update rules",
description: formatAxiosError(
err,
"An error occurred while updating rules"
)
});
});
if (res && res.status === 200) {
setRulesEnabled(val);
updateResource({ applyRules: val });
toast({
title: "Enable Rules",
description: "Rule evaluation has been updated"
});
}
}
async function saveRules() { async function saveRules() {
try { try {
setLoading(true); setLoading(true);
@ -199,7 +229,10 @@ export default function ResourceRules(props: {
setLoading(false); setLoading(false);
return; return;
} }
if (rule.match === "PATH" && !isValidUrlGlobPattern(rule.value)) { if (
rule.match === "PATH" &&
!isValidUrlGlobPattern(rule.value)
) {
toast({ toast({
variant: "destructive", variant: "destructive",
title: "Invalid URL path", title: "Invalid URL path",
@ -334,6 +367,25 @@ export default function ResourceRules(props: {
return ( return (
<SettingsContainer> <SettingsContainer>
<SettingsSection>
<SettingsSectionHeader>
<SettingsSectionTitle>Enable Rules</SettingsSectionTitle>
<SettingsSectionDescription>
Enable or disable rule evaluation for this resource
</SettingsSectionDescription>
</SettingsSectionHeader>
<SettingsSectionBody>
<SwitchInput
id="rules-toggle"
label="Enable Rules"
defaultChecked={resource.applyRules}
onCheckedChange={async (val) => {
await saveApplyRules(val);
}}
/>
</SettingsSectionBody>
</SettingsSection>
<SettingsSection> <SettingsSection>
<SettingsSectionHeader> <SettingsSectionHeader>
<SettingsSectionTitle> <SettingsSectionTitle>
@ -400,9 +452,11 @@ export default function ResourceRules(props: {
<SelectItem value="CIDR"> <SelectItem value="CIDR">
CIDR CIDR
</SelectItem> </SelectItem>
{resource.http && (
<SelectItem value="PATH"> <SelectItem value="PATH">
PATH PATH
</SelectItem> </SelectItem>
)}
</SelectContent> </SelectContent>
</Select> </Select>
</FormControl> </FormControl>
@ -421,14 +475,21 @@ export default function ResourceRules(props: {
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
<FormDescription> <FormDescription>
Enter CIDR or path value based Enter CIDR{" "}
on match type {resource.http
? "or path value"
: ""}{" "}
based on match type
</FormDescription> </FormDescription>
</FormItem> </FormItem>
)} )}
/> />
</div> </div>
<Button type="submit" variant="outline"> <Button
type="submit"
variant="outline"
disabled={loading || !rulesEnabled}
>
Add Rule Add Rule
</Button> </Button>
</form> </form>
@ -518,7 +579,7 @@ function isValidCIDR(cidr: string): boolean {
function isValidUrlGlobPattern(pattern: string): boolean { function isValidUrlGlobPattern(pattern: string): boolean {
// Remove leading slash if present // Remove leading slash if present
pattern = pattern.startsWith('/') ? pattern.slice(1) : pattern; pattern = pattern.startsWith("/") ? pattern.slice(1) : pattern;
// Empty string is not valid // Empty string is not valid
if (!pattern) { if (!pattern) {
@ -526,7 +587,7 @@ function isValidUrlGlobPattern(pattern: string): boolean {
} }
// Split path into segments // Split path into segments
const segments = pattern.split('/'); const segments = pattern.split("/");
// Check each segment // Check each segment
for (let i = 0; i < segments.length; i++) { for (let i = 0; i < segments.length; i++) {
@ -538,7 +599,7 @@ function isValidUrlGlobPattern(pattern: string): boolean {
} }
// If segment contains *, it must be exactly * // If segment contains *, it must be exactly *
if (segment.includes('*') && segment !== '*') { if (segment.includes("*") && segment !== "*") {
return false; return false;
} }