From afca6f811dcc9df7c00c8ab321824f935cf53ed4 Mon Sep 17 00:00:00 2001 From: alphaeusmote <41258468-alphaeusmote@users.noreply.replit.com> Date: Tue, 8 Apr 2025 21:19:22 +0000 Subject: [PATCH] Restored to 'd77ce2adbe81c897769f79623edb2fdd7c5cdbc7' Replit-Restored-To: d77ce2adbe81c897769f79623edb2fdd7c5cdbc7 --- Dockerfile | 46 ++ client/src/App.tsx | 2 - .../ldap/enhanced-ldap-query-builder.tsx | 682 ------------------ .../components/ldap/ldap-query-builder.tsx | 441 ----------- client/src/layouts/dashboard-layout.tsx | 1 - client/src/lib/ldap-types.ts | 36 - client/src/pages/auth-page.tsx | 23 +- client/src/pages/ldap-query-builder-page.tsx | 537 -------------- package-lock.json | 225 ------ package.json | 2 - server/auth.ts | 169 ++--- server/authorization.ts | 103 ++- server/ldap-filter-builder.ts | 194 ----- server/ldap-query-builder-routes.ts | 653 ----------------- server/ldap.ts | 503 +++++-------- server/routes.ts | 73 -- server/storage.ts | 176 +---- shared/schema.ts | 75 -- 18 files changed, 357 insertions(+), 3584 deletions(-) create mode 100644 Dockerfile delete mode 100644 client/src/components/ldap/enhanced-ldap-query-builder.tsx delete mode 100644 client/src/components/ldap/ldap-query-builder.tsx delete mode 100644 client/src/lib/ldap-types.ts delete mode 100644 client/src/pages/ldap-query-builder-page.tsx delete mode 100644 server/ldap-filter-builder.ts delete mode 100644 server/ldap-query-builder-routes.ts diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..8838ae6 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,46 @@ +FROM node:20-slim AS builder + +WORKDIR /app + +# Copy package files +COPY package*.json ./ + +# Install dependencies +RUN npm ci + +# Copy the rest of the source code +COPY . . + +# Build the application +RUN npm run build + +# Production stage +FROM node:20-slim AS runner + +WORKDIR /app + +# Set environment to production +ENV NODE_ENV=production + +# Copy package files and install production dependencies +COPY package*.json ./ +RUN npm ci --production + +# Copy build artifacts from the builder stage +COPY --from=builder /app/dist ./dist + +# Copy schema files for Drizzle +COPY ./shared/schema.ts ./shared/ +COPY ./drizzle.config.ts ./ + +# Set the database URL environment variable (this will be overridden at runtime) +ENV DATABASE_URL=postgres://postgres:postgres@localhost:5432/postgres + +# Set session secret (should be overridden at runtime) +ENV SESSION_SECRET=changeme + +# Expose the port the app runs on +EXPOSE 5000 + +# Start the application +CMD ["node", "dist/index.js"] \ No newline at end of file diff --git a/client/src/App.tsx b/client/src/App.tsx index aa442dc..5393fbf 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -11,7 +11,6 @@ import ComputersPage from "@/pages/computers-page"; import DomainsPage from "@/pages/domains-page"; import ApiTokensPage from "@/pages/api-tokens-page"; import LdapConnectionsPage from "@/pages/ldap-connections-page"; -import LdapQueryBuilderPage from "@/pages/ldap-query-builder-page"; import SettingsPage from "@/pages/settings-page"; import UserManagementPage from "@/pages/user-management-page"; import { ProtectedRoute } from "./lib/protected-route"; @@ -31,7 +30,6 @@ function Router() { - diff --git a/client/src/components/ldap/enhanced-ldap-query-builder.tsx b/client/src/components/ldap/enhanced-ldap-query-builder.tsx deleted file mode 100644 index 3319b81..0000000 --- a/client/src/components/ldap/enhanced-ldap-query-builder.tsx +++ /dev/null @@ -1,682 +0,0 @@ -import React, { useEffect, useState } from "react"; -import { useQuery } from "@tanstack/react-query"; -import { useToast } from "@/hooks/use-toast"; -import { LdapConnection } from "@shared/schema"; -import { - Card, - CardContent, - CardHeader, - CardTitle, - CardDescription, - CardFooter -} from "@/components/ui/card"; -import { Button } from "@/components/ui/button"; -import { Loader2, Plus, Trash, Info } from "lucide-react"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { - Tabs, - TabsContent, - TabsList, - TabsTrigger, -} from "@/components/ui/tabs"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { Badge } from "@/components/ui/badge"; -import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from "@/components/ui/tooltip"; - -// Import shared types from our shared lib -import { LdapOperator, LdapCondition, LdapAttribute } from "@/lib/ldap-types"; - -export type LdapQueryBuilderParams = { - targetObject: "users" | "groups" | "computers" | "ous"; - filter: LdapCondition; -}; - -// Used for displaying the operators in the UI -const operatorLabels: Record = { - [LdapOperator.AND]: "AND (All conditions)", - [LdapOperator.OR]: "OR (Any condition)", - [LdapOperator.NOT]: "NOT", - [LdapOperator.EQUALS]: "Equals", - [LdapOperator.NOT_EQUALS]: "Does not equal", - [LdapOperator.STARTS_WITH]: "Starts with", - [LdapOperator.ENDS_WITH]: "Ends with", - [LdapOperator.CONTAINS]: "Contains", - [LdapOperator.GREATER_THAN]: "Greater than", - [LdapOperator.LESS_THAN]: "Less than", - [LdapOperator.PRESENT]: "Has value (attribute exists)", - [LdapOperator.APPROX]: "Approximately equals", -}; - -// Group operators by type for the UI -const logicalOperators = [LdapOperator.AND, LdapOperator.OR, LdapOperator.NOT]; -const comparisonOperators = [ - LdapOperator.EQUALS, - LdapOperator.NOT_EQUALS, - LdapOperator.STARTS_WITH, - LdapOperator.ENDS_WITH, - LdapOperator.CONTAINS, - LdapOperator.GREATER_THAN, - LdapOperator.LESS_THAN, - LdapOperator.PRESENT, - LdapOperator.APPROX, -]; - -interface LdapQueryBuilderProps { - connections: LdapConnection[]; - value: LdapQueryBuilderParams; - onChange: (value: LdapQueryBuilderParams) => void; - onTest?: () => void; - onSave?: () => void; -} - -export function EnhancedLdapQueryBuilder({ - connections, - value, - onChange, - onTest, - onSave -}: LdapQueryBuilderProps) { - const { toast } = useToast(); - const [selectedConnectionId, setSelectedConnectionId] = useState( - connections.length > 0 ? connections[0].id : null - ); - - // Load available attributes based on connection and object type - const { data: availableAttributes = [], isLoading: isLoadingAttributes } = useQuery({ - queryKey: ["/api/ldap-queries/attributes", { connectionId: selectedConnectionId, targetObject: value.targetObject }], - enabled: !!selectedConnectionId && !!value.targetObject, - }); - - // Group attributes by their type for better organization - const groupedAttributes = React.useMemo(() => { - const grouped = { - string: availableAttributes.filter(attr => !attr.type || attr.type === "string"), - number: availableAttributes.filter(attr => attr.type === "number"), - datetime: availableAttributes.filter(attr => attr.type === "datetime"), - boolean: availableAttributes.filter(attr => attr.type === "boolean"), - }; - - return grouped; - }, [availableAttributes]); - - // Update the selected connection ID if the connections list changes - useEffect(() => { - if (connections.length > 0 && !selectedConnectionId) { - setSelectedConnectionId(connections[0].id); - } - }, [connections, selectedConnectionId]); - - // Handle changes to the query target object type - const handleTargetObjectChange = (targetObject: "users" | "groups" | "computers" | "ous") => { - onChange({ - ...value, - targetObject, - }); - }; - - // Handle updates to a condition - const handleConditionChange = ( - condition: LdapCondition, - path: number[] = [] - ): LdapCondition => { - if (path.length === 0) { - return condition; - } - - const [index, ...restPath] = path; - const updatedConditions = [...(value.filter.conditions || [])]; - - if (restPath.length === 0) { - updatedConditions[index] = condition; - } else { - updatedConditions[index] = handleConditionChange( - updatedConditions[index], - restPath - ); - } - - return { - ...value.filter, - conditions: updatedConditions, - }; - }; - - // Add a new condition to a logical operator - const handleAddCondition = (path: number[] = []) => { - let currentCondition = value.filter; - let parent = currentCondition; - - // Navigate to the target condition - for (const index of path) { - if (!currentCondition.conditions) { - currentCondition.conditions = []; - } - parent = currentCondition; - currentCondition = currentCondition.conditions[index]; - } - - // Add a new condition - if (!parent.conditions) { - parent.conditions = []; - } - - parent.conditions.push({ - operator: LdapOperator.EQUALS, - attribute: availableAttributes.length > 0 ? availableAttributes[0].name : undefined, - value: "", - }); - - onChange({ ...value }); - }; - - // Remove a condition - const handleRemoveCondition = (path: number[]) => { - if (path.length === 0) return; - - const parentPath = path.slice(0, -1); - const index = path[path.length - 1]; - - let currentCondition = value.filter; - - // Navigate to the parent condition - for (const idx of parentPath) { - if (!currentCondition.conditions) return; - currentCondition = currentCondition.conditions[idx]; - } - - // Remove the condition - if (currentCondition.conditions) { - currentCondition.conditions = currentCondition.conditions.filter((_, i) => i !== index); - onChange({ ...value }); - } - }; - - // Get recommended operators based on attribute type - const getRecommendedOperators = (attributeName: string | undefined): LdapOperator[] => { - if (!attributeName) return comparisonOperators; - - const attribute = availableAttributes.find(attr => attr.name === attributeName); - if (!attribute) return comparisonOperators; - - // Return appropriate operators based on type - switch(attribute.type) { - case "number": - return [ - LdapOperator.EQUALS, - LdapOperator.NOT_EQUALS, - LdapOperator.GREATER_THAN, - LdapOperator.LESS_THAN, - LdapOperator.PRESENT - ]; - case "datetime": - return [ - LdapOperator.EQUALS, - LdapOperator.NOT_EQUALS, - LdapOperator.GREATER_THAN, - LdapOperator.LESS_THAN, - LdapOperator.PRESENT - ]; - case "boolean": - return [ - LdapOperator.EQUALS, - LdapOperator.NOT_EQUALS, - LdapOperator.PRESENT - ]; - default: // string - return [ - LdapOperator.EQUALS, - LdapOperator.NOT_EQUALS, - LdapOperator.STARTS_WITH, - LdapOperator.ENDS_WITH, - LdapOperator.CONTAINS, - LdapOperator.PRESENT, - LdapOperator.APPROX - ]; - } - }; - - // UI component for rendering a single condition - const ConditionItem = ({ - condition, - path = [] - }: { - condition: LdapCondition; - path: number[]; - }) => { - const isLogical = logicalOperators.includes(condition.operator); - const selectedAttribute = availableAttributes.find(attr => attr.name === condition.attribute); - const recommendedOperators = !isLogical ? getRecommendedOperators(condition.attribute) : []; - - return ( -
-
- - - {!isLogical && ( - <> - - - {condition.operator !== LdapOperator.PRESENT && ( - { - const updatedCondition = { ...condition, value: e.target.value }; - const newFilter = handleConditionChange(updatedCondition, path); - onChange({ ...value, filter: newFilter }); - }} - /> - )} - - )} - - {path.length > 0 && ( - - )} -
- - {isLogical && ( -
- {condition.conditions?.map((childCondition, index) => ( - - ))} - -
- )} -
- ); - }; - - return ( - - - LDAP Query Builder - - Build LDAP queries with a visual interface - - - -
-
-
- - -
-
- - -
-
- -
- - - - Visual Builder - LDAP Filter Preview - - - - - -
-
-                    {JSON.stringify(value.filter, null, 2)}
-                  
-
-
-
-
- - {availableAttributes.length > 0 && ( -
- -
- {availableAttributes.map((attr) => ( - - - - - {attr.name} - {attr.isMultiValued && *} - - - -
-

{attr.name}

- {attr.description &&

{attr.description}

} -

Type: {attr.type || 'string'}

- {attr.isMultiValued && ( -

Multi-valued attribute

- )} -
-
-
-
- ))} -
-
- )} -
-
- - {onTest && ( - - )} - {onSave && ( - - )} - -
- ); -} \ No newline at end of file diff --git a/client/src/components/ldap/ldap-query-builder.tsx b/client/src/components/ldap/ldap-query-builder.tsx deleted file mode 100644 index 65e0f6c..0000000 --- a/client/src/components/ldap/ldap-query-builder.tsx +++ /dev/null @@ -1,441 +0,0 @@ -import React, { useEffect, useState } from "react"; -import { useQuery } from "@tanstack/react-query"; -import { useToast } from "@/hooks/use-toast"; -import { LdapConnection } from "@shared/schema"; -import { - Card, - CardContent, - CardHeader, - CardTitle, - CardDescription, - CardFooter -} from "@/components/ui/card"; -import { Button } from "@/components/ui/button"; -import { Loader2, Plus, Trash } from "lucide-react"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { - Tabs, - TabsContent, - TabsList, - TabsTrigger, -} from "@/components/ui/tabs"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { Badge } from "@/components/ui/badge"; - -// Import shared types from our shared lib -import { LdapOperator, LdapCondition, LdapAttribute } from "@/lib/ldap-types"; - -export type LdapQueryBuilderParams = { - targetObject: "users" | "groups" | "computers" | "ous"; - filter: LdapCondition; -}; - -// Used for displaying the operators in the UI -const operatorLabels: Record = { - [LdapOperator.AND]: "AND (All conditions)", - [LdapOperator.OR]: "OR (Any condition)", - [LdapOperator.NOT]: "NOT", - [LdapOperator.EQUALS]: "Equals", - [LdapOperator.NOT_EQUALS]: "Does not equal", - [LdapOperator.STARTS_WITH]: "Starts with", - [LdapOperator.ENDS_WITH]: "Ends with", - [LdapOperator.CONTAINS]: "Contains", - [LdapOperator.GREATER_THAN]: "Greater than", - [LdapOperator.LESS_THAN]: "Less than", - [LdapOperator.PRESENT]: "Has value (attribute exists)", - [LdapOperator.APPROX]: "Approximately equals", -}; - -// Group operators by type for the UI -const logicalOperators = [LdapOperator.AND, LdapOperator.OR, LdapOperator.NOT]; -const comparisonOperators = [ - LdapOperator.EQUALS, - LdapOperator.NOT_EQUALS, - LdapOperator.STARTS_WITH, - LdapOperator.ENDS_WITH, - LdapOperator.CONTAINS, - LdapOperator.GREATER_THAN, - LdapOperator.LESS_THAN, - LdapOperator.PRESENT, - LdapOperator.APPROX, -]; - -interface LdapQueryBuilderProps { - connections: LdapConnection[]; - value: LdapQueryBuilderParams; - onChange: (value: LdapQueryBuilderParams) => void; - onTest?: () => void; - onSave?: () => void; -} - -export function LdapQueryBuilder({ - connections, - value, - onChange, - onTest, - onSave -}: LdapQueryBuilderProps) { - const { toast } = useToast(); - const [selectedConnectionId, setSelectedConnectionId] = useState( - connections.length > 0 ? connections[0].id : null - ); - - // Load available attributes based on connection and object type - const { data: availableAttributes = [], isLoading: isLoadingAttributes } = useQuery({ - queryKey: ["/api/ldap-queries/attributes", { connectionId: selectedConnectionId, targetObject: value.targetObject }], - enabled: !!selectedConnectionId && !!value.targetObject, - }); - - // Update the selected connection ID if the connections list changes - useEffect(() => { - if (connections.length > 0 && !selectedConnectionId) { - setSelectedConnectionId(connections[0].id); - } - }, [connections, selectedConnectionId]); - - // Handle changes to the query target object type - const handleTargetObjectChange = (targetObject: "users" | "groups" | "computers" | "ous") => { - onChange({ - ...value, - targetObject, - }); - }; - - // Handle updates to a condition - const handleConditionChange = ( - condition: LdapCondition, - path: number[] = [] - ): LdapCondition => { - if (path.length === 0) { - return condition; - } - - const [index, ...restPath] = path; - const updatedConditions = [...(value.filter.conditions || [])]; - - if (restPath.length === 0) { - updatedConditions[index] = condition; - } else { - updatedConditions[index] = handleConditionChange( - updatedConditions[index], - restPath - ); - } - - return { - ...value.filter, - conditions: updatedConditions, - }; - }; - - // Add a new condition to a logical operator - const handleAddCondition = (path: number[] = []) => { - let currentCondition = value.filter; - let parent = currentCondition; - - // Navigate to the target condition - for (const index of path) { - if (!currentCondition.conditions) { - currentCondition.conditions = []; - } - parent = currentCondition; - currentCondition = currentCondition.conditions[index]; - } - - // Add a new condition - if (!parent.conditions) { - parent.conditions = []; - } - - parent.conditions.push({ - operator: LdapOperator.EQUALS, - attribute: availableAttributes.length > 0 ? availableAttributes[0].name : undefined, - value: "", - }); - - onChange({ ...value }); - }; - - // Remove a condition - const handleRemoveCondition = (path: number[]) => { - if (path.length === 0) return; - - const parentPath = path.slice(0, -1); - const index = path[path.length - 1]; - - let currentCondition = value.filter; - - // Navigate to the parent condition - for (const idx of parentPath) { - if (!currentCondition.conditions) return; - currentCondition = currentCondition.conditions[idx]; - } - - // Remove the condition - if (currentCondition.conditions) { - currentCondition.conditions = currentCondition.conditions.filter((_, i) => i !== index); - onChange({ ...value }); - } - }; - - // UI component for rendering a single condition - const ConditionItem = ({ - condition, - path = [] - }: { - condition: LdapCondition; - path: number[]; - }) => { - const isLogical = logicalOperators.includes(condition.operator); - - return ( -
-
- - - {!isLogical && ( - <> - - - {condition.operator !== LdapOperator.PRESENT && ( - { - const updatedCondition = { ...condition, value: e.target.value }; - const newFilter = handleConditionChange(updatedCondition, path); - onChange({ ...value, filter: newFilter }); - }} - /> - )} - - )} - - {path.length > 0 && ( - - )} -
- - {isLogical && ( -
- {condition.conditions?.map((childCondition, index) => ( - - ))} - -
- )} -
- ); - }; - - return ( - - - LDAP Query Builder - - Build LDAP queries with a visual interface - - - -
-
-
- - -
-
- - -
-
- -
- - - - Visual Builder - LDAP Filter Preview - - - - - -
-
-                    {JSON.stringify(value.filter, null, 2)}
-                  
-
-
-
-
- - {availableAttributes.length > 0 && ( -
- -
- {availableAttributes.map((attr) => ( - - {attr.name} {attr.type ? `(${attr.type})` : ''} - - ))} -
-
- )} -
-
- - {onTest && ( - - )} - {onSave && ( - - )} - -
- ); -} \ No newline at end of file diff --git a/client/src/layouts/dashboard-layout.tsx b/client/src/layouts/dashboard-layout.tsx index 6f7bf91..bd72bad 100644 --- a/client/src/layouts/dashboard-layout.tsx +++ b/client/src/layouts/dashboard-layout.tsx @@ -67,7 +67,6 @@ const menuSections: MenuSection[] = [ items: [ { title: "API Tokens", path: "/api-tokens", icon: }, { title: "LDAP Connections", path: "/ldap-connections", icon: }, - { title: "LDAP Query Builder", path: "/ldap-query-builder", icon: }, { title: "Settings", path: "/settings", icon: }, { title: "User Management", path: "/user-management", icon: }, ], diff --git a/client/src/lib/ldap-types.ts b/client/src/lib/ldap-types.ts deleted file mode 100644 index 2afa14b..0000000 --- a/client/src/lib/ldap-types.ts +++ /dev/null @@ -1,36 +0,0 @@ -// LDAP query types -export enum LdapOperator { - // Logical operators - AND = "and", - OR = "or", - NOT = "not", - - // Comparison operators - EQUALS = "equals", - NOT_EQUALS = "notEquals", - STARTS_WITH = "startsWith", - ENDS_WITH = "endsWith", - CONTAINS = "contains", - GREATER_THAN = "greaterThan", - LESS_THAN = "lessThan", - PRESENT = "present", // attribute exists - APPROX = "approx", // approximately equals -} - -// Type definition for an LDAP condition -export interface LdapCondition { - operator: LdapOperator; - attribute?: string; // Not required for logical operators (AND, OR, NOT) - value?: string; // Not required for some operators (PRESENT, logical operators) - conditions?: LdapCondition[]; // For nested conditions with logical operators -} - -// Type definition for an LDAP attribute -export interface LdapAttribute { - name: string; - type: string; - description?: string; - syntax?: string; - isMultiValued?: boolean; - isMandatory?: boolean; -} \ No newline at end of file diff --git a/client/src/pages/auth-page.tsx b/client/src/pages/auth-page.tsx index 84b67c2..1b8eb6b 100644 --- a/client/src/pages/auth-page.tsx +++ b/client/src/pages/auth-page.tsx @@ -100,29 +100,18 @@ export default function AuthPage() { }, }); - // Register mutation - using our simplified endpoint + // Register mutation const registerMutation = useMutation({ mutationFn: async (credentials: any) => { - try { - const res = await apiRequest("POST", "/api/simple-register", credentials); - if (!res.ok) { - const errorData = await res.json(); - throw new Error(errorData.message || "Registration failed"); - } - return await res.json(); - } catch (error) { - console.error("Registration error:", error); - throw error instanceof Error ? error : new Error("Unknown registration error"); - } + const res = await apiRequest("POST", "/api/register", credentials); + return await res.json(); }, - onSuccess: (response) => { + onSuccess: (user) => { toast({ title: "Registration successful", - description: response.message || `Welcome, ${response.username}!`, + description: `Welcome, ${user.username}!`, }); - // Switch to login tab automatically - const loginTab = document.querySelector('[data-state="inactive"][data-value="login"]') as HTMLElement; - if (loginTab) loginTab.click(); + navigate('/'); }, onError: (error: Error) => { toast({ diff --git a/client/src/pages/ldap-query-builder-page.tsx b/client/src/pages/ldap-query-builder-page.tsx deleted file mode 100644 index e3f3442..0000000 --- a/client/src/pages/ldap-query-builder-page.tsx +++ /dev/null @@ -1,537 +0,0 @@ -import { useState } from "react"; -import { useQuery, useMutation } from "@tanstack/react-query"; -import { useToast } from "@/hooks/use-toast"; -import { DashboardLayout } from "@/layouts/dashboard-layout"; -import { LdapConnection, LdapQuery } from "@shared/schema"; -import { LdapQueryBuilderParams } from "@/components/ldap/ldap-query-builder"; -import { EnhancedLdapQueryBuilder } from "@/components/ldap/enhanced-ldap-query-builder"; -import { LdapCondition, LdapOperator } from "@/lib/ldap-types"; -import { apiRequest, queryClient } from "@/lib/queryClient"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { Textarea } from "@/components/ui/textarea"; -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, -} from "@/components/ui/dialog"; -import { DataTable } from "@/components/ui/data-table"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger -} from "@/components/ui/dropdown-menu"; -import { - Card, - CardContent, - CardDescription, - CardFooter, - CardHeader, - CardTitle -} from "@/components/ui/card"; -import { - Tabs, - TabsContent, - TabsList, - TabsTrigger, -} from "@/components/ui/tabs"; -import { Loader2, MoreHorizontal, Plus, Code, Save, Play, FileClock } from "lucide-react"; -import { format } from "date-fns"; - -export default function LdapQueryBuilderPage() { - const { toast } = useToast(); - const [showCreateDialog, setShowCreateDialog] = useState(false); - const [showTestDialog, setShowTestDialog] = useState(false); - const [testResults, setTestResults] = useState(null); - - const [queryName, setQueryName] = useState(""); - const [queryDescription, setQueryDescription] = useState(""); - - const [activeQuery, setActiveQuery] = useState(null); - - const [queryBuilderParams, setQueryBuilderParams] = useState({ - targetObject: "users", - filter: { - operator: LdapOperator.AND, - conditions: [] - } - }); - - // Fetch all saved queries - const { data: savedQueries = [], isLoading: isLoadingQueries } = useQuery({ - queryKey: ["/api/ldap-queries"], - }); - - // Fetch LDAP connections for the query builder - const { data: connections = [], isLoading: isLoadingConnections } = useQuery({ - queryKey: ["/api/ldap-connections"], - }); - - // Mutation for creating a new query - const createQueryMutation = useMutation({ - mutationFn: async (data: { - name: string; - description: string; - targetObject: string; - filter: any; - }) => { - const response = await apiRequest("POST", "/api/ldap-queries", data); - return await response.json(); - }, - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ["/api/ldap-queries"] }); - setShowCreateDialog(false); - setQueryName(""); - setQueryDescription(""); - toast({ - title: "Query saved", - description: "The LDAP query has been saved successfully.", - }); - }, - onError: (error) => { - toast({ - title: "Error saving query", - description: error.message, - variant: "destructive", - }); - } - }); - - // Mutation for testing a query - const testQueryMutation = useMutation({ - mutationFn: async (data: { - connectionId: number; - targetObject: string; - filter: any; - limit?: number; - }) => { - const response = await apiRequest("POST", "/api/ldap-queries/test", data); - return await response.json(); - }, - onSuccess: (data) => { - setTestResults(data.results); - setShowTestDialog(true); - toast({ - title: "Query executed", - description: `Query returned ${data.count} results.`, - }); - }, - onError: (error) => { - toast({ - title: "Error testing query", - description: error.message, - variant: "destructive", - }); - } - }); - - // Mutation for updating an existing query - const updateQueryMutation = useMutation({ - mutationFn: async (data: { - id: number; - name: string; - description: string; - targetObject: string; - filter: any; - }) => { - const response = await apiRequest("PUT", `/api/ldap-queries/${data.id}`, data); - return await response.json(); - }, - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ["/api/ldap-queries"] }); - setActiveQuery(null); - toast({ - title: "Query updated", - description: "The LDAP query has been updated successfully.", - }); - }, - onError: (error) => { - toast({ - title: "Error updating query", - description: error.message, - variant: "destructive", - }); - } - }); - - // Function to handle query deletion - const deleteQuery = async (id: number) => { - try { - await apiRequest("DELETE", `/api/ldap-queries/${id}`); - queryClient.invalidateQueries({ queryKey: ["/api/ldap-queries"] }); - toast({ - title: "Query deleted", - description: "The LDAP query has been deleted successfully.", - }); - } catch (error: any) { - toast({ - title: "Error deleting query", - description: error.message, - variant: "destructive", - }); - } - }; - - // Function to load a query for editing - const loadQuery = (query: LdapQuery) => { - setActiveQuery(query); - setQueryBuilderParams({ - targetObject: query.targetObject as "users" | "groups" | "computers" | "ous", - filter: query.filterJson as LdapCondition - }); - setQueryName(query.name); - setQueryDescription(query.description || ""); - }; - - // Function to handle saving a query - const handleSaveQuery = () => { - if (activeQuery) { - updateQueryMutation.mutate({ - id: activeQuery.id, - name: queryName, - description: queryDescription, - targetObject: queryBuilderParams.targetObject, - filter: queryBuilderParams.filter - }); - } else { - setShowCreateDialog(true); - } - }; - - // Function to handle testing a query - const handleTestQuery = () => { - if (connections.length === 0) { - toast({ - title: "No connections available", - description: "Please add an LDAP connection first.", - variant: "destructive", - }); - return; - } - - testQueryMutation.mutate({ - connectionId: connections[0].id, - targetObject: queryBuilderParams.targetObject, - filter: queryBuilderParams.filter, - limit: 100 - }); - }; - - // Generate columns for the saved queries table - const queriesColumns = [ - { - header: "Name", - accessorKey: "name" as keyof LdapQuery, - }, - { - header: "Description", - accessorKey: "description" as keyof LdapQuery, - }, - { - header: "Target", - accessorKey: "targetObject" as keyof LdapQuery, - cell: (row: LdapQuery) => { - const targets: Record = { - users: "Users", - groups: "Groups", - computers: "Computers", - ous: "Organizational Units", - }; - return targets[row.targetObject] || row.targetObject; - }, - }, - { - header: "Last Updated", - accessorKey: "updatedAt" as keyof LdapQuery, - cell: (row: LdapQuery) => - row.updatedAt ? format(new Date(row.updatedAt), "MMM dd, yyyy HH:mm") : "", - }, - { - header: "", - accessorKey: "id" as keyof LdapQuery, - cell: (row: LdapQuery) => ( - - - - - - loadQuery(row)}> - - Edit - - { - testQueryMutation.mutate({ - connectionId: connections.length > 0 ? connections[0].id : 0, - targetObject: row.targetObject, - filter: row.filterJson as LdapCondition, - limit: 100 - }); - }}> - - Test - - deleteQuery(row.id)}> - - Delete - - - - ), - }, - ]; - - // Generate columns for the test results table dynamically based on the returned attributes - const getTestResultsColumns = () => { - if (!testResults || testResults.length === 0) return []; - - const firstResult = testResults[0]; - return Object.keys(firstResult).map(key => ({ - header: key, - accessorKey: key as keyof typeof firstResult, - cell: (row: any) => { - const value = row[key]; - if (value === null || value === undefined) return "-"; - if (typeof value === "boolean") return value ? "Yes" : "No"; - if (Array.isArray(value)) return value.join(", "); - return String(value); - } - })); - }; - - return ( - -
- {/* Left panel - Saved Queries */} -
- - -
- Saved Queries - - Reuse and manage your saved LDAP queries - -
- -
- -
- -
-
-
-
- - {/* Right panel - Query Builder */} -
- - -
-
- - {activeQuery ? "Edit Query" : "New Query"} - - - {activeQuery ? `Editing: ${activeQuery.name}` : "Create a new LDAP query"} - -
-
- - -
-
-
- -
- {activeQuery ? ( -
-
- - setQueryName(e.target.value)} - placeholder="My Query" - /> -
-
- - setQueryDescription(e.target.value)} - placeholder="What does this query do?" - /> -
-
- ) : null} - -
- -
-
-
-
-
-
- - {/* Create Query Dialog */} - - - - Save Query - - Give your query a name and description for future reference. - - -
-
- - setQueryName(e.target.value)} - placeholder="Enter a name for your query" - /> -
-
- -