From 75cec731e854d40763599a69f506ec3b168af60d Mon Sep 17 00:00:00 2001 From: Adrian Astles <49412215+adrianeastles@users.noreply.github.com> Date: Fri, 8 Aug 2025 19:30:26 +0800 Subject: [PATCH] Resource Rules page: Split into 3 clear sections: Enabled Rules (with explanation), Rule Templates, and Resource Rules Configuration Hide Rules Configuration when rules are disabled MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rule Template pages: Rules: adopt Settings section layout; right-aligned “Add Rule” button that opens a Create Rule dialog; remove inline add form; consistent table styling --- .../resources/[resourceId]/rules/page.tsx | 582 +++++++++--------- .../[templateId]/general/page.tsx | 69 ++- .../[templateId]/rules/page.tsx | 25 +- .../ruleTemplate/ResourceRulesManager.tsx | 77 ++- .../ruleTemplate/TemplateRulesManager.tsx | 211 ++++--- 5 files changed, 498 insertions(+), 466 deletions(-) diff --git a/src/app/[orgId]/settings/resources/[resourceId]/rules/page.tsx b/src/app/[orgId]/settings/resources/[resourceId]/rules/page.tsx index a8edbdff..bd42db03 100644 --- a/src/app/[orgId]/settings/resources/[resourceId]/rules/page.tsx +++ b/src/app/[orgId]/settings/resources/[resourceId]/rules/page.tsx @@ -57,8 +57,7 @@ import { } from "@app/components/Settings"; import { ListResourceRulesResponse } from "@server/routers/resource/listResourceRules"; import { SwitchInput } from "@app/components/SwitchInput"; -import { Alert, AlertDescription, AlertTitle } from "@app/components/ui/alert"; -import { ArrowUpDown, Check, InfoIcon, X, ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from "lucide-react"; +import { ArrowUpDown, Check, X, ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from "lucide-react"; import { InfoSection, InfoSections, @@ -74,6 +73,15 @@ import { Switch } from "@app/components/ui/switch"; import { useRouter } from "next/navigation"; import { useTranslations } from "next-intl"; import { ResourceRulesManager } from "@app/components/ruleTemplate/ResourceRulesManager"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger +} from "@app/components/ui/dialog"; // Schema for rule validation const addRuleSchema = z.object({ @@ -103,6 +111,7 @@ export default function ResourceRules(props: { pageIndex: 0, pageSize: 25 }); + const [createDialogOpen, setCreateDialogOpen] = useState(false); const router = useRouter(); const t = useTranslations(); @@ -574,302 +583,63 @@ export default function ResourceRules(props: { return ( - {/* */} - {/* */} - {/* {t('rulesAbout')} */} - {/* */} - {/*
*/} - {/*

*/} - {/* {t('rulesAboutDescription')} */} - {/*

*/} - {/*
*/} - {/* */} - {/* */} - {/* {t('rulesActions')} */} - {/*
    */} - {/*
  • */} - {/* */} - {/* {t('rulesActionAlwaysAllow')} */} - {/*
  • */} - {/*
  • */} - {/* */} - {/* {t('rulesActionAlwaysDeny')} */} - {/*
  • */} - {/*
*/} - {/*
*/} - {/* */} - {/* */} - {/* {t('rulesMatchCriteria')} */} - {/* */} - {/*
    */} - {/*
  • */} - {/* {t('rulesMatchCriteriaIpAddress')} */} - {/*
  • */} - {/*
  • */} - {/* {t('rulesMatchCriteriaIpAddressRange')} */} - {/*
  • */} - {/*
  • */} - {/* {t('rulesMatchCriteriaUrl')} */} - {/*
  • */} - {/*
*/} - {/*
*/} - {/*
*/} - {/*
*/} - {/*
*/} - + {/* 1. Enabled Rules Control & How it works */} - {t('rulesResource')} + {t('rulesEnable')} - {t('rulesResourceDescription')} + {t('rulesEnableDescription')} -
-
- setRulesEnabled(val)} - /> +
+ setRulesEnabled(val)} + /> +
+
+
+ {t('rulesAboutDescription')}
- -
- -
- ( - - {t('rulesAction')} - - - - - - )} - /> - ( - - {t('rulesMatchType')} - - - - - - )} - /> - ( - - - - - - - - )} - /> - -
-
- - - - {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header) => ( - - {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef - .header, - header.getContext() - )} - - ))} - - ))} - - - {table.getRowModel().rows?.length ? ( - table.getRowModel().rows.map((row) => ( - - {row.getVisibleCells().map((cell) => ( - - {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} - - ))} - - )) - ) : ( - - - {t('rulesNoOne')} - - - )} - - {/* */} - {/* {t('rulesOrder')} */} - {/* */} -
- - {/* Pagination Controls */} - {rules.length > 0 && ( -
-
- Showing {table.getState().pagination.pageIndex * table.getState().pagination.pageSize + 1} to{" "} - {Math.min( - (table.getState().pagination.pageIndex + 1) * table.getState().pagination.pageSize, - table.getFilteredRowModel().rows.length - )}{" "} - of {table.getFilteredRowModel().rows.length} rules -
-
-
-

Rows per page

- -
-
- Page {table.getState().pagination.pageIndex + 1} of{" "} - {table.getPageCount()} -
-
- - - - -
-
-
- )} + + + {t('rulesActions')} +
    +
  • + + {t('rulesActionAlwaysAllow')} +
  • +
  • + + {t('rulesActionAlwaysDeny')} +
  • +
+
+ + {t('rulesMatchCriteria')} +
    +
  • + {t('rulesMatchCriteriaIpAddress')} +
  • +
  • + {t('rulesMatchCriteriaIpAddressRange')} +
  • +
  • + {t('rulesMatchCriteriaUrl')} +
  • +
+
+
- {/* Template Assignment Section */} + {/* 2. Rule Templates Section */} {rulesEnabled && ( @@ -881,21 +651,247 @@ export default function ResourceRules(props: { - )} + {/* 3. Resource Rules Configuration */} + {rulesEnabled && ( + + + + {t('rulesResource')} + + + {t('rulesResourceDescription')} + + + +
+ + + + + + + {t('ruleSubmit')} + + {t('rulesResourceDescription')} + + +
+ { + await addRule(data); + setCreateDialogOpen(false); + })} + className="space-y-4" + > +
+ ( + + {t('rulesAction')} + + + + + + )} + /> + ( + + {t('rulesMatchType')} + + + + + + )} + /> + ( + + + + + + + + )} + /> +
+ + + +
+ +
+
+
+ + + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext() + )} + + ))} + + ))} + + + {table.getRowModel().rows?.length ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender( + cell.column.columnDef.cell, + cell.getContext() + )} + + ))} + + )) + ) : ( + + + {t('rulesNoOne')} + + + )} + +
+ + {rules.length > 0 && ( +
+
+ Showing {table.getState().pagination.pageIndex * table.getState().pagination.pageSize + 1} to{" "} + {Math.min( + (table.getState().pagination.pageIndex + 1) * table.getState().pagination.pageSize, + table.getFilteredRowModel().rows.length + )}{" "} + of {table.getFilteredRowModel().rows.length} rules +
+
+
+

Rows per page

+ +
+
+ Page {table.getState().pagination.pageIndex + 1} of {table.getPageCount()} +
+
+ + + + +
+
+
+ )} +
+
+ )} +
-
diff --git a/src/app/[orgId]/settings/rule-templates/[templateId]/general/page.tsx b/src/app/[orgId]/settings/rule-templates/[templateId]/general/page.tsx index 304df96a..05b44d4c 100644 --- a/src/app/[orgId]/settings/rule-templates/[templateId]/general/page.tsx +++ b/src/app/[orgId]/settings/rule-templates/[templateId]/general/page.tsx @@ -12,9 +12,13 @@ import { useEnvContext } from "@app/hooks/useEnvContext"; import { SettingsContainer, SettingsSection, - SettingsSectionHeader + SettingsSectionHeader, + SettingsSectionTitle, + SettingsSectionDescription, + SettingsSectionBody, + SettingsSectionFooter, + SettingsSectionForm } from "@app/components/Settings"; -import SettingsSectionTitle from "@app/components/SettingsSectionTitle"; import { Button } from "@app/components/ui/button"; import { Input } from "@app/components/ui/input"; import { Textarea } from "@app/components/ui/textarea"; @@ -118,37 +122,44 @@ export default function GeneralPage() { - + + {t("templateDetails")} + + + Update the name and description for this rule template. + -
-
- - - {errors.name && ( -

{errors.name.message}

- )} -
-
- -