diff --git a/flowsint-web/src/app/api/projects/[project_id]/investigations/[investigation_id]/data/route.ts b/flowsint-web/src/app/api/projects/[project_id]/investigations/[investigation_id]/data/route.ts index 42b9d4a..d66917b 100644 --- a/flowsint-web/src/app/api/projects/[project_id]/investigations/[investigation_id]/data/route.ts +++ b/flowsint-web/src/app/api/projects/[project_id]/investigations/[investigation_id]/data/route.ts @@ -14,7 +14,7 @@ export async function GET(_: Request, { params }: { params: Promise<{ investigat return NextResponse.json({ error: "Unauthorized" }, { status: 401 }) } const { data: individuals, error: indError } = await supabase .from("individuals") - .select("*, ip_addresses(*), phone_numbers(*), social_accounts(*), emails(*), physical_addresses(*), group_id") + .select("*, ip_addresses(*), phone_numbers(*), social_accounts(*), emails(*), physical_addresses(*), vehicles(*), group_id") .eq("investigation_id", investigation_id) const { data: groups, error: groupsError } = await supabase .from("groups") @@ -127,6 +127,22 @@ export async function GET(_: Request, { params }: { params: Promise<{ investigat label: "IP", }) }) + // Ajouter les adresses IP + ind.vehicles?.forEach((vehicle: any) => { + nodes.push({ + id: vehicle.id.toString(), + type: "vehicle", + data: { label: `${vehicle.plate}-${vehicle.model}` }, + position: { x: -100, y: -100 }, + }) + edges.push({ + source: individualId, + target: vehicle.id.toString(), + type: "custom", + id: `${individualId}-${vehicle.id}`.toString(), + label: vehicle.type, + }) + }) // Ajouter les adresses physiques ind.physical_addresses?.forEach((address: any) => { nodes.push({ diff --git a/flowsint-web/src/components/investigations/new-actions.tsx b/flowsint-web/src/components/investigations/new-actions.tsx index 572b10d..c89fdf5 100644 --- a/flowsint-web/src/components/investigations/new-actions.tsx +++ b/flowsint-web/src/components/investigations/new-actions.tsx @@ -5,8 +5,7 @@ import { useParams } from "next/navigation" import { supabase } from "@/lib/supabase/client" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" -import { AlertCircle, AtSign, Camera, Facebook, Github, GithubIcon, Instagram, Locate, MapPin, MessageCircleDashed, Phone, PlusIcon, Send, User } from "lucide-react" -import type React from "react" // Added import for React +import { AlertCircle } from "lucide-react" import { toast } from "sonner" import { DropdownMenu, @@ -16,12 +15,14 @@ import { DropdownMenuSub, DropdownMenuPortal, DropdownMenuContent, - DropdownMenuSubContent + DropdownMenuSubContent, } from "@/components/ui/dropdown-menu" import { nodesTypes } from "@/lib/utils" import { Alert, AlertTitle, AlertDescription } from "../ui/alert" import { Badge } from "../ui/badge" import { Dialog, DialogClose, DialogContent, DialogDescription, DialogTitle } from "../ui/dialog" +import { actionItems, type ActionItem } from "@/lib/action-items" +import { PlusIcon } from "lucide-react" export default function NewActions({ addNodes }: { addNodes: any }) { const { investigation_id } = useParams() @@ -30,13 +31,13 @@ export default function NewActions({ addNodes }: { addNodes: any }) { const [error, setError] = useState(null) const [loading, setLoading] = useState(false) - const handleOpenAddNodeModal = (e: { stopPropagation: () => void }, tableName: string, individualId?: string) => { + const handleOpenAddNodeModal = (e: { stopPropagation: () => void }, key: string) => { e.stopPropagation() - if (!nodesTypes[tableName as keyof typeof nodesTypes]) { + if (!nodesTypes[key as keyof typeof nodesTypes]) { toast.error("Invalid node type.") return } - setCurrentNodeType(nodesTypes[tableName as keyof typeof nodesTypes]) + setCurrentNodeType(nodesTypes[key as keyof typeof nodesTypes]) setError(null) setOpenNodeModal(true) } @@ -49,6 +50,7 @@ export default function NewActions({ addNodes }: { addNodes: any }) { const data = Object.fromEntries(new FormData(e.currentTarget)) await handleAddNode(data) } + const handleAddNode = async (data: any) => { try { setLoading(true) @@ -58,8 +60,9 @@ export default function NewActions({ addNodes }: { addNodes: any }) { .insert(dataToInsert) .select("*") .single() + console.log(insertError) if (insertError) { - toast.error(insertError.details) + toast.error("Failed to create node.") setLoading(false) return } @@ -79,77 +82,63 @@ export default function NewActions({ addNodes }: { addNodes: any }) { setError(null) } catch (error) { toast.error("An unexpected error occurred") + } finally { + setLoading(false) } } + + // Render a dropdown menu item + const renderMenuItem = (item: ActionItem) => { + const Icon = item.icon + + if (item.children) { + return ( + + + + {item.label} + + + + {item.children.map((childItem) => renderMenuItem(childItem))} + + + + ) + } + return ( + handleOpenAddNodeModal(e, item.key)} + > + + {item.label} + {item.comingSoon && ( + + soon + + )} + + ) + } + return ( <> - - - handleOpenAddNodeModal(e, "individuals")}> - New relation - - handleOpenAddNodeModal(e, "phone_numbers")}> - - Phone number - - handleOpenAddNodeModal(e, "physical_addresses")}> - - Physical address - - handleOpenAddNodeModal(e, "emails")}> - - Email address - - handleOpenAddNodeModal(e, "ip_addresses")}> - - IP address - - - Social account - - - handleOpenAddNodeModal(e, "social_accounts_facebook")}> - - Facebook - - handleOpenAddNodeModal(e, "social_accounts_instagram")}> - - Instagram - - handleOpenAddNodeModal(e, "social_accounts_telegram")}> - - Telegram - - handleOpenAddNodeModal(e, "social_accounts_signal")}> - - Signal - - handleOpenAddNodeModal(e, "social_accounts_snapchat")}> - - Snapchat - - handleOpenAddNodeModal(e, "social_accounts_github")}> - - Github - - handleOpenAddNodeModal(e, "social_accounts_coco")}> - Coco{" "} - - soon - - - - - - - + + + + {actionItems.map((item) => renderMenuItem(item))} + New {currentNodeType?.type} Add a new related {currentNodeType?.type}.
-
+
{currentNodeType?.fields.map((field: any, i: number) => { const [key, value] = field.split(":") return ( @@ -182,6 +171,5 @@ export default function NewActions({ addNodes }: { addNodes: any }) {
) - } diff --git a/flowsint-web/src/components/investigations/sketch/graph.tsx b/flowsint-web/src/components/investigations/sketch/graph.tsx index 4371fe6..6cb3c8c 100644 --- a/flowsint-web/src/components/investigations/sketch/graph.tsx +++ b/flowsint-web/src/components/investigations/sketch/graph.tsx @@ -47,11 +47,8 @@ import AddNodeModal from "../add-node-modal" import { Dialog, DialogTrigger } from "../../ui/dialog" import { memo } from "react" import { shallow } from "zustand/shallow" -import FloatingEdge from "./simple-floating-edge" import { TooltipProvider } from "@/components/ui/tooltip" import NodeContextMenu from "./nodes/node-context-menu" -import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs" -import ProfilePanel from "./profile-panel" import GroupNode from "./nodes/group" import CustomEdge from "./nodes/custom-edge" const edgeTypes = { diff --git a/flowsint-web/src/components/investigations/sketch/nodes/custom-edge.tsx b/flowsint-web/src/components/investigations/sketch/nodes/custom-edge.tsx index 5a1311a..6a3ec1b 100644 --- a/flowsint-web/src/components/investigations/sketch/nodes/custom-edge.tsx +++ b/flowsint-web/src/components/investigations/sketch/nodes/custom-edge.tsx @@ -72,13 +72,13 @@ const EditableEdge = memo((props: EdgeProps) => { value={editValue} onChange={handleInputChange} onBlur={handleInputBlur} - className="h-7 w-24" + className="h-5 text-xs rounded-sm" /> - - - - - - - {currentNode.id && } - + handleOpenAddNodeModal(e, item.key)} + > + + {item.label} + {item.comingSoon && ( + + soon + + )} + ) - }, -) + } + + if (!currentNode) return null + + return ( + <> + + + {Boolean(currentNode?.data?.email) && ( + Search {currentNodeType} + )} + + New note + + + + New + {actionItems.map((item) => renderMenuItem(item))} + + View and edit + Duplicate + + + Delete + ⌘ ⌫ + + + + + + New {currentNodeType?.type} + Add a new related {currentNodeType?.type}. +
+
+ {currentNodeType?.fields.map((field: any, i: number) => { + const [key, value] = field.split(":") + return ( + + ) + })} +
+ {error && ( + + + Error + {error} + + )} +
+ + + + +
+
+
+
+ {currentNode.id && ( + + )} + + ) +}) export default NodeContextMenu diff --git a/flowsint-web/src/store/flow-store.ts b/flowsint-web/src/store/flow-store.ts index 15c900f..ea7c824 100644 --- a/flowsint-web/src/store/flow-store.ts +++ b/flowsint-web/src/store/flow-store.ts @@ -176,7 +176,7 @@ const createStore = (initialNodes: AppNode[] = [], initialEdges: Edge[] = []) => animated, style: { ...edge.style, - stroke: animated ? "var(--primary)" : "#b1b1b750", + // stroke: animated ? "var(--primary)" : "#b1b1b750", opacity: animated ? 1 : 0.25, }, };