feat/ida66 #3
@ -22,6 +22,7 @@ import type {
|
||||
ListFunctionsParams,
|
||||
PageFunctionSummaryResponse,
|
||||
SegmentResponse,
|
||||
StringResponse,
|
||||
} from "../api.schemas";
|
||||
|
||||
import { customInstance } from "../../api";
|
||||
@ -248,6 +249,116 @@ export function useGetAnalysis<TData = Awaited<ReturnType<typeof getAnalysis>>,
|
||||
return { ...query, queryKey: queryOptions.queryKey };
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all strings extracted during a static analysis.
|
||||
* @summary List strings in an analysis
|
||||
*/
|
||||
export const listStrings = (
|
||||
analysisId: string,
|
||||
options?: SecondParameter<typeof customInstance>,
|
||||
signal?: AbortSignal
|
||||
) => {
|
||||
return customInstance<StringResponse[]>(
|
||||
{ url: `/analysis/${encodeURIComponent(String(analysisId))}/strings`, method: "GET", signal },
|
||||
options
|
||||
);
|
||||
};
|
||||
|
||||
export const getListStringsQueryKey = (analysisId: string) => {
|
||||
return [`/analysis/${analysisId}/strings`] as const;
|
||||
};
|
||||
|
||||
export const getListStringsQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof listStrings>>,
|
||||
TError = unknown,
|
||||
>(
|
||||
analysisId: string,
|
||||
options?: {
|
||||
query?: Partial<UseQueryOptions<Awaited<ReturnType<typeof listStrings>>, TError, TData>>;
|
||||
request?: SecondParameter<typeof customInstance>;
|
||||
}
|
||||
) => {
|
||||
const { query: queryOptions, request: requestOptions } = options ?? {};
|
||||
|
||||
const queryKey = queryOptions?.queryKey ?? getListStringsQueryKey(analysisId);
|
||||
|
||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof listStrings>>> = ({ signal }) =>
|
||||
listStrings(analysisId, requestOptions, signal);
|
||||
|
||||
return {
|
||||
queryKey,
|
||||
queryFn,
|
||||
enabled: analysisId !== null && analysisId !== undefined,
|
||||
...queryOptions,
|
||||
} as UseQueryOptions<Awaited<ReturnType<typeof listStrings>>, TError, TData> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
};
|
||||
};
|
||||
|
||||
export type ListStringsQueryResult = NonNullable<Awaited<ReturnType<typeof listStrings>>>;
|
||||
export type ListStringsQueryError = unknown;
|
||||
|
||||
export function useListStrings<TData = Awaited<ReturnType<typeof listStrings>>, TError = unknown>(
|
||||
analysisId: string,
|
||||
options: {
|
||||
query: Partial<UseQueryOptions<Awaited<ReturnType<typeof listStrings>>, TError, TData>> &
|
||||
Pick<
|
||||
DefinedInitialDataOptions<
|
||||
Awaited<ReturnType<typeof listStrings>>,
|
||||
TError,
|
||||
Awaited<ReturnType<typeof listStrings>>
|
||||
>,
|
||||
"initialData"
|
||||
>;
|
||||
request?: SecondParameter<typeof customInstance>;
|
||||
},
|
||||
queryClient?: QueryClient
|
||||
): DefinedUseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData, TError> };
|
||||
export function useListStrings<TData = Awaited<ReturnType<typeof listStrings>>, TError = unknown>(
|
||||
analysisId: string,
|
||||
options?: {
|
||||
query?: Partial<UseQueryOptions<Awaited<ReturnType<typeof listStrings>>, TError, TData>> &
|
||||
Pick<
|
||||
UndefinedInitialDataOptions<
|
||||
Awaited<ReturnType<typeof listStrings>>,
|
||||
TError,
|
||||
Awaited<ReturnType<typeof listStrings>>
|
||||
>,
|
||||
"initialData"
|
||||
>;
|
||||
request?: SecondParameter<typeof customInstance>;
|
||||
},
|
||||
queryClient?: QueryClient
|
||||
): UseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData, TError> };
|
||||
export function useListStrings<TData = Awaited<ReturnType<typeof listStrings>>, TError = unknown>(
|
||||
analysisId: string,
|
||||
options?: {
|
||||
query?: Partial<UseQueryOptions<Awaited<ReturnType<typeof listStrings>>, TError, TData>>;
|
||||
request?: SecondParameter<typeof customInstance>;
|
||||
},
|
||||
queryClient?: QueryClient
|
||||
): UseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData, TError> };
|
||||
/**
|
||||
* @summary List strings in an analysis
|
||||
*/
|
||||
|
||||
export function useListStrings<TData = Awaited<ReturnType<typeof listStrings>>, TError = unknown>(
|
||||
analysisId: string,
|
||||
options?: {
|
||||
query?: Partial<UseQueryOptions<Awaited<ReturnType<typeof listStrings>>, TError, TData>>;
|
||||
request?: SecondParameter<typeof customInstance>;
|
||||
},
|
||||
queryClient?: QueryClient
|
||||
): UseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData, TError> } {
|
||||
const queryOptions = getListStringsQueryOptions(analysisId, options);
|
||||
|
||||
const query = useQuery(queryOptions, queryClient) as UseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
};
|
||||
|
||||
return { ...query, queryKey: queryOptions.queryKey };
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve memory segments (code, data, etc.) identified during a static analysis.
|
||||
* @summary List segments in an analysis
|
||||
|
||||
@ -4,12 +4,6 @@
|
||||
* OpenAPI definition
|
||||
* OpenAPI spec version: v0
|
||||
*/
|
||||
export interface EngineResponse {
|
||||
name: string;
|
||||
label: string;
|
||||
available: boolean;
|
||||
}
|
||||
|
||||
export interface WorkspaceRequest {
|
||||
/** @minLength 1 */
|
||||
name: string;
|
||||
@ -141,6 +135,12 @@ export interface XrefResponse {
|
||||
address?: string;
|
||||
}
|
||||
|
||||
export interface EngineResponse {
|
||||
name?: string;
|
||||
label?: string;
|
||||
available?: boolean;
|
||||
}
|
||||
|
||||
export interface MessageResponse {
|
||||
id?: string;
|
||||
sessionId?: string;
|
||||
@ -162,6 +162,14 @@ export interface AnalysisResponse {
|
||||
updatedAt?: string;
|
||||
}
|
||||
|
||||
export interface StringResponse {
|
||||
id?: string;
|
||||
address?: string;
|
||||
value?: string;
|
||||
encoding?: string;
|
||||
referencedBy?: string;
|
||||
}
|
||||
|
||||
export interface SegmentResponse {
|
||||
id?: string;
|
||||
name?: string;
|
||||
@ -194,11 +202,11 @@ export interface SortObject {
|
||||
|
||||
export interface PageableObject {
|
||||
offset?: number;
|
||||
paged?: boolean;
|
||||
pageNumber?: number;
|
||||
pageSize?: number;
|
||||
sort?: SortObject;
|
||||
paged?: boolean;
|
||||
unpaged?: boolean;
|
||||
sort?: SortObject;
|
||||
}
|
||||
|
||||
export interface PageFunctionSummaryResponse {
|
||||
@ -208,10 +216,10 @@ export interface PageFunctionSummaryResponse {
|
||||
content?: FunctionSummaryResponse[];
|
||||
number?: number;
|
||||
pageable?: PageableObject;
|
||||
numberOfElements?: number;
|
||||
sort?: SortObject;
|
||||
first?: boolean;
|
||||
last?: boolean;
|
||||
numberOfElements?: number;
|
||||
empty?: boolean;
|
||||
}
|
||||
|
||||
|
||||
@ -36,7 +36,7 @@ export const ConfirmDialog = ({
|
||||
<DialogHeader>
|
||||
<div className="flex items-center gap-3">
|
||||
{variant === "destructive" && (
|
||||
<div className="bg-destructive/10 flex h-10 w-10 items-center justify-center rounded-full">
|
||||
<div className="bg-destructive/10 flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-full">
|
||||
<AlertTriangle className="text-destructive h-5 w-5" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -43,7 +43,7 @@ export const AnalyzeDialog = ({
|
||||
<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">
|
||||
<div className="bg-destructive/10 flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-full">
|
||||
<AlertTriangle className="text-destructive h-5 w-5" />
|
||||
</div>
|
||||
<div>
|
||||
|
||||
110
src/features/binary/StringTable.tsx
Normal file
110
src/features/binary/StringTable.tsx
Normal file
@ -0,0 +1,110 @@
|
||||
import { useState } from "react";
|
||||
import { Search } from "lucide-react";
|
||||
import type { StringResponse } from "@/api/generated/api.schemas";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
|
||||
interface StringTableProps {
|
||||
strings: StringResponse[];
|
||||
functionNameToId: Record<string, string>;
|
||||
onSelectFunction: (functionId: string) => void;
|
||||
highlightedAddress?: string;
|
||||
}
|
||||
|
||||
export function StringTable({
|
||||
strings,
|
||||
functionNameToId,
|
||||
onSelectFunction,
|
||||
highlightedAddress,
|
||||
}: StringTableProps) {
|
||||
const [search, setSearch] = useState("");
|
||||
|
||||
const filtered = strings.filter((s) => s.value?.toLowerCase().includes(search.toLowerCase()));
|
||||
|
||||
return (
|
||||
<div className="flex min-h-0 flex-1 flex-col">
|
||||
<div className="border-border shrink-0 border-b p-3">
|
||||
<div className="relative">
|
||||
<Search className="text-muted-foreground absolute top-1/2 left-2.5 h-4 w-4 -translate-y-1/2" />
|
||||
<Input
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
placeholder="Filter strings..."
|
||||
className="bg-muted h-8 border-transparent pl-9"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ScrollArea className="min-h-0 flex-1">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow className="hover:bg-transparent">
|
||||
<TableHead className="w-[110px]">Address</TableHead>
|
||||
<TableHead>Value</TableHead>
|
||||
<TableHead className="w-[90px]">Encoding</TableHead>
|
||||
<TableHead className="w-[140px]">Referenced by</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{filtered.map((s) => (
|
||||
<TableRow
|
||||
key={s.id}
|
||||
className={cn(
|
||||
highlightedAddress && s.address === highlightedAddress && "bg-primary/10"
|
||||
)}
|
||||
>
|
||||
<TableCell className="font-mono text-xs">{s.address}</TableCell>
|
||||
<TableCell className="max-w-[200px] truncate font-mono text-xs">
|
||||
{s.value}
|
||||
</TableCell>
|
||||
<TableCell className="text-muted-foreground text-xs">{s.encoding}</TableCell>
|
||||
<TableCell className="text-xs">
|
||||
{s.referencedBy
|
||||
?.split(/\s+/)
|
||||
.filter(Boolean)
|
||||
.map((name, i) => {
|
||||
const funcId = functionNameToId[name];
|
||||
return (
|
||||
<span key={name}>
|
||||
{i > 0 && " "}
|
||||
{funcId ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onSelectFunction(funcId)}
|
||||
className="text-primary hover:underline"
|
||||
>
|
||||
{name}
|
||||
</button>
|
||||
) : (
|
||||
name
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
{filtered.length === 0 && (
|
||||
<div className="text-muted-foreground py-4 text-center text-xs">
|
||||
{search ? "No matching strings" : "No strings found"}
|
||||
</div>
|
||||
)}
|
||||
</ScrollArea>
|
||||
|
||||
<div className="border-border text-muted-foreground border-t p-3 text-xs">
|
||||
{search ? `${filtered.length} of ${strings.length} strings` : `${strings.length} strings`}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -1,14 +1,24 @@
|
||||
"use client";
|
||||
|
||||
import { ArrowUpRight, ArrowDownRight, Link, Loader2, GitFork, GripHorizontal } from "lucide-react";
|
||||
import {
|
||||
ArrowUpRight,
|
||||
ArrowDownRight,
|
||||
Link,
|
||||
Loader2,
|
||||
GitFork,
|
||||
GripHorizontal,
|
||||
Text,
|
||||
} from "lucide-react";
|
||||
import { useGetCallers, useGetCallees } from "@/api/generated/function/function.ts";
|
||||
import type { XrefResponse } from "@/api/generated/api.schemas";
|
||||
import type { XrefResponse, StringResponse } from "@/api/generated/api.schemas";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
|
||||
interface XrefPanelProps {
|
||||
functionId?: string;
|
||||
onSelectFunction: (functionId: string) => void;
|
||||
strings?: StringResponse[];
|
||||
onSelectString?: (address: string) => void;
|
||||
}
|
||||
|
||||
function XrefTypeBadge({ type }: { type?: string }) {
|
||||
@ -108,7 +118,12 @@ function XrefList({
|
||||
);
|
||||
}
|
||||
|
||||
export function XrefPanel({ functionId, onSelectFunction }: XrefPanelProps) {
|
||||
export function XrefPanel({
|
||||
functionId,
|
||||
onSelectFunction,
|
||||
strings,
|
||||
onSelectString,
|
||||
}: XrefPanelProps) {
|
||||
const { data: callers, isLoading: callersLoading } = useGetCallers(functionId ?? "", {
|
||||
query: { enabled: !!functionId },
|
||||
});
|
||||
@ -146,6 +161,41 @@ export function XrefPanel({ functionId, onSelectFunction }: XrefPanelProps) {
|
||||
onSelectFunction={onSelectFunction}
|
||||
isLoading={calleesLoading}
|
||||
/>
|
||||
|
||||
{strings && (
|
||||
<>
|
||||
<div className="border-border mx-3 border-t" />
|
||||
<div className="px-3 py-2">
|
||||
<h4 className="text-muted-foreground mb-1.5 flex items-center gap-1.5 text-xs font-medium uppercase">
|
||||
<Text className="h-3 w-3" />
|
||||
Strings
|
||||
<span className="text-muted-foreground/60 ml-auto">{strings.length}</span>
|
||||
</h4>
|
||||
{strings.length === 0 ? (
|
||||
<p className="text-muted-foreground/50 py-2 text-center text-xs italic">None</p>
|
||||
) : (
|
||||
<div className="space-y-0.5">
|
||||
{strings.map((s, i) => (
|
||||
<button
|
||||
key={s.id ?? i}
|
||||
type="button"
|
||||
onClick={() => s.address && onSelectString?.(s.address)}
|
||||
disabled={!s.address || !onSelectString}
|
||||
className="hover:bg-muted flex w-full items-center gap-2 rounded-md px-2 py-1 text-left text-xs transition-colors disabled:opacity-50"
|
||||
>
|
||||
<span className="text-foreground flex-1 truncate font-mono">{s.value}</span>
|
||||
{s.address && (
|
||||
<span className="text-muted-foreground shrink-0 font-mono text-[10px]">
|
||||
{s.address}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</ScrollArea>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -4,7 +4,11 @@ import { useInfiniteQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { useGetBinary, useAnalyzeBinary } from "@/api/generated/binary/binary.ts";
|
||||
import { useGetProject } from "@/api/generated/project/project.ts";
|
||||
import { useGetWorkspace } from "@/api/generated/workspace/workspace.ts";
|
||||
import { useListAnalyses, useListSegments } from "@/api/generated/analysis/analysis.ts";
|
||||
import {
|
||||
useListAnalyses,
|
||||
useListSegments,
|
||||
useListStrings,
|
||||
} from "@/api/generated/analysis/analysis.ts";
|
||||
import { useGetFunction } from "@/api/generated/function/function.ts";
|
||||
import { useListEngines } from "@/api/generated/engine/engine.ts";
|
||||
import { customInstance } from "@/api/api.ts";
|
||||
@ -76,6 +80,11 @@ export function useBinaryPage() {
|
||||
query: { enabled: !!analysisId },
|
||||
});
|
||||
|
||||
// Fetch strings for the analysis
|
||||
const { data: strings } = useListStrings(analysisId ?? "", {
|
||||
query: { enabled: !!analysisId },
|
||||
});
|
||||
|
||||
// Infinite query for functions pagination — single page with all functions
|
||||
const functionsQuery = useInfiniteQuery({
|
||||
queryKey: ["functions", analysisId],
|
||||
@ -138,6 +147,25 @@ export function useBinaryPage() {
|
||||
return map;
|
||||
}, [allFunctions, segmentsList]);
|
||||
|
||||
// Build map: function name → id (for resolving referencedBy in strings)
|
||||
const functionNameToId = useMemo(() => {
|
||||
const map: Record<string, string> = {};
|
||||
for (const f of allFunctions) {
|
||||
if (f.name && f.id) map[f.name] = f.id;
|
||||
}
|
||||
return map;
|
||||
}, [allFunctions]);
|
||||
|
||||
// Strings referenced by the currently selected function
|
||||
const stringsList = useMemo(() => strings ?? [], [strings]);
|
||||
const functionStrings = useMemo(() => {
|
||||
if (!selectedFunction?.name) return [];
|
||||
return stringsList.filter((s) => {
|
||||
if (!s.referencedBy) return false;
|
||||
return s.referencedBy.split(/\s+/).includes(selectedFunction.name!);
|
||||
});
|
||||
}, [stringsList, selectedFunction]);
|
||||
|
||||
// Filter functions by segment, flags, and text search (text search is in FunctionTree)
|
||||
const filteredFunctions = useMemo(() => {
|
||||
let result = allFunctions;
|
||||
@ -214,6 +242,9 @@ export function useBinaryPage() {
|
||||
isFetchingNextPage: functionsQuery.isFetchingNextPage,
|
||||
functionsLoading: functionsQuery.isLoading,
|
||||
functionsTotal: functionsQuery.data?.pages[0]?.totalElements ?? 0,
|
||||
strings: stringsList,
|
||||
functionNameToId,
|
||||
functionStrings,
|
||||
selectedFunction,
|
||||
selectedFunctionLoading,
|
||||
analyzeMutation,
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
import { useState } from "react";
|
||||
import { ArrowLeft } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { EmptyState } from "@/components/EmptyState";
|
||||
import { Breadcrumbs } from "@/components/Breadcrumbs.tsx";
|
||||
import { BinaryHeader } from "@/features/binary/BinaryHeader.tsx";
|
||||
import { FunctionTree } from "@/features/binary/FunctionTree";
|
||||
import { StringTable } from "@/features/binary/StringTable";
|
||||
import { XrefPanel } from "@/features/binary/XrefPanel";
|
||||
import {
|
||||
ResizableHandle,
|
||||
@ -14,6 +16,7 @@ import { useBinaryPage } from "@/features/binary/hooks/useBinaryPage";
|
||||
import { CodeViewer } from "@/features/binary/CodeViewer";
|
||||
import { AIChatPanel } from "@/features/binary/AIChatPanel";
|
||||
import { AnalyzeDialog } from "@/features/binary/AnalyzeDialog";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
|
||||
const Binary = () => {
|
||||
const {
|
||||
@ -36,6 +39,9 @@ const Binary = () => {
|
||||
hasNextPage,
|
||||
isFetchingNextPage,
|
||||
functionsTotal,
|
||||
strings,
|
||||
functionNameToId,
|
||||
functionStrings,
|
||||
selectedFunction,
|
||||
selectedFunctionLoading,
|
||||
analyzeMutation,
|
||||
@ -53,6 +59,19 @@ const Binary = () => {
|
||||
if (match) selectFunctionByAddress(match[1]);
|
||||
};
|
||||
|
||||
const [activeTab, setActiveTab] = useState("functions");
|
||||
const [highlightedStringAddress, setHighlightedStringAddress] = useState<string>();
|
||||
|
||||
const handleSelectStringInXref = (address: string) => {
|
||||
setActiveTab("strings");
|
||||
setHighlightedStringAddress(address);
|
||||
};
|
||||
|
||||
const handleTabChange = (value: string) => {
|
||||
setActiveTab(value);
|
||||
setHighlightedStringAddress(undefined);
|
||||
};
|
||||
|
||||
if (binaryLoading) return null;
|
||||
|
||||
if (!binary) {
|
||||
@ -104,24 +123,47 @@ const Binary = () => {
|
||||
<ResizablePanelGroup orientation="vertical" className="h-full min-h-0 flex-col">
|
||||
<ResizablePanel id="function-tree-panel" defaultSize={100} minSize={30}>
|
||||
<div className="flex h-full flex-col overflow-hidden">
|
||||
<div className="border-border flex shrink-0 items-center border-b px-4 py-2">
|
||||
<span className="text-sm font-medium">Functions</span>
|
||||
</div>
|
||||
<FunctionTree
|
||||
functions={functions}
|
||||
selectedFunctionId={selectedFunctionId}
|
||||
onSelectFunction={(f) => setSelectedFunctionId(f.id)}
|
||||
onLoadMore={fetchNextPage}
|
||||
hasMore={!!hasNextPage}
|
||||
isLoading={isFetchingNextPage}
|
||||
totalCount={functionsTotal}
|
||||
segments={segments}
|
||||
selectedSegmentId={selectedSegmentId}
|
||||
onSelectSegment={setSelectedSegmentId}
|
||||
segmentByAddress={segmentByAddress}
|
||||
selectedFlags={selectedFlags}
|
||||
onToggleFlag={toggleFlag}
|
||||
/>
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
onValueChange={handleTabChange}
|
||||
className="flex min-h-0 flex-1 flex-col"
|
||||
>
|
||||
<TabsList className="mx-3 mt-2 shrink-0">
|
||||
<TabsTrigger value="functions">Functions</TabsTrigger>
|
||||
<TabsTrigger value="strings">Strings</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent
|
||||
value="functions"
|
||||
className="mt-1 flex min-h-0 flex-1 flex-col overflow-hidden data-[state=inactive]:hidden"
|
||||
>
|
||||
<FunctionTree
|
||||
functions={functions}
|
||||
selectedFunctionId={selectedFunctionId}
|
||||
onSelectFunction={(f) => setSelectedFunctionId(f.id)}
|
||||
onLoadMore={fetchNextPage}
|
||||
hasMore={!!hasNextPage}
|
||||
isLoading={isFetchingNextPage}
|
||||
totalCount={functionsTotal}
|
||||
segments={segments}
|
||||
selectedSegmentId={selectedSegmentId}
|
||||
onSelectSegment={setSelectedSegmentId}
|
||||
segmentByAddress={segmentByAddress}
|
||||
selectedFlags={selectedFlags}
|
||||
onToggleFlag={toggleFlag}
|
||||
/>
|
||||
</TabsContent>
|
||||
<TabsContent
|
||||
value="strings"
|
||||
className="mt-1 flex min-h-0 flex-1 flex-col overflow-hidden data-[state=inactive]:hidden"
|
||||
>
|
||||
<StringTable
|
||||
strings={strings ?? []}
|
||||
functionNameToId={functionNameToId}
|
||||
onSelectFunction={setSelectedFunctionId}
|
||||
highlightedAddress={highlightedStringAddress}
|
||||
/>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
</ResizablePanel>
|
||||
|
||||
@ -141,6 +183,8 @@ const Binary = () => {
|
||||
<XrefPanel
|
||||
functionId={selectedFunctionId}
|
||||
onSelectFunction={setSelectedFunctionId}
|
||||
strings={functionStrings}
|
||||
onSelectString={handleSelectStringInXref}
|
||||
/>
|
||||
</div>
|
||||
</ResizablePanel>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user