From 2592b89bc61d67b28eb50bb52245d85e56eba2ba Mon Sep 17 00:00:00 2001 From: Ahmed Darrazi Date: Sun, 7 Dec 2025 01:50:34 +0100 Subject: [PATCH] feat: Add settings overview page with getAllPolicySettings - Add new /settings-overview route displaying all policy settings in table - Implement getAllPolicySettings() server action with tenant isolation - Add 'All Settings' navigation item with Database icon - Use date-fns for relative time display (lastSyncedAt) - Server-side rendering for optimal performance --- app/(app)/settings-overview/page.tsx | 100 +++++++++++++++++++++++++++ config/nav.ts | 3 +- lib/actions/policySettings.ts | 54 +++++++++++++++ 3 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 app/(app)/settings-overview/page.tsx diff --git a/app/(app)/settings-overview/page.tsx b/app/(app)/settings-overview/page.tsx new file mode 100644 index 0000000..5c995ad --- /dev/null +++ b/app/(app)/settings-overview/page.tsx @@ -0,0 +1,100 @@ +import { getAllPolicySettings } from '@/lib/actions/policySettings'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from '@/components/ui/table'; +import { formatDistanceToNow } from 'date-fns'; +import { de } from 'date-fns/locale'; + +export default async function SettingsOverviewPage() { + const result = await getAllPolicySettings(); + + if (!result.success) { + return ( +
+
+ + + Policy Settings Overview + + {result.error || 'Failed to load settings'} + + + +
+
+ ); + } + + const settings = result.data || []; + + return ( +
+
+ + + Policy Settings Overview + + {settings.length} policy setting{settings.length !== 1 ? 's' : ''} found + + + + {settings.length === 0 ? ( +
+

No policy settings found

+

+ Try syncing your policies first +

+
+ ) : ( +
+ + + + Policy Name + Policy Type + Setting Name + Setting Value + Last Synced + + + + {settings.map((setting) => ( + + + {setting.policyName} + + + + {setting.policyType} + + + + {setting.settingName} + + + {setting.settingValue} + + + {formatDistanceToNow(new Date(setting.lastSyncedAt), { + addSuffix: true, + locale: de, + })} + + + ))} + +
+
+ )} +
+
+
+
+ ); +} diff --git a/config/nav.ts b/config/nav.ts index 25ceb4d..db2d0ec 100644 --- a/config/nav.ts +++ b/config/nav.ts @@ -1,5 +1,5 @@ import { SidebarLink } from "@/components/SidebarItems"; -import { Cog, Globe, User, HomeIcon, Search } from "lucide-react"; +import { Cog, Globe, User, HomeIcon, Search, Database } from "lucide-react"; type AdditionalLinks = { title: string; @@ -9,6 +9,7 @@ type AdditionalLinks = { export const defaultLinks: SidebarLink[] = [ { href: "/dashboard", title: "Home", icon: HomeIcon }, { href: "/search", title: "Search", icon: Search }, + { href: "/settings-overview", title: "All Settings", icon: Database }, { href: "/account", title: "Account", icon: User }, { href: "/settings", title: "Settings", icon: Cog }, ]; diff --git a/lib/actions/policySettings.ts b/lib/actions/policySettings.ts index cb46fbe..04bb064 100644 --- a/lib/actions/policySettings.ts +++ b/lib/actions/policySettings.ts @@ -33,6 +33,13 @@ export interface RecentSettingsResult { error?: string; } +export interface AllSettingsResult { + success: boolean; + data?: PolicySettingSearchResult[]; + error?: string; + totalCount?: number; +} + /** * Search policy settings by keyword across settingName and settingValue * @@ -207,6 +214,53 @@ export async function getRecentPolicySettings( } } +/** + * Get all policy settings for the current user's tenant + * + * **Security**: Enforces tenant isolation with explicit WHERE tenantId filter + * + * @returns All policy settings for user's tenant + */ +export async function getAllPolicySettings(): Promise { + try { + const { session } = await getUserAuth(); + + // T017: Explicit security check + if (!session?.user) { + return { success: false, error: 'Unauthorized' }; + } + + // T017: Explicit security check + const tenantId = session.user.tenantId; + if (!tenantId) { + return { success: false, error: 'Tenant not found' }; + } + + // T017: Query filtered by tenantId for security + const results = await db + .select({ + id: policySettings.id, + policyName: policySettings.policyName, + policyType: policySettings.policyType, + settingName: policySettings.settingName, + settingValue: policySettings.settingValue, + lastSyncedAt: policySettings.lastSyncedAt, + }) + .from(policySettings) + .where(eq(policySettings.tenantId, tenantId)) // CRITICAL: Tenant isolation + .orderBy(desc(policySettings.lastSyncedAt)); + + return { + success: true, + data: results, + totalCount: results.length, + }; + } catch (error) { + console.error('Get all settings failed:', error); + return { success: false, error: 'Failed to fetch settings' }; + } +} + /** * TEMPORARY: Seed test data for the current user's tenant * This is a development helper to populate realistic policy settings