feat(binary): add engine selection for binary analysis
This commit is contained in:
parent
decb831ba1
commit
cd208b51fc
@ -4,6 +4,12 @@
|
|||||||
* OpenAPI definition
|
* OpenAPI definition
|
||||||
* OpenAPI spec version: v0
|
* OpenAPI spec version: v0
|
||||||
*/
|
*/
|
||||||
|
export interface EngineResponse {
|
||||||
|
name: string;
|
||||||
|
label: string;
|
||||||
|
available: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export interface WorkspaceRequest {
|
export interface WorkspaceRequest {
|
||||||
/** @minLength 1 */
|
/** @minLength 1 */
|
||||||
name: string;
|
name: string;
|
||||||
|
|||||||
120
src/api/generated/engine/engine.ts
Normal file
120
src/api/generated/engine/engine.ts
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
/**
|
||||||
|
* Generated by orval v8.15.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* OpenAPI definition
|
||||||
|
* OpenAPI spec version: v0
|
||||||
|
*/
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import type {
|
||||||
|
DataTag,
|
||||||
|
DefinedInitialDataOptions,
|
||||||
|
DefinedUseQueryResult,
|
||||||
|
QueryClient,
|
||||||
|
QueryFunction,
|
||||||
|
QueryKey,
|
||||||
|
UndefinedInitialDataOptions,
|
||||||
|
UseQueryOptions,
|
||||||
|
UseQueryResult,
|
||||||
|
} from "@tanstack/react-query";
|
||||||
|
|
||||||
|
import type { EngineResponse } from "../api.schemas";
|
||||||
|
|
||||||
|
import { customInstance } from "../../api";
|
||||||
|
|
||||||
|
type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve all registered static analysis engines with their availability status.
|
||||||
|
* @summary List available analysis engines
|
||||||
|
*/
|
||||||
|
export const listEngines = (
|
||||||
|
options?: SecondParameter<typeof customInstance>,
|
||||||
|
signal?: AbortSignal
|
||||||
|
) => {
|
||||||
|
return customInstance<EngineResponse[]>({ url: `/engines`, method: "GET", signal }, options);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getListEnginesQueryKey = () => {
|
||||||
|
return [`/engines`] as const;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getListEnginesQueryOptions = <
|
||||||
|
TData = Awaited<ReturnType<typeof listEngines>>,
|
||||||
|
TError = unknown,
|
||||||
|
>(options?: {
|
||||||
|
query?: Partial<UseQueryOptions<Awaited<ReturnType<typeof listEngines>>, TError, TData>>;
|
||||||
|
request?: SecondParameter<typeof customInstance>;
|
||||||
|
}) => {
|
||||||
|
const { query: queryOptions, request: requestOptions } = options ?? {};
|
||||||
|
|
||||||
|
const queryKey = queryOptions?.queryKey ?? getListEnginesQueryKey();
|
||||||
|
|
||||||
|
const queryFn: QueryFunction<Awaited<ReturnType<typeof listEngines>>> = ({ signal }) =>
|
||||||
|
listEngines(requestOptions, signal);
|
||||||
|
|
||||||
|
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
|
||||||
|
Awaited<ReturnType<typeof listEngines>>,
|
||||||
|
TError,
|
||||||
|
TData
|
||||||
|
> & { queryKey: DataTag<QueryKey, TData, TError> };
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ListEnginesQueryResult = NonNullable<Awaited<ReturnType<typeof listEngines>>>;
|
||||||
|
export type ListEnginesQueryError = unknown;
|
||||||
|
|
||||||
|
export function useListEngines<TData = Awaited<ReturnType<typeof listEngines>>, TError = unknown>(
|
||||||
|
options: {
|
||||||
|
query: Partial<UseQueryOptions<Awaited<ReturnType<typeof listEngines>>, TError, TData>> &
|
||||||
|
Pick<
|
||||||
|
DefinedInitialDataOptions<
|
||||||
|
Awaited<ReturnType<typeof listEngines>>,
|
||||||
|
TError,
|
||||||
|
Awaited<ReturnType<typeof listEngines>>
|
||||||
|
>,
|
||||||
|
"initialData"
|
||||||
|
>;
|
||||||
|
request?: SecondParameter<typeof customInstance>;
|
||||||
|
},
|
||||||
|
queryClient?: QueryClient
|
||||||
|
): DefinedUseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData, TError> };
|
||||||
|
export function useListEngines<TData = Awaited<ReturnType<typeof listEngines>>, TError = unknown>(
|
||||||
|
options?: {
|
||||||
|
query?: Partial<UseQueryOptions<Awaited<ReturnType<typeof listEngines>>, TError, TData>> &
|
||||||
|
Pick<
|
||||||
|
UndefinedInitialDataOptions<
|
||||||
|
Awaited<ReturnType<typeof listEngines>>,
|
||||||
|
TError,
|
||||||
|
Awaited<ReturnType<typeof listEngines>>
|
||||||
|
>,
|
||||||
|
"initialData"
|
||||||
|
>;
|
||||||
|
request?: SecondParameter<typeof customInstance>;
|
||||||
|
},
|
||||||
|
queryClient?: QueryClient
|
||||||
|
): UseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData, TError> };
|
||||||
|
export function useListEngines<TData = Awaited<ReturnType<typeof listEngines>>, TError = unknown>(
|
||||||
|
options?: {
|
||||||
|
query?: Partial<UseQueryOptions<Awaited<ReturnType<typeof listEngines>>, TError, TData>>;
|
||||||
|
request?: SecondParameter<typeof customInstance>;
|
||||||
|
},
|
||||||
|
queryClient?: QueryClient
|
||||||
|
): UseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData, TError> };
|
||||||
|
/**
|
||||||
|
* @summary List available analysis engines
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function useListEngines<TData = Awaited<ReturnType<typeof listEngines>>, TError = unknown>(
|
||||||
|
options?: {
|
||||||
|
query?: Partial<UseQueryOptions<Awaited<ReturnType<typeof listEngines>>, TError, TData>>;
|
||||||
|
request?: SecondParameter<typeof customInstance>;
|
||||||
|
},
|
||||||
|
queryClient?: QueryClient
|
||||||
|
): UseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData, TError> } {
|
||||||
|
const queryOptions = getListEnginesQueryOptions(options);
|
||||||
|
|
||||||
|
const query = useQuery(queryOptions, queryClient) as UseQueryResult<TData, TError> & {
|
||||||
|
queryKey: DataTag<QueryKey, TData, TError>;
|
||||||
|
};
|
||||||
|
|
||||||
|
return { ...query, queryKey: queryOptions.queryKey };
|
||||||
|
}
|
||||||
57
src/components/EngineSelect.tsx
Normal file
57
src/components/EngineSelect.tsx
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/components/ui/select";
|
||||||
|
import { Loader2 } from "lucide-react";
|
||||||
|
import type { EngineResponse } from "@/api/generated/api.schemas";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
|
||||||
|
interface EngineSelectProps {
|
||||||
|
engines: EngineResponse[];
|
||||||
|
value?: string;
|
||||||
|
onChange: (engine: string) => void;
|
||||||
|
placeholder?: string;
|
||||||
|
isLoading?: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EngineSelect = ({
|
||||||
|
engines,
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
placeholder = "Select engine",
|
||||||
|
isLoading = false,
|
||||||
|
disabled = false,
|
||||||
|
}: EngineSelectProps) => {
|
||||||
|
const available = useMemo(() => engines.filter((e) => e.available), [engines]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select value={value} onValueChange={onChange} disabled={disabled || isLoading}>
|
||||||
|
<SelectTrigger className="w-full">
|
||||||
|
{isLoading ? (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Loader2 className="h-4 w-4 animate-spin" />
|
||||||
|
<span className="text-muted-foreground">Loading engines...</span>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<SelectValue placeholder={placeholder} />
|
||||||
|
)}
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{available.length === 0 && !isLoading && (
|
||||||
|
<div className="text-muted-foreground px-2 py-4 text-center text-sm">
|
||||||
|
No engines available
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{available.map((engine) => (
|
||||||
|
<SelectItem key={engine.name} value={engine.name}>
|
||||||
|
{engine.label}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -109,7 +109,7 @@ const SelectItem = React.forwardRef<
|
|||||||
<SelectPrimitive.Item
|
<SelectPrimitive.Item
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"focus:bg-accent focus:text-accent-foreground relative flex w-full cursor-default items-center rounded-sm py-1.5 pr-2 pl-8 text-sm outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
"focus:bg-accent focus:text-accent-foreground hover:bg-accent hover:text-accent-foreground relative flex w-full cursor-pointer items-center rounded-sm py-1.5 pr-2 pl-3 text-sm outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:cursor-default data-[disabled]:opacity-50 data-[state=checked]:pl-8",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
96
src/features/binary/AnalyzeDialog.tsx
Normal file
96
src/features/binary/AnalyzeDialog.tsx
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from "@/components/ui/dialog";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { EngineSelect } from "@/components/EngineSelect";
|
||||||
|
import { AlertTriangle } from "lucide-react";
|
||||||
|
import type { EngineResponse } from "@/api/generated/api.schemas";
|
||||||
|
|
||||||
|
interface AnalyzeDialogProps {
|
||||||
|
open: boolean;
|
||||||
|
onOpenChange: (open: boolean) => void;
|
||||||
|
isReanalyze: boolean;
|
||||||
|
engines: EngineResponse[];
|
||||||
|
enginesLoading: boolean;
|
||||||
|
onConfirm: (engine: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AnalyzeDialog = ({
|
||||||
|
open,
|
||||||
|
onOpenChange,
|
||||||
|
isReanalyze,
|
||||||
|
engines,
|
||||||
|
enginesLoading,
|
||||||
|
onConfirm,
|
||||||
|
}: AnalyzeDialogProps) => {
|
||||||
|
const [selectedEngine, setSelectedEngine] = useState<string>();
|
||||||
|
|
||||||
|
const handleConfirm = () => {
|
||||||
|
if (!selectedEngine) return;
|
||||||
|
onConfirm(selectedEngine);
|
||||||
|
onOpenChange(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
{isReanalyze && (
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="bg-destructive/10 flex h-10 w-10 items-center justify-center rounded-full">
|
||||||
|
<AlertTriangle className="text-destructive h-5 w-5" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<DialogTitle>Re-analyze binary</DialogTitle>
|
||||||
|
<DialogDescription className="mt-1">
|
||||||
|
This will replace all current analysis data. Function names, comments, and
|
||||||
|
decompiled code will be lost.
|
||||||
|
</DialogDescription>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{!isReanalyze && (
|
||||||
|
<div>
|
||||||
|
<DialogTitle>Analyze binary</DialogTitle>
|
||||||
|
<DialogDescription className="mt-1">
|
||||||
|
Select the analysis engine to use.
|
||||||
|
</DialogDescription>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<div className="space-y-4 py-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="text-sm font-medium">Analysis Engine</label>
|
||||||
|
<EngineSelect
|
||||||
|
engines={engines}
|
||||||
|
value={selectedEngine}
|
||||||
|
onChange={setSelectedEngine}
|
||||||
|
isLoading={enginesLoading}
|
||||||
|
placeholder="Select an engine"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DialogFooter>
|
||||||
|
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant={isReanalyze ? "destructive" : "default"}
|
||||||
|
onClick={handleConfirm}
|
||||||
|
disabled={!selectedEngine}
|
||||||
|
>
|
||||||
|
{isReanalyze ? "Re-analyze" : "Analyze"}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -6,6 +6,7 @@ import { useGetProject } from "@/api/generated/project/project.ts";
|
|||||||
import { useGetWorkspace } from "@/api/generated/workspace/workspace.ts";
|
import { useGetWorkspace } from "@/api/generated/workspace/workspace.ts";
|
||||||
import { useListAnalyses, useListSegments } from "@/api/generated/analysis/analysis.ts";
|
import { useListAnalyses, useListSegments } from "@/api/generated/analysis/analysis.ts";
|
||||||
import { useGetFunction } from "@/api/generated/function/function.ts";
|
import { useGetFunction } from "@/api/generated/function/function.ts";
|
||||||
|
import { useListEngines } from "@/api/generated/engine/engine.ts";
|
||||||
import { customInstance } from "@/api/api.ts";
|
import { customInstance } from "@/api/api.ts";
|
||||||
import type {
|
import type {
|
||||||
FunctionSummaryResponse,
|
FunctionSummaryResponse,
|
||||||
@ -42,7 +43,7 @@ export function useBinaryPage() {
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const [selectedFunctionId, setSelectedFunctionId] = useState<string>();
|
const [selectedFunctionId, setSelectedFunctionId] = useState<string>();
|
||||||
const [confirmReanalyzeOpen, setConfirmReanalyzeOpen] = useState(false);
|
const [analyzeDialogOpen, setAnalyzeDialogOpen] = useState(false);
|
||||||
const [selectedSegmentId, setSelectedSegmentId] = useState<string>();
|
const [selectedSegmentId, setSelectedSegmentId] = useState<string>();
|
||||||
const [selectedFlags, setSelectedFlags] = useState<Set<string>>(new Set());
|
const [selectedFlags, setSelectedFlags] = useState<Set<string>>(new Set());
|
||||||
|
|
||||||
@ -96,6 +97,9 @@ export function useBinaryPage() {
|
|||||||
{ query: { enabled: !!selectedFunctionId } }
|
{ query: { enabled: !!selectedFunctionId } }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Fetch available engines
|
||||||
|
const { data: engines, isLoading: enginesLoading } = useListEngines();
|
||||||
|
|
||||||
// Analyze / re-analyze mutation
|
// Analyze / re-analyze mutation
|
||||||
const analyzeMutation = useAnalyzeBinary({
|
const analyzeMutation = useAnalyzeBinary({
|
||||||
mutation: {
|
mutation: {
|
||||||
@ -107,17 +111,13 @@ export function useBinaryPage() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleAnalyze = () => {
|
const handleOpenAnalyzeDialog = () => {
|
||||||
analyzeMutation.mutate({ binaryId, params: { engine: "IDA5" } });
|
setAnalyzeDialogOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleReanalyzeRequest = () => {
|
const handleAnalyzeConfirm = (engine: string) => {
|
||||||
setConfirmReanalyzeOpen(true);
|
setAnalyzeDialogOpen(false);
|
||||||
};
|
analyzeMutation.mutate({ binaryId, params: { engine } });
|
||||||
|
|
||||||
const handleReanalyzeConfirm = () => {
|
|
||||||
setConfirmReanalyzeOpen(false);
|
|
||||||
handleAnalyze();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Flatten all pages into a single array
|
// Flatten all pages into a single array
|
||||||
@ -217,10 +217,11 @@ export function useBinaryPage() {
|
|||||||
selectedFunction,
|
selectedFunction,
|
||||||
selectedFunctionLoading,
|
selectedFunctionLoading,
|
||||||
analyzeMutation,
|
analyzeMutation,
|
||||||
confirmReanalyzeOpen,
|
analyzeDialogOpen,
|
||||||
setConfirmReanalyzeOpen,
|
setAnalyzeDialogOpen,
|
||||||
handleAnalyze,
|
handleOpenAnalyzeDialog,
|
||||||
handleReanalyzeRequest,
|
handleAnalyzeConfirm,
|
||||||
handleReanalyzeConfirm,
|
engines: engines ?? [],
|
||||||
|
enginesLoading,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,6 +13,8 @@ interface FileUploadProps {
|
|||||||
accept?: string;
|
accept?: string;
|
||||||
maxSize?: number;
|
maxSize?: number;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
autoUpload?: boolean;
|
||||||
|
onFileSelect?: (file: File) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FileUpload = ({
|
export const FileUpload = ({
|
||||||
@ -23,6 +25,8 @@ export const FileUpload = ({
|
|||||||
accept = ".exe,.com,.dll,.bin",
|
accept = ".exe,.com,.dll,.bin",
|
||||||
maxSize = 50 * 1024 * 1024,
|
maxSize = 50 * 1024 * 1024,
|
||||||
className,
|
className,
|
||||||
|
autoUpload = true,
|
||||||
|
onFileSelect,
|
||||||
}: FileUploadProps) => {
|
}: FileUploadProps) => {
|
||||||
const [isDragging, setIsDragging] = useState(false);
|
const [isDragging, setIsDragging] = useState(false);
|
||||||
const [file, setFile] = useState<File | null>(null);
|
const [file, setFile] = useState<File | null>(null);
|
||||||
@ -49,9 +53,13 @@ export const FileUpload = ({
|
|||||||
|
|
||||||
setValidationError(null);
|
setValidationError(null);
|
||||||
setFile(file);
|
setFile(file);
|
||||||
onUpload(file);
|
if (autoUpload) {
|
||||||
|
onUpload(file);
|
||||||
|
} else {
|
||||||
|
onFileSelect?.(file);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[onUpload, maxSize]
|
[onUpload, maxSize, autoUpload, onFileSelect]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleDrop = useCallback(
|
const handleDrop = useCallback(
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { useState } from "react";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@ -8,14 +9,19 @@ import {
|
|||||||
} from "@/components/ui/dialog.tsx";
|
} from "@/components/ui/dialog.tsx";
|
||||||
import { Button } from "@/components/ui/button.tsx";
|
import { Button } from "@/components/ui/button.tsx";
|
||||||
import { FileUpload } from "@/features/project/FileUpload.tsx";
|
import { FileUpload } from "@/features/project/FileUpload.tsx";
|
||||||
|
import { EngineSelect } from "@/components/EngineSelect";
|
||||||
|
import { Upload } from "lucide-react";
|
||||||
|
import type { EngineResponse } from "@/api/generated/api.schemas";
|
||||||
|
|
||||||
interface UploadBinaryDialogProps {
|
interface UploadBinaryDialogProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onOpenChange: (open: boolean) => void;
|
onOpenChange: (open: boolean) => void;
|
||||||
onUpload: (file: File) => void;
|
onUpload: (file: File, engine?: string) => void;
|
||||||
isUploading: boolean;
|
isUploading: boolean;
|
||||||
uploadError: string | null;
|
uploadError: string | null;
|
||||||
onReset: () => void;
|
onReset: () => void;
|
||||||
|
engines: EngineResponse[];
|
||||||
|
enginesLoading: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UploadBinaryDialog = ({
|
export const UploadBinaryDialog = ({
|
||||||
@ -25,7 +31,31 @@ export const UploadBinaryDialog = ({
|
|||||||
isUploading,
|
isUploading,
|
||||||
uploadError,
|
uploadError,
|
||||||
onReset,
|
onReset,
|
||||||
|
engines,
|
||||||
|
enginesLoading,
|
||||||
}: UploadBinaryDialogProps) => {
|
}: UploadBinaryDialogProps) => {
|
||||||
|
const [selectedFile, setSelectedFile] = useState<File | null>(null);
|
||||||
|
const [selectedEngine, setSelectedEngine] = useState<string>();
|
||||||
|
|
||||||
|
const handleFileSelect = (file: File) => {
|
||||||
|
setSelectedFile(file);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEngineChange = (engine: string) => {
|
||||||
|
setSelectedEngine(engine || undefined);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUpload = () => {
|
||||||
|
if (!selectedFile) return;
|
||||||
|
onUpload(selectedFile, selectedEngine);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleReset = () => {
|
||||||
|
setSelectedFile(null);
|
||||||
|
setSelectedEngine(undefined);
|
||||||
|
onReset();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
<DialogContent className="sm:max-w-lg">
|
<DialogContent className="sm:max-w-lg">
|
||||||
@ -45,19 +75,52 @@ export const UploadBinaryDialog = ({
|
|||||||
<span className="text-sm font-medium">Select Binary File</span>
|
<span className="text-sm font-medium">Select Binary File</span>
|
||||||
</div>
|
</div>
|
||||||
<FileUpload
|
<FileUpload
|
||||||
onUpload={onUpload}
|
onUpload={() => {}}
|
||||||
|
onFileSelect={handleFileSelect}
|
||||||
isUploading={isUploading}
|
isUploading={isUploading}
|
||||||
error={uploadError}
|
error={uploadError}
|
||||||
onReset={onReset}
|
onReset={handleReset}
|
||||||
accept=".exe,.com,.dll,.bin,.elf"
|
accept=".exe,.com,.dll,.bin,.elf"
|
||||||
maxSize={100 * 1024 * 1024}
|
maxSize={100 * 1024 * 1024}
|
||||||
|
autoUpload={false}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{selectedFile && (
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="bg-muted text-muted-foreground flex h-6 w-6 items-center justify-center rounded-full text-xs font-medium">
|
||||||
|
2
|
||||||
|
</div>
|
||||||
|
<span className="text-sm font-medium">Analysis Engine</span>
|
||||||
|
<span className="text-muted-foreground text-xs">(optional)</span>
|
||||||
|
</div>
|
||||||
|
<EngineSelect
|
||||||
|
engines={engines}
|
||||||
|
value={selectedEngine}
|
||||||
|
onChange={handleEngineChange}
|
||||||
|
isLoading={enginesLoading}
|
||||||
|
placeholder="Select engine (optional)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button variant="outline" onClick={() => onOpenChange(false)} disabled={isUploading}>
|
<Button variant="outline" onClick={() => onOpenChange(false)} disabled={isUploading}>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
|
{selectedFile && (
|
||||||
|
<Button onClick={handleUpload} disabled={isUploading}>
|
||||||
|
{isUploading ? (
|
||||||
|
<>Uploading...</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Upload className="mr-2 h-4 w-4" />
|
||||||
|
Upload
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</div>
|
</div>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { useGetProject } from "@/api/generated/project/project.ts";
|
|||||||
import { useGetWorkspace } from "@/api/generated/workspace/workspace.ts";
|
import { useGetWorkspace } from "@/api/generated/workspace/workspace.ts";
|
||||||
import { useDeleteBinary, useGetBinaries, useUploadBinary } from "@/api/generated/binary/binary.ts";
|
import { useDeleteBinary, useGetBinaries, useUploadBinary } from "@/api/generated/binary/binary.ts";
|
||||||
import { downloadBinary } from "@/api/generated/binary/binary.ts";
|
import { downloadBinary } from "@/api/generated/binary/binary.ts";
|
||||||
|
import { useListEngines } from "@/api/generated/engine/engine.ts";
|
||||||
import { useQueryClient } from "@tanstack/react-query";
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
import type { BinaryResponse } from "@/api/generated/api.schemas.ts";
|
import type { BinaryResponse } from "@/api/generated/api.schemas.ts";
|
||||||
|
|
||||||
@ -28,6 +29,8 @@ export function useProjectPage(projectId: string) {
|
|||||||
{ query: { enabled: !!projectId } }
|
{ query: { enabled: !!projectId } }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { data: engines, isLoading: enginesLoading } = useListEngines();
|
||||||
|
|
||||||
const { mutate: mutateDeleteBinary } = useDeleteBinary({
|
const { mutate: mutateDeleteBinary } = useDeleteBinary({
|
||||||
mutation: {
|
mutation: {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
@ -69,8 +72,8 @@ export function useProjectPage(projectId: string) {
|
|||||||
setBinaryToDelete(null);
|
setBinaryToDelete(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUploadBinary = (file: File) => {
|
const handleUploadBinary = (file: File, engine?: string) => {
|
||||||
uploadMutation.mutate({ projectId, data: { file } });
|
uploadMutation.mutate({ projectId, data: { file }, params: engine ? { engine } : undefined });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDownloadBinary = async (binary: BinaryResponse) => {
|
const handleDownloadBinary = async (binary: BinaryResponse) => {
|
||||||
@ -104,5 +107,7 @@ export function useProjectPage(projectId: string) {
|
|||||||
uploadError,
|
uploadError,
|
||||||
handleUploadBinary,
|
handleUploadBinary,
|
||||||
handleDownloadBinary,
|
handleDownloadBinary,
|
||||||
|
engines: engines ?? [],
|
||||||
|
enginesLoading,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import { ArrowLeft } from "lucide-react";
|
import { ArrowLeft } from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { EmptyState } from "@/components/EmptyState";
|
import { EmptyState } from "@/components/EmptyState";
|
||||||
import { ConfirmDialog } from "@/components/ConfirmDialog";
|
|
||||||
import { Breadcrumbs } from "@/components/Breadcrumbs.tsx";
|
import { Breadcrumbs } from "@/components/Breadcrumbs.tsx";
|
||||||
import { BinaryHeader } from "@/features/binary/BinaryHeader.tsx";
|
import { BinaryHeader } from "@/features/binary/BinaryHeader.tsx";
|
||||||
import { FunctionTree } from "@/features/binary/FunctionTree";
|
import { FunctionTree } from "@/features/binary/FunctionTree";
|
||||||
@ -14,6 +13,7 @@ import {
|
|||||||
import { useBinaryPage } from "@/features/binary/hooks/useBinaryPage";
|
import { useBinaryPage } from "@/features/binary/hooks/useBinaryPage";
|
||||||
import { CodeViewer } from "@/features/binary/CodeViewer";
|
import { CodeViewer } from "@/features/binary/CodeViewer";
|
||||||
import { AIChatPanel } from "@/features/binary/AIChatPanel";
|
import { AIChatPanel } from "@/features/binary/AIChatPanel";
|
||||||
|
import { AnalyzeDialog } from "@/features/binary/AnalyzeDialog";
|
||||||
|
|
||||||
const Binary = () => {
|
const Binary = () => {
|
||||||
const {
|
const {
|
||||||
@ -39,11 +39,12 @@ const Binary = () => {
|
|||||||
selectedFunction,
|
selectedFunction,
|
||||||
selectedFunctionLoading,
|
selectedFunctionLoading,
|
||||||
analyzeMutation,
|
analyzeMutation,
|
||||||
confirmReanalyzeOpen,
|
analyzeDialogOpen,
|
||||||
setConfirmReanalyzeOpen,
|
setAnalyzeDialogOpen,
|
||||||
handleAnalyze,
|
handleOpenAnalyzeDialog,
|
||||||
handleReanalyzeRequest,
|
handleAnalyzeConfirm,
|
||||||
handleReanalyzeConfirm,
|
engines,
|
||||||
|
enginesLoading,
|
||||||
} = useBinaryPage();
|
} = useBinaryPage();
|
||||||
|
|
||||||
const navigateToEntryPoint = () => {
|
const navigateToEntryPoint = () => {
|
||||||
@ -91,7 +92,7 @@ const Binary = () => {
|
|||||||
binary={binary}
|
binary={binary}
|
||||||
hasAnalysis={binary.staticAnalysisDone}
|
hasAnalysis={binary.staticAnalysisDone}
|
||||||
isAnalyzing={analyzeMutation.isPending}
|
isAnalyzing={analyzeMutation.isPending}
|
||||||
onAnalyze={binary.staticAnalysisDone ? handleReanalyzeRequest : handleAnalyze}
|
onAnalyze={handleOpenAnalyzeDialog}
|
||||||
entryPoint={analysis?.entryPoint}
|
entryPoint={analysis?.entryPoint}
|
||||||
onNavigateToEntryPoint={navigateToEntryPoint}
|
onNavigateToEntryPoint={navigateToEntryPoint}
|
||||||
/>
|
/>
|
||||||
@ -165,14 +166,14 @@ const Binary = () => {
|
|||||||
</ResizablePanelGroup>
|
</ResizablePanelGroup>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ConfirmDialog
|
<AnalyzeDialog
|
||||||
open={confirmReanalyzeOpen}
|
key={analyzeDialogOpen ? "analyze-open" : "analyze-closed"}
|
||||||
onOpenChange={setConfirmReanalyzeOpen}
|
open={analyzeDialogOpen}
|
||||||
title="Re-analyze binary"
|
onOpenChange={setAnalyzeDialogOpen}
|
||||||
description="This will replace all current analysis data. Function names, comments, and decompiled code will be lost. Are you sure?"
|
isReanalyze={!!binary.staticAnalysisDone}
|
||||||
confirmLabel="Re-analyze"
|
engines={engines}
|
||||||
variant="destructive"
|
enginesLoading={enginesLoading}
|
||||||
onConfirm={handleReanalyzeConfirm}
|
onConfirm={handleAnalyzeConfirm}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -32,6 +32,8 @@ const Project = () => {
|
|||||||
uploadError,
|
uploadError,
|
||||||
handleUploadBinary,
|
handleUploadBinary,
|
||||||
handleDownloadBinary,
|
handleDownloadBinary,
|
||||||
|
engines,
|
||||||
|
enginesLoading,
|
||||||
} = useProjectPage(projectId!);
|
} = useProjectPage(projectId!);
|
||||||
|
|
||||||
if (!project || !workspace) {
|
if (!project || !workspace) {
|
||||||
@ -111,12 +113,15 @@ const Project = () => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<UploadBinaryDialog
|
<UploadBinaryDialog
|
||||||
|
key={uploadDialogOpen ? "upload-open" : "upload-closed"}
|
||||||
open={uploadDialogOpen}
|
open={uploadDialogOpen}
|
||||||
onOpenChange={handleCloseUploadDialog}
|
onOpenChange={handleCloseUploadDialog}
|
||||||
onUpload={handleUploadBinary}
|
onUpload={handleUploadBinary}
|
||||||
isUploading={uploadMutation.isPending}
|
isUploading={uploadMutation.isPending}
|
||||||
uploadError={uploadError}
|
uploadError={uploadError}
|
||||||
onReset={uploadMutation.reset}
|
onReset={uploadMutation.reset}
|
||||||
|
engines={engines}
|
||||||
|
enginesLoading={enginesLoading}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ConfirmDialog
|
<ConfirmDialog
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user