Browse Source

Add enable rules toggle

Owen 5 months ago
parent
commit
9694261f3e

+ 2 - 1
server/routers/resource/updateResource.ts

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

+ 26 - 11
src/app/[orgId]/settings/resources/[resourceId]/connectivity/page.tsx

@@ -131,7 +131,7 @@ export default function ReverseProxyTargets(props: {
         resolver: zodResolver(addTargetSchema),
         defaultValues: {
             ip: "",
-            method: resource.http ? "http" : null,
+            method: resource.http ? "http" : null
             // protocol: "TCP",
         } as z.infer<typeof addTargetSchema>
     });
@@ -316,17 +316,31 @@ export default function ReverseProxyTargets(props: {
     }
 
     async function saveSsl(val: boolean) {
-        const res = await api.post(`/resource/${params.resourceId}`, {
-            ssl: val
-        });
+        const res = await api
+            .post(`/resource/${params.resourceId}`, {
+                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"
+                    )
+                });
+            });
 
-        setSslEnabled(val);
-        updateResource({ ssl: val });
+        if (res && res.status === 200) {
+            setSslEnabled(val);
+            updateResource({ ssl: val });
 
-        toast({
-            title: "SSL Configuration",
-            description: "SSL configuration updated successfully"
-        });
+            toast({
+                title: "SSL Configuration",
+                description: "SSL configuration updated successfully"
+            });
+        }
     }
 
     const columns: ColumnDef<LocalTarget>[] = [
@@ -652,7 +666,8 @@ export default function ReverseProxyTargets(props: {
                         </Table>
                     </TableContainer>
                     <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>
                 </SettingsSectionBody>
                 <SettingsSectionFooter>

+ 93 - 32
src/app/[orgId]/settings/resources/[resourceId]/rules/page.tsx

@@ -56,6 +56,7 @@ import {
     SettingsSectionFooter
 } from "@app/components/Settings";
 import { ListResourceRulesResponse } from "@server/routers/resource/listResourceRules";
+import { SwitchInput } from "@app/components/SwitchInput";
 
 // Schema for rule validation
 const addRuleSchema = z.object({
@@ -74,12 +75,13 @@ export default function ResourceRules(props: {
 }) {
     const params = use(props.params);
     const { toast } = useToast();
-    const { resource } = useResourceContext();
+    const { resource, updateResource } = useResourceContext();
     const api = createApiClient(useEnvContext());
     const [rules, setRules] = useState<LocalRule[]>([]);
     const [rulesToRemove, setRulesToRemove] = useState<number[]>([]);
     const [loading, setLoading] = useState(false);
     const [pageLoading, setPageLoading] = useState(true);
+    const [rulesEnabled, setRulesEnabled] = useState(resource.applyRules);
 
     const addRuleForm = useForm({
         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() {
         try {
             setLoading(true);
@@ -199,7 +229,10 @@ export default function ResourceRules(props: {
                     setLoading(false);
                     return;
                 }
-                if (rule.match === "PATH" && !isValidUrlGlobPattern(rule.value)) {
+                if (
+                    rule.match === "PATH" &&
+                    !isValidUrlGlobPattern(rule.value)
+                ) {
                     toast({
                         variant: "destructive",
                         title: "Invalid URL path",
@@ -334,6 +367,25 @@ export default function ResourceRules(props: {
 
     return (
         <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>
                 <SettingsSectionHeader>
                     <SettingsSectionTitle>
@@ -400,9 +452,11 @@ export default function ResourceRules(props: {
                                                         <SelectItem value="CIDR">
                                                             CIDR
                                                         </SelectItem>
-                                                        <SelectItem value="PATH">
-                                                            PATH
-                                                        </SelectItem>
+                                                        {resource.http && (
+                                                            <SelectItem value="PATH">
+                                                                PATH
+                                                            </SelectItem>
+                                                        )}
                                                     </SelectContent>
                                                 </Select>
                                             </FormControl>
@@ -421,14 +475,21 @@ export default function ResourceRules(props: {
                                             </FormControl>
                                             <FormMessage />
                                             <FormDescription>
-                                                Enter CIDR or path value based
-                                                on match type
+                                                Enter CIDR{" "}
+                                                {resource.http
+                                                    ? "or path value"
+                                                    : ""}{" "}
+                                                based on match type
                                             </FormDescription>
                                         </FormItem>
                                     )}
                                 />
                             </div>
-                            <Button type="submit" variant="outline">
+                            <Button
+                                type="submit"
+                                variant="outline"
+                                disabled={loading || !rulesEnabled}
+                            >
                                 Add Rule
                             </Button>
                         </form>
@@ -518,35 +579,35 @@ function isValidCIDR(cidr: string): boolean {
 
 function isValidUrlGlobPattern(pattern: string): boolean {
     // Remove leading slash if present
-    pattern = pattern.startsWith('/') ? pattern.slice(1) : pattern;
-    
+    pattern = pattern.startsWith("/") ? pattern.slice(1) : pattern;
+
     // Empty string is not valid
     if (!pattern) {
-      return false;
+        return false;
     }
-  
+
     // Split path into segments
-    const segments = pattern.split('/');
-    
+    const segments = pattern.split("/");
+
     // Check each segment
     for (let i = 0; i < segments.length; i++) {
-      const segment = segments[i];
-      
-      // Empty segments are not allowed (double slashes)
-      if (!segment && i !== segments.length - 1) {
-        return false;
-      }
-      
-      // If segment contains *, it must be exactly *
-      if (segment.includes('*') && segment !== '*') {
-        return false;
-      }
-      
-      // Check for invalid characters
-      if (!/^[a-zA-Z0-9_*-]*$/.test(segment)) {
-        return false;
-      }
+        const segment = segments[i];
+
+        // Empty segments are not allowed (double slashes)
+        if (!segment && i !== segments.length - 1) {
+            return false;
+        }
+
+        // If segment contains *, it must be exactly *
+        if (segment.includes("*") && segment !== "*") {
+            return false;
+        }
+
+        // Check for invalid characters
+        if (!/^[a-zA-Z0-9_*-]*$/.test(segment)) {
+            return false;
+        }
     }
-    
+
     return true;
-  }
+}