Add search functionality to the whitelist page and improve IP status indication

Add a search bar to the whitelist page and filter results by IP, reason, and notes. Modify the detections page to visually indicate when an IP is already whitelisted by changing the button color to green and using a different icon.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 7a657272-55ba-4a79-9a2e-f1ed9bc7a528
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 4231475f-0a12-42cd-bf3f-3401022fd4e5
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/449cf7c4-c97a-45ae-8234-e5c5b8d6a84f/7a657272-55ba-4a79-9a2e-f1ed9bc7a528/8i4FqXF
This commit is contained in:
marco370 2026-01-02 15:37:32 +00:00
parent 0298b4a790
commit 1b47e08129
2 changed files with 62 additions and 17 deletions

View File

@ -5,10 +5,10 @@ import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Slider } from "@/components/ui/slider";
import { AlertTriangle, Search, Shield, Globe, MapPin, Building2, ShieldPlus } from "lucide-react";
import { AlertTriangle, Search, Shield, Globe, MapPin, Building2, ShieldPlus, ShieldCheck } from "lucide-react";
import { format } from "date-fns";
import { useState } from "react";
import type { Detection } from "@shared/schema";
import type { Detection, Whitelist } from "@shared/schema";
import { getFlag } from "@/lib/country-flags";
import { apiRequest, queryClient } from "@/lib/queryClient";
import { useToast } from "@/hooks/use-toast";
@ -39,6 +39,14 @@ export default function Detections() {
refetchInterval: 5000,
});
// Fetch whitelist to check if IP is already whitelisted
const { data: whitelistData } = useQuery<Whitelist[]>({
queryKey: ["/api/whitelist"],
});
// Create a Set of whitelisted IPs for fast lookup
const whitelistedIps = new Set(whitelistData?.map(w => w.ipAddress) || []);
const filteredDetections = detections?.filter((d) =>
d.sourceIp.toLowerCase().includes(searchQuery.toLowerCase()) ||
d.anomalyType.toLowerCase().includes(searchQuery.toLowerCase())
@ -278,17 +286,30 @@ export default function Detections() {
</Badge>
)}
<Button
variant="outline"
size="sm"
onClick={() => addToWhitelistMutation.mutate(detection)}
disabled={addToWhitelistMutation.isPending}
className="w-full"
data-testid={`button-whitelist-${detection.id}`}
>
<ShieldPlus className="h-3 w-3 mr-1" />
Whitelist
</Button>
{whitelistedIps.has(detection.sourceIp) ? (
<Button
variant="outline"
size="sm"
disabled
className="w-full bg-green-500/10 border-green-500 text-green-600 dark:text-green-400"
data-testid={`button-whitelist-${detection.id}`}
>
<ShieldCheck className="h-3 w-3 mr-1" />
In Whitelist
</Button>
) : (
<Button
variant="outline"
size="sm"
onClick={() => addToWhitelistMutation.mutate(detection)}
disabled={addToWhitelistMutation.isPending}
className="w-full"
data-testid={`button-whitelist-${detection.id}`}
>
<ShieldPlus className="h-3 w-3 mr-1" />
Whitelist
</Button>
)}
</div>
</div>
</div>

View File

@ -2,7 +2,7 @@ import { useQuery, useMutation } from "@tanstack/react-query";
import { queryClient, apiRequest } from "@/lib/queryClient";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Shield, Plus, Trash2, CheckCircle2, XCircle } from "lucide-react";
import { Shield, Plus, Trash2, CheckCircle2, XCircle, Search } from "lucide-react";
import { format } from "date-fns";
import { useState } from "react";
import { useForm } from "react-hook-form";
@ -44,6 +44,7 @@ const whitelistFormSchema = insertWhitelistSchema.extend({
export default function WhitelistPage() {
const { toast } = useToast();
const [isAddDialogOpen, setIsAddDialogOpen] = useState(false);
const [searchQuery, setSearchQuery] = useState("");
const form = useForm<z.infer<typeof whitelistFormSchema>>({
resolver: zodResolver(whitelistFormSchema),
@ -59,6 +60,13 @@ export default function WhitelistPage() {
queryKey: ["/api/whitelist"],
});
// Filter whitelist based on search query
const filteredWhitelist = whitelist?.filter((item) =>
item.ipAddress.toLowerCase().includes(searchQuery.toLowerCase()) ||
item.reason?.toLowerCase().includes(searchQuery.toLowerCase()) ||
item.comment?.toLowerCase().includes(searchQuery.toLowerCase())
);
const addMutation = useMutation({
mutationFn: async (data: z.infer<typeof whitelistFormSchema>) => {
return await apiRequest("POST", "/api/whitelist", data);
@ -189,11 +197,27 @@ export default function WhitelistPage() {
</Dialog>
</div>
{/* Search Bar */}
<Card data-testid="card-search">
<CardContent className="pt-6">
<div className="relative">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
placeholder="Cerca per IP, motivo o note..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-9"
data-testid="input-search-whitelist"
/>
</div>
</CardContent>
</Card>
<Card data-testid="card-whitelist">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Shield className="h-5 w-5" />
IP Protetti ({whitelist?.length || 0})
IP Protetti ({filteredWhitelist?.length || 0}{searchQuery && whitelist ? ` di ${whitelist.length}` : ''})
</CardTitle>
</CardHeader>
<CardContent>
@ -201,9 +225,9 @@ export default function WhitelistPage() {
<div className="text-center py-8 text-muted-foreground" data-testid="text-loading">
Caricamento...
</div>
) : whitelist && whitelist.length > 0 ? (
) : filteredWhitelist && filteredWhitelist.length > 0 ? (
<div className="space-y-3">
{whitelist.map((item) => (
{filteredWhitelist.map((item) => (
<div
key={item.id}
className="p-4 rounded-lg border hover-elevate"