mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-09 21:14:22 +00:00
feat(ui): add OpenCypher query editor (#10445)
This commit is contained in:
@@ -4,6 +4,10 @@ All notable changes to the **Prowler UI** are documented in this file.
|
||||
|
||||
## [1.23.0] (Prowler UNRELEASED)
|
||||
|
||||
### 🔄 Changed
|
||||
|
||||
- Attack Paths custom openCypher queries now use a code editor with syntax highlighting and line numbers [(#10445)](https://github.com/prowler-cloud/prowler/pull/10445)
|
||||
|
||||
### 🐞 Fixed
|
||||
|
||||
- Clear Filters now resets all filters including muted findings and auto-applies, Clear all in pills only removes pill-visible sub-filters, and the discard icon is now an Undo text button [(#10446)](https://github.com/prowler-cloud/prowler/pull/10446)
|
||||
|
||||
@@ -93,7 +93,9 @@ const createCustomQuery = (
|
||||
description: "",
|
||||
placeholder: CUSTOM_QUERY_PLACEHOLDER,
|
||||
required: true,
|
||||
input_type: QUERY_PARAMETER_INPUT_TYPES.TEXTAREA,
|
||||
input_type: QUERY_PARAMETER_INPUT_TYPES.CODE_EDITOR,
|
||||
editor_language: "openCypher",
|
||||
requirement_badge: "Read-only*",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { QueryCodeEditor } from "./query-code-editor";
|
||||
|
||||
describe("QueryCodeEditor", () => {
|
||||
it("renders a code editor surface with the compact read-only badge", () => {
|
||||
// Given
|
||||
render(
|
||||
<QueryCodeEditor
|
||||
ariaLabel="openCypher"
|
||||
language="openCypher"
|
||||
value=""
|
||||
placeholder="MATCH (n) RETURN n LIMIT 25"
|
||||
requirementBadge="Read-only*"
|
||||
onChange={() => {}}
|
||||
/>,
|
||||
);
|
||||
|
||||
// When
|
||||
const editor = screen.getByRole("textbox", { name: /opencypher/i });
|
||||
|
||||
// Then
|
||||
expect(editor).toHaveAttribute("contenteditable", "true");
|
||||
expect(screen.getByText("openCypher")).toBeInTheDocument();
|
||||
expect(screen.getByText("Read-only*")).toBeInTheDocument();
|
||||
expect(screen.getByText("MATCH (n) RETURN n LIMIT 25")).toBeInTheDocument();
|
||||
expect(screen.getByText("1")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("propagates content changes and exposes the invalid state in the container", async () => {
|
||||
// Given
|
||||
const user = userEvent.setup();
|
||||
const onChange = vi.fn();
|
||||
|
||||
render(
|
||||
<QueryCodeEditor
|
||||
ariaLabel="openCypher"
|
||||
language="openCypher"
|
||||
value=""
|
||||
invalid={true}
|
||||
onChange={onChange}
|
||||
/>,
|
||||
);
|
||||
|
||||
// When
|
||||
await user.type(screen.getByRole("textbox", { name: /opencypher/i }), "A");
|
||||
|
||||
// Then
|
||||
expect(onChange).toHaveBeenCalled();
|
||||
expect(screen.getByTestId("query-code-editor")).toHaveClass(
|
||||
"border-border-error-primary",
|
||||
);
|
||||
});
|
||||
});
|
||||
+399
@@ -0,0 +1,399 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
HighlightStyle,
|
||||
StreamLanguage,
|
||||
syntaxHighlighting,
|
||||
} from "@codemirror/language";
|
||||
import { tags } from "@lezer/highlight";
|
||||
import CodeMirror, {
|
||||
EditorView,
|
||||
highlightActiveLineGutter,
|
||||
lineNumbers,
|
||||
placeholder as codeEditorPlaceholder,
|
||||
} from "@uiw/react-codemirror";
|
||||
import { useTheme } from "next-themes";
|
||||
import { type HTMLAttributes } from "react";
|
||||
|
||||
import { Badge } from "@/components/shadcn";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const OPEN_CYPHER_KEYWORDS = new Set([
|
||||
"all",
|
||||
"and",
|
||||
"as",
|
||||
"asc",
|
||||
"ascending",
|
||||
"by",
|
||||
"call",
|
||||
"case",
|
||||
"contains",
|
||||
"create",
|
||||
"delete",
|
||||
"desc",
|
||||
"descending",
|
||||
"detach",
|
||||
"distinct",
|
||||
"else",
|
||||
"end",
|
||||
"exists",
|
||||
"false",
|
||||
"in",
|
||||
"is",
|
||||
"limit",
|
||||
"match",
|
||||
"merge",
|
||||
"not",
|
||||
"null",
|
||||
"optional",
|
||||
"or",
|
||||
"order",
|
||||
"remove",
|
||||
"return",
|
||||
"set",
|
||||
"skip",
|
||||
"then",
|
||||
"true",
|
||||
"unwind",
|
||||
"where",
|
||||
"with",
|
||||
"xor",
|
||||
"yield",
|
||||
]);
|
||||
|
||||
const OPEN_CYPHER_FUNCTIONS = new Set([
|
||||
"collect",
|
||||
"coalesce",
|
||||
"count",
|
||||
"exists",
|
||||
"head",
|
||||
"id",
|
||||
"keys",
|
||||
"labels",
|
||||
"last",
|
||||
"length",
|
||||
"nodes",
|
||||
"properties",
|
||||
"range",
|
||||
"reduce",
|
||||
"relationships",
|
||||
"size",
|
||||
"startnode",
|
||||
"sum",
|
||||
"tail",
|
||||
"timestamp",
|
||||
"tolower",
|
||||
"toupper",
|
||||
"trim",
|
||||
"type",
|
||||
]);
|
||||
|
||||
interface OpenCypherParserState {
|
||||
inBlockComment: boolean;
|
||||
inString: "'" | '"' | null;
|
||||
}
|
||||
|
||||
const openCypherLanguage = StreamLanguage.define<OpenCypherParserState>({
|
||||
startState() {
|
||||
return {
|
||||
inBlockComment: false,
|
||||
inString: null,
|
||||
};
|
||||
},
|
||||
token(stream, state) {
|
||||
if (state.inBlockComment) {
|
||||
while (!stream.eol()) {
|
||||
if (stream.match("*/")) {
|
||||
state.inBlockComment = false;
|
||||
break;
|
||||
}
|
||||
stream.next();
|
||||
}
|
||||
|
||||
return "comment";
|
||||
}
|
||||
|
||||
if (state.inString) {
|
||||
let escaped = false;
|
||||
|
||||
while (!stream.eol()) {
|
||||
const next = stream.next();
|
||||
|
||||
if (escaped) {
|
||||
escaped = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (next === "\\") {
|
||||
escaped = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (next === state.inString) {
|
||||
state.inString = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// OpenCypher only supports single-line strings — reset at EOL so an
|
||||
// unclosed quote does not bleed into subsequent lines.
|
||||
if (stream.eol()) {
|
||||
state.inString = null;
|
||||
}
|
||||
|
||||
return "string";
|
||||
}
|
||||
|
||||
if (stream.eatSpace()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (stream.match("//")) {
|
||||
stream.skipToEnd();
|
||||
return "comment";
|
||||
}
|
||||
|
||||
if (stream.match("/*")) {
|
||||
state.inBlockComment = true;
|
||||
return "comment";
|
||||
}
|
||||
|
||||
const quote = stream.peek();
|
||||
if (quote === "'" || quote === '"') {
|
||||
state.inString = quote;
|
||||
stream.next();
|
||||
return "string";
|
||||
}
|
||||
|
||||
if (stream.match(/\$[A-Za-z_][\w]*/)) {
|
||||
return "variableName";
|
||||
}
|
||||
|
||||
if (stream.match(/:[A-Za-z_][\w]*/)) {
|
||||
return "typeName";
|
||||
}
|
||||
|
||||
if (stream.match(/[()[\]{},.;]/)) {
|
||||
return "punctuation";
|
||||
}
|
||||
|
||||
if (stream.match(/[<>!=~|&+\-/*%^]+/)) {
|
||||
return "operator";
|
||||
}
|
||||
|
||||
if (stream.match(/\d+(?:\.\d+)?/)) {
|
||||
return "number";
|
||||
}
|
||||
|
||||
if (stream.match(/[A-Za-z_][\w]*/)) {
|
||||
const currentValue = stream.current();
|
||||
const normalizedValue = currentValue.toLowerCase();
|
||||
|
||||
if (OPEN_CYPHER_KEYWORDS.has(normalizedValue)) {
|
||||
return "keyword";
|
||||
}
|
||||
|
||||
if (
|
||||
OPEN_CYPHER_FUNCTIONS.has(normalizedValue) &&
|
||||
stream.match(/\s*(?=\()/, false)
|
||||
) {
|
||||
return "function";
|
||||
}
|
||||
|
||||
return "variableName";
|
||||
}
|
||||
|
||||
stream.next();
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
||||
const lightHighlightStyle = HighlightStyle.define([
|
||||
{ tag: tags.keyword, color: "#0550ae", fontWeight: "600" },
|
||||
{ tag: tags.string, color: "#0a3069" },
|
||||
{ tag: tags.number, color: "#8250df" },
|
||||
{ tag: [tags.typeName, tags.className], color: "#953800" },
|
||||
{ tag: [tags.variableName, tags.propertyName], color: "#24292f" },
|
||||
{ tag: tags.function(tags.variableName), color: "#8250df" },
|
||||
{ tag: tags.operator, color: "#57606a" },
|
||||
{ tag: tags.comment, color: "#6e7781", fontStyle: "italic" },
|
||||
{ tag: tags.punctuation, color: "#57606a" },
|
||||
]);
|
||||
|
||||
const darkHighlightStyle = HighlightStyle.define([
|
||||
{ tag: tags.keyword, color: "#79c0ff", fontWeight: "600" },
|
||||
{ tag: tags.string, color: "#a5d6ff" },
|
||||
{ tag: tags.number, color: "#d2a8ff" },
|
||||
{ tag: [tags.typeName, tags.className], color: "#ffa657" },
|
||||
{ tag: [tags.variableName, tags.propertyName], color: "#e6edf3" },
|
||||
{ tag: tags.function(tags.variableName), color: "#d2a8ff" },
|
||||
{ tag: tags.operator, color: "#8b949e" },
|
||||
{ tag: tags.comment, color: "#8b949e", fontStyle: "italic" },
|
||||
{ tag: tags.punctuation, color: "#8b949e" },
|
||||
]);
|
||||
|
||||
const MONO_FONT =
|
||||
'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace';
|
||||
|
||||
const baseThemeRules: Record<string, Record<string, string>> = {
|
||||
"&": {
|
||||
backgroundColor: "transparent",
|
||||
color: "var(--text-neutral-primary)",
|
||||
fontFamily: MONO_FONT,
|
||||
fontSize: "12px",
|
||||
},
|
||||
"&.cm-focused": {
|
||||
outline: "none",
|
||||
},
|
||||
".cm-scroller": {
|
||||
minHeight: "320px",
|
||||
overflow: "auto",
|
||||
fontFamily: MONO_FONT,
|
||||
lineHeight: "1.5rem",
|
||||
},
|
||||
".cm-content": {
|
||||
padding: "16px",
|
||||
caretColor: "var(--text-neutral-primary)",
|
||||
},
|
||||
".cm-line": {
|
||||
padding: "0 0 0 8px",
|
||||
},
|
||||
".cm-gutters": {
|
||||
backgroundColor: "var(--bg-neutral-secondary)",
|
||||
color: "var(--text-neutral-tertiary)",
|
||||
borderRight: "1px solid var(--border-neutral-secondary)",
|
||||
minWidth: "44px",
|
||||
},
|
||||
".cm-lineNumbers .cm-gutterElement": {
|
||||
padding: "0 10px 0 12px",
|
||||
},
|
||||
".cm-activeLineGutter": {
|
||||
backgroundColor: "var(--bg-neutral-secondary)",
|
||||
color: "var(--text-neutral-secondary)",
|
||||
},
|
||||
".cm-activeLine": {
|
||||
backgroundColor: "transparent",
|
||||
},
|
||||
".cm-cursor, .cm-dropCursor": {
|
||||
borderLeftColor: "var(--text-neutral-primary)",
|
||||
},
|
||||
".cm-placeholder": {
|
||||
color: "var(--text-neutral-tertiary)",
|
||||
},
|
||||
};
|
||||
|
||||
const LIGHT_SELECTION_BG = "rgba(9, 105, 218, 0.18)";
|
||||
const DARK_SELECTION_BG = "rgba(121, 192, 255, 0.18)";
|
||||
|
||||
const lightTheme = EditorView.theme(
|
||||
{
|
||||
...baseThemeRules,
|
||||
".cm-selectionBackground, &.cm-focused .cm-selectionBackground, ::selection":
|
||||
{ backgroundColor: LIGHT_SELECTION_BG },
|
||||
},
|
||||
{ dark: false },
|
||||
);
|
||||
|
||||
const darkTheme = EditorView.theme(
|
||||
{
|
||||
...baseThemeRules,
|
||||
".cm-selectionBackground, &.cm-focused .cm-selectionBackground, ::selection":
|
||||
{ backgroundColor: DARK_SELECTION_BG },
|
||||
},
|
||||
{ dark: true },
|
||||
);
|
||||
|
||||
interface QueryCodeEditorProps
|
||||
extends Omit<HTMLAttributes<HTMLDivElement>, "onChange"> {
|
||||
ariaLabel: string;
|
||||
language?: "openCypher";
|
||||
value: string;
|
||||
placeholder?: string;
|
||||
invalid?: boolean;
|
||||
requirementBadge?: string;
|
||||
onChange: (value: string) => void;
|
||||
onBlur?: () => void;
|
||||
}
|
||||
|
||||
export const QueryCodeEditor = ({
|
||||
id,
|
||||
className,
|
||||
ariaLabel,
|
||||
language = "openCypher",
|
||||
value,
|
||||
placeholder,
|
||||
invalid = false,
|
||||
requirementBadge,
|
||||
onChange,
|
||||
onBlur,
|
||||
...props
|
||||
}: QueryCodeEditorProps) => {
|
||||
const { resolvedTheme } = useTheme();
|
||||
const isDarkMode = resolvedTheme === "dark";
|
||||
const editorTheme = isDarkMode ? darkTheme : lightTheme;
|
||||
const editorHighlightStyle = isDarkMode
|
||||
? darkHighlightStyle
|
||||
: lightHighlightStyle;
|
||||
|
||||
return (
|
||||
<div
|
||||
data-testid="query-code-editor"
|
||||
data-language={language}
|
||||
className={cn(
|
||||
"border-border-neutral-secondary bg-bg-neutral-primary overflow-hidden rounded-xl border",
|
||||
invalid && "border-border-error-primary",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div className="border-border-neutral-secondary bg-bg-neutral-secondary flex items-center justify-between border-b px-4 py-2">
|
||||
<span className="text-text-neutral-secondary text-xs font-medium">
|
||||
{ariaLabel}
|
||||
</span>
|
||||
{requirementBadge ? (
|
||||
<Badge
|
||||
variant="tag"
|
||||
className="text-text-neutral-secondary border-border-neutral-secondary bg-bg-neutral-primary px-2 py-0 text-[11px]"
|
||||
>
|
||||
{requirementBadge}
|
||||
</Badge>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<CodeMirror
|
||||
value={value}
|
||||
theme={editorTheme}
|
||||
basicSetup={{
|
||||
foldGutter: false,
|
||||
highlightActiveLine: false,
|
||||
highlightActiveLineGutter: false,
|
||||
searchKeymap: false,
|
||||
}}
|
||||
editable={true}
|
||||
onChange={onChange}
|
||||
extensions={[
|
||||
lineNumbers(),
|
||||
highlightActiveLineGutter(),
|
||||
EditorView.lineWrapping,
|
||||
codeEditorPlaceholder(placeholder ?? ""),
|
||||
openCypherLanguage,
|
||||
syntaxHighlighting(editorHighlightStyle),
|
||||
EditorView.contentAttributes.of({
|
||||
id: id ?? "",
|
||||
"aria-label": ariaLabel,
|
||||
"aria-invalid": invalid ? "true" : "false",
|
||||
}),
|
||||
EditorView.editorAttributes.of({
|
||||
class: "minimal-scrollbar",
|
||||
}),
|
||||
EditorView.domEventHandlers({
|
||||
blur: () => {
|
||||
onBlur?.();
|
||||
return false;
|
||||
},
|
||||
}),
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
+11
-16
@@ -61,10 +61,12 @@ function TestCustomQueryForm() {
|
||||
name: "query",
|
||||
label: "openCypher",
|
||||
data_type: "string",
|
||||
input_type: "textarea",
|
||||
input_type: "code-editor",
|
||||
placeholder: "MATCH (n) RETURN n LIMIT 25",
|
||||
description: "",
|
||||
required: true,
|
||||
editor_language: "openCypher",
|
||||
requirement_badge: "Read-only*",
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -133,7 +135,7 @@ describe("QueryParametersForm", () => {
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders a textarea when the parameter input type is textarea", () => {
|
||||
it("renders a code editor when the parameter input type is code-editor", () => {
|
||||
// Given
|
||||
render(<TestCustomQueryForm />);
|
||||
|
||||
@@ -142,25 +144,18 @@ describe("QueryParametersForm", () => {
|
||||
const codeEditor = screen.getByTestId("query-code-editor");
|
||||
|
||||
// Then
|
||||
expect(input.tagName).toBe("TEXTAREA");
|
||||
expect(input).toHaveAttribute("data-slot", "textarea");
|
||||
expect(input).toHaveAttribute("placeholder", "MATCH (n) RETURN n LIMIT 25");
|
||||
expect(input).toHaveAttribute("spellcheck", "false");
|
||||
expect(input).toHaveAttribute("autocomplete", "off");
|
||||
expect(input).toHaveAttribute("autocorrect", "off");
|
||||
expect(input).toHaveAttribute("autocapitalize", "none");
|
||||
expect(input).toHaveClass(
|
||||
"minimal-scrollbar",
|
||||
"min-h-[320px]",
|
||||
"font-mono",
|
||||
"leading-6",
|
||||
);
|
||||
expect(input.tagName).toBe("DIV");
|
||||
expect(input).toHaveAttribute("contenteditable", "true");
|
||||
expect(codeEditor).toHaveAttribute("data-language", "openCypher");
|
||||
expect(codeEditor).toHaveClass(
|
||||
"rounded-xl",
|
||||
"border",
|
||||
"bg-bg-neutral-primary",
|
||||
);
|
||||
expect(screen.getByText("Read-only")).toBeInTheDocument();
|
||||
expect(screen.getByText("Read-only*")).toBeInTheDocument();
|
||||
expect(screen.getByText("openCypher")).toBeInTheDocument();
|
||||
expect(screen.queryByText("openCypher*")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("Read-only")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("uses the design-system error token for field validation messages", async () => {
|
||||
|
||||
+19
-43
@@ -12,11 +12,12 @@ import {
|
||||
} from "@/components/ui/form";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
ATTACK_PATH_QUERY_IDS,
|
||||
type AttackPathQuery,
|
||||
QUERY_PARAMETER_INPUT_TYPES,
|
||||
} from "@/types/attack-paths";
|
||||
|
||||
import { QueryCodeEditor } from "./query-code-editor";
|
||||
|
||||
interface QueryParametersFormProps {
|
||||
selectedQuery: AttackPathQuery | null | undefined;
|
||||
}
|
||||
@@ -88,19 +89,17 @@ export const QueryParametersForm = ({
|
||||
|
||||
const isTextarea =
|
||||
param.input_type === QUERY_PARAMETER_INPUT_TYPES.TEXTAREA;
|
||||
const isCustomCodeEditor =
|
||||
selectedQuery.id === ATTACK_PATH_QUERY_IDS.CUSTOM &&
|
||||
param.name === "query" &&
|
||||
isTextarea;
|
||||
const isCodeEditor =
|
||||
param.input_type === QUERY_PARAMETER_INPUT_TYPES.CODE_EDITOR;
|
||||
|
||||
return (
|
||||
<FormItem
|
||||
className={cn(
|
||||
"flex flex-col gap-1.5",
|
||||
isTextarea && "md:col-span-2",
|
||||
(isTextarea || isCodeEditor) && "md:col-span-2",
|
||||
)}
|
||||
>
|
||||
{!isCustomCodeEditor && (
|
||||
{!isCodeEditor && (
|
||||
<FormLabel className="text-text-neutral-tertiary text-xs font-medium">
|
||||
{param.label}
|
||||
{param.required && (
|
||||
@@ -108,42 +107,19 @@ export const QueryParametersForm = ({
|
||||
)}
|
||||
</FormLabel>
|
||||
)}
|
||||
{isCustomCodeEditor ? (
|
||||
<div
|
||||
data-testid="query-code-editor"
|
||||
className={cn(
|
||||
"border-border-neutral-secondary bg-bg-neutral-primary overflow-hidden rounded-xl border",
|
||||
fieldState.invalid && "border-border-error-primary",
|
||||
)}
|
||||
>
|
||||
<div className="border-border-neutral-secondary bg-bg-neutral-secondary flex items-center justify-between border-b px-4 py-2">
|
||||
<span className="text-text-neutral-secondary text-xs font-medium">
|
||||
{param.label}
|
||||
{param.required && (
|
||||
<span className="text-text-error-primary">*</span>
|
||||
)}
|
||||
</span>
|
||||
<span className="text-text-neutral-tertiary text-[11px]">
|
||||
Read-only
|
||||
</span>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
{...field}
|
||||
aria-label={param.label}
|
||||
variant="ghost"
|
||||
textareaSize="lg"
|
||||
placeholder={placeholder}
|
||||
value={field.value ?? ""}
|
||||
rows={14}
|
||||
spellCheck={false}
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="none"
|
||||
className="minimal-scrollbar min-h-[320px] rounded-none border-0 bg-transparent font-mono text-xs leading-6 hover:bg-transparent focus:bg-transparent focus:ring-0"
|
||||
/>
|
||||
</FormControl>
|
||||
</div>
|
||||
{isCodeEditor ? (
|
||||
<FormControl>
|
||||
<QueryCodeEditor
|
||||
ariaLabel={param.label}
|
||||
language={param.editor_language}
|
||||
value={String(field.value ?? "")}
|
||||
placeholder={placeholder}
|
||||
invalid={fieldState.invalid}
|
||||
requirementBadge={param.requirement_badge}
|
||||
onChange={field.onChange}
|
||||
onBlur={field.onBlur}
|
||||
/>
|
||||
</FormControl>
|
||||
) : (
|
||||
<FormControl>
|
||||
{isTextarea ? (
|
||||
|
||||
+3
-1
@@ -59,7 +59,9 @@ const mockQueries: AttackPathQuery[] = [
|
||||
data_type: "string",
|
||||
description: "",
|
||||
required: true,
|
||||
input_type: "textarea",
|
||||
input_type: "code-editor",
|
||||
editor_language: "openCypher",
|
||||
requirement_badge: "Read-only*",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -22,7 +22,7 @@ const getValidationSchema = (query?: AttackPathQuery) => {
|
||||
const isCustomQueryParameter =
|
||||
query.id === ATTACK_PATH_QUERY_IDS.CUSTOM &&
|
||||
param.name === "query" &&
|
||||
param.input_type === QUERY_PARAMETER_INPUT_TYPES.TEXTAREA;
|
||||
param.input_type === QUERY_PARAMETER_INPUT_TYPES.CODE_EDITOR;
|
||||
|
||||
let fieldSchema: z.ZodTypeAny = isCustomQueryParameter
|
||||
? customAttackPathQuerySchema
|
||||
|
||||
@@ -15,6 +15,46 @@
|
||||
"strategy": "installed",
|
||||
"generatedAt": "2026-02-12T11:04:39.164Z"
|
||||
},
|
||||
{
|
||||
"section": "dependencies",
|
||||
"name": "@codemirror/autocomplete",
|
||||
"from": "6.20.1",
|
||||
"to": "6.20.1",
|
||||
"strategy": "installed",
|
||||
"generatedAt": "2026-03-26T08:39:34.728Z"
|
||||
},
|
||||
{
|
||||
"section": "dependencies",
|
||||
"name": "@codemirror/commands",
|
||||
"from": "6.10.3",
|
||||
"to": "6.10.3",
|
||||
"strategy": "installed",
|
||||
"generatedAt": "2026-03-26T08:39:34.728Z"
|
||||
},
|
||||
{
|
||||
"section": "dependencies",
|
||||
"name": "@codemirror/language",
|
||||
"from": "6.12.2",
|
||||
"to": "6.12.2",
|
||||
"strategy": "installed",
|
||||
"generatedAt": "2026-03-26T08:39:34.728Z"
|
||||
},
|
||||
{
|
||||
"section": "dependencies",
|
||||
"name": "@codemirror/state",
|
||||
"from": "6.6.0",
|
||||
"to": "6.6.0",
|
||||
"strategy": "installed",
|
||||
"generatedAt": "2026-03-26T08:39:34.728Z"
|
||||
},
|
||||
{
|
||||
"section": "dependencies",
|
||||
"name": "@codemirror/view",
|
||||
"from": "6.40.0",
|
||||
"to": "6.40.0",
|
||||
"strategy": "installed",
|
||||
"generatedAt": "2026-03-26T08:39:34.728Z"
|
||||
},
|
||||
{
|
||||
"section": "dependencies",
|
||||
"name": "@extractus/feed-extractor",
|
||||
@@ -79,6 +119,14 @@
|
||||
"strategy": "installed",
|
||||
"generatedAt": "2025-12-12T10:01:54.132Z"
|
||||
},
|
||||
{
|
||||
"section": "dependencies",
|
||||
"name": "@lezer/highlight",
|
||||
"from": "1.2.3",
|
||||
"to": "1.2.3",
|
||||
"strategy": "installed",
|
||||
"generatedAt": "2026-03-26T08:39:34.728Z"
|
||||
},
|
||||
{
|
||||
"section": "dependencies",
|
||||
"name": "@next/third-parties",
|
||||
@@ -327,6 +375,14 @@
|
||||
"strategy": "installed",
|
||||
"generatedAt": "2025-10-22T12:36:37.962Z"
|
||||
},
|
||||
{
|
||||
"section": "dependencies",
|
||||
"name": "@uiw/react-codemirror",
|
||||
"from": "4.25.8",
|
||||
"to": "4.25.8",
|
||||
"strategy": "installed",
|
||||
"generatedAt": "2026-03-26T08:39:34.728Z"
|
||||
},
|
||||
{
|
||||
"section": "dependencies",
|
||||
"name": "ai",
|
||||
@@ -367,6 +423,14 @@
|
||||
"strategy": "installed",
|
||||
"generatedAt": "2025-11-19T12:28:39.510Z"
|
||||
},
|
||||
{
|
||||
"section": "dependencies",
|
||||
"name": "codemirror",
|
||||
"from": "6.0.2",
|
||||
"to": "6.0.2",
|
||||
"strategy": "installed",
|
||||
"generatedAt": "2026-03-26T08:39:34.728Z"
|
||||
},
|
||||
{
|
||||
"section": "dependencies",
|
||||
"name": "d3",
|
||||
|
||||
@@ -29,6 +29,11 @@
|
||||
"dependencies": {
|
||||
"@ai-sdk/react": "2.0.111",
|
||||
"@aws-sdk/client-bedrock-runtime": "3.988.0",
|
||||
"@codemirror/autocomplete": "6.20.1",
|
||||
"@codemirror/commands": "6.10.3",
|
||||
"@codemirror/language": "6.12.2",
|
||||
"@codemirror/state": "6.6.0",
|
||||
"@codemirror/view": "6.40.0",
|
||||
"@extractus/feed-extractor": "7.1.7",
|
||||
"@heroui/react": "2.8.4",
|
||||
"@hookform/resolvers": "5.2.2",
|
||||
@@ -37,6 +42,7 @@
|
||||
"@langchain/core": "1.1.15",
|
||||
"@langchain/mcp-adapters": "1.1.3",
|
||||
"@langchain/openai": "1.1.3",
|
||||
"@lezer/highlight": "1.2.3",
|
||||
"@next/third-parties": "16.1.6",
|
||||
"@radix-ui/react-alert-dialog": "1.1.14",
|
||||
"@radix-ui/react-avatar": "1.1.11",
|
||||
@@ -68,11 +74,13 @@
|
||||
"@tanstack/react-table": "8.21.3",
|
||||
"@types/dagre": "0.7.53",
|
||||
"@types/js-yaml": "4.0.9",
|
||||
"@uiw/react-codemirror": "4.25.8",
|
||||
"ai": "5.0.109",
|
||||
"alert": "6.0.2",
|
||||
"class-variance-authority": "0.7.1",
|
||||
"clsx": "2.1.1",
|
||||
"cmdk": "1.1.1",
|
||||
"codemirror": "6.0.2",
|
||||
"d3": "7.9.0",
|
||||
"dagre": "0.8.5",
|
||||
"date-fns": "4.1.0",
|
||||
|
||||
Generated
+202
@@ -38,6 +38,21 @@ importers:
|
||||
'@aws-sdk/client-bedrock-runtime':
|
||||
specifier: 3.988.0
|
||||
version: 3.988.0
|
||||
'@codemirror/autocomplete':
|
||||
specifier: 6.20.1
|
||||
version: 6.20.1
|
||||
'@codemirror/commands':
|
||||
specifier: 6.10.3
|
||||
version: 6.10.3
|
||||
'@codemirror/language':
|
||||
specifier: 6.12.2
|
||||
version: 6.12.2
|
||||
'@codemirror/state':
|
||||
specifier: 6.6.0
|
||||
version: 6.6.0
|
||||
'@codemirror/view':
|
||||
specifier: 6.40.0
|
||||
version: 6.40.0
|
||||
'@extractus/feed-extractor':
|
||||
specifier: 7.1.7
|
||||
version: 7.1.7
|
||||
@@ -62,6 +77,9 @@ importers:
|
||||
'@langchain/openai':
|
||||
specifier: 1.1.3
|
||||
version: 1.1.3(@langchain/core@1.1.15(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.4.0(@opentelemetry/api@1.9.0))(openai@6.16.0(ws@8.19.0)(zod@4.1.11)))(ws@8.19.0)
|
||||
'@lezer/highlight':
|
||||
specifier: 1.2.3
|
||||
version: 1.2.3
|
||||
'@next/third-parties':
|
||||
specifier: 16.1.6
|
||||
version: 16.1.6(next@16.1.6(@babel/core@7.28.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)
|
||||
@@ -155,6 +173,9 @@ importers:
|
||||
'@types/js-yaml':
|
||||
specifier: 4.0.9
|
||||
version: 4.0.9
|
||||
'@uiw/react-codemirror':
|
||||
specifier: 4.25.8
|
||||
version: 4.25.8(@babel/runtime@7.28.6)(@codemirror/autocomplete@6.20.1)(@codemirror/language@6.12.2)(@codemirror/lint@6.9.5)(@codemirror/search@6.6.0)(@codemirror/state@6.6.0)(@codemirror/theme-one-dark@6.1.3)(@codemirror/view@6.40.0)(codemirror@6.0.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
ai:
|
||||
specifier: 5.0.109
|
||||
version: 5.0.109(zod@4.1.11)
|
||||
@@ -170,6 +191,9 @@ importers:
|
||||
cmdk:
|
||||
specifier: 1.1.1
|
||||
version: 1.1.1(@types/react-dom@19.2.3(@types/react@19.2.8))(@types/react@19.2.8)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
codemirror:
|
||||
specifier: 6.0.2
|
||||
version: 6.0.2
|
||||
d3:
|
||||
specifier: 7.9.0
|
||||
version: 7.9.0
|
||||
@@ -914,6 +938,30 @@ packages:
|
||||
'@chevrotain/utils@11.0.3':
|
||||
resolution: {integrity: sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==}
|
||||
|
||||
'@codemirror/autocomplete@6.20.1':
|
||||
resolution: {integrity: sha512-1cvg3Vz1dSSToCNlJfRA2WSI4ht3K+WplO0UMOgmUYPivCyy2oueZY6Lx7M9wThm7SDUBViRmuT+OG/i8+ON9A==}
|
||||
|
||||
'@codemirror/commands@6.10.3':
|
||||
resolution: {integrity: sha512-JFRiqhKu+bvSkDLI+rUhJwSxQxYb759W5GBezE8Uc8mHLqC9aV/9aTC7yJSqCtB3F00pylrLCwnyS91Ap5ej4Q==}
|
||||
|
||||
'@codemirror/language@6.12.2':
|
||||
resolution: {integrity: sha512-jEPmz2nGGDxhRTg3lTpzmIyGKxz3Gp3SJES4b0nAuE5SWQoKdT5GoQ69cwMmFd+wvFUhYirtDTr0/DRHpQAyWg==}
|
||||
|
||||
'@codemirror/lint@6.9.5':
|
||||
resolution: {integrity: sha512-GElsbU9G7QT9xXhpUg1zWGmftA/7jamh+7+ydKRuT0ORpWS3wOSP0yT1FOlIZa7mIJjpVPipErsyvVqB9cfTFA==}
|
||||
|
||||
'@codemirror/search@6.6.0':
|
||||
resolution: {integrity: sha512-koFuNXcDvyyotWcgOnZGmY7LZqEOXZaaxD/j6n18TCLx2/9HieZJ5H6hs1g8FiRxBD0DNfs0nXn17g872RmYdw==}
|
||||
|
||||
'@codemirror/state@6.6.0':
|
||||
resolution: {integrity: sha512-4nbvra5R5EtiCzr9BTHiTLc+MLXK2QGiAVYMyi8PkQd3SR+6ixar/Q/01Fa21TBIDOZXgeWV4WppsQolSreAPQ==}
|
||||
|
||||
'@codemirror/theme-one-dark@6.1.3':
|
||||
resolution: {integrity: sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==}
|
||||
|
||||
'@codemirror/view@6.40.0':
|
||||
resolution: {integrity: sha512-WA0zdU7xfF10+5I3HhUUq3kqOx3KjqmtQ9lqZjfK7jtYk4G72YW9rezcSywpaUMCWOMlq+6E0pO1IWg1TNIhtg==}
|
||||
|
||||
'@csstools/color-helpers@6.0.1':
|
||||
resolution: {integrity: sha512-NmXRccUJMk2AWA5A7e5a//3bCIMyOu2hAtdRYrhPPHjDxINuCwX1w6rnIZ4xjLcp0ayv6h8Pc3X0eJUGiAAXHQ==}
|
||||
engines: {node: '>=20.19.0'}
|
||||
@@ -2161,6 +2209,18 @@ packages:
|
||||
peerDependencies:
|
||||
'@langchain/core': ^1.0.0
|
||||
|
||||
'@lezer/common@1.5.1':
|
||||
resolution: {integrity: sha512-6YRVG9vBkaY7p1IVxL4s44n5nUnaNnGM2/AckNgYOnxTG2kWh1vR8BMxPseWPjRNpb5VtXnMpeYAEAADoRV1Iw==}
|
||||
|
||||
'@lezer/highlight@1.2.3':
|
||||
resolution: {integrity: sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==}
|
||||
|
||||
'@lezer/lr@1.4.8':
|
||||
resolution: {integrity: sha512-bPWa0Pgx69ylNlMlPvBPryqeLYQjyJjqPx+Aupm5zydLIF3NE+6MMLT8Yi23Bd9cif9VS00aUebn+6fDIGBcDA==}
|
||||
|
||||
'@marijn/find-cluster-break@1.0.2':
|
||||
resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==}
|
||||
|
||||
'@mermaid-js/parser@0.6.3':
|
||||
resolution: {integrity: sha512-lnjOhe7zyHjc+If7yT4zoedx2vo4sHaTmtkl1+or8BRTnCtDmcTpAjpzDSfCZrshM5bCoz0GyidzadJAH1xobA==}
|
||||
|
||||
@@ -4975,6 +5035,28 @@ packages:
|
||||
resolution: {integrity: sha512-LZ2NqIHFhvFwxG0qZeLL9DvdNAHPGCY5dIRwBhyYeU+LfLhcStE1ImjsuTG/WaVh3XysGaeLW8Rqq7cGkPCFvw==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
||||
'@uiw/codemirror-extensions-basic-setup@4.25.8':
|
||||
resolution: {integrity: sha512-9Rr+liiBmK4xzZHszL+twNRJApthqmITBwDP3emNTtTrkBFN4gHlqfp+nodKmoVt1+bUH1qQCtyqt+7dbDTHiw==}
|
||||
peerDependencies:
|
||||
'@codemirror/autocomplete': '>=6.0.0'
|
||||
'@codemirror/commands': '>=6.0.0'
|
||||
'@codemirror/language': '>=6.0.0'
|
||||
'@codemirror/lint': '>=6.0.0'
|
||||
'@codemirror/search': '>=6.0.0'
|
||||
'@codemirror/state': '>=6.0.0'
|
||||
'@codemirror/view': '>=6.0.0'
|
||||
|
||||
'@uiw/react-codemirror@4.25.8':
|
||||
resolution: {integrity: sha512-A0aLOuJZm2yJ+U9GlMFwxwFciztjd5LhcAG4SMqFxdD58wH+sCQXuY4UU5J2hqgS390qAlShtUgREvJPUonbuQ==}
|
||||
peerDependencies:
|
||||
'@babel/runtime': '>=7.11.0'
|
||||
'@codemirror/state': '>=6.0.0'
|
||||
'@codemirror/theme-one-dark': '>=6.0.0'
|
||||
'@codemirror/view': '>=6.0.0'
|
||||
codemirror: '>=6.0.0'
|
||||
react: '>=17.0.0'
|
||||
react-dom: '>=17.0.0'
|
||||
|
||||
'@ungap/structured-clone@1.3.0':
|
||||
resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
|
||||
|
||||
@@ -5532,6 +5614,9 @@ packages:
|
||||
code-block-writer@13.0.3:
|
||||
resolution: {integrity: sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==}
|
||||
|
||||
codemirror@6.0.2:
|
||||
resolution: {integrity: sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==}
|
||||
|
||||
color-convert@2.0.1:
|
||||
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
|
||||
engines: {node: '>=7.0.0'}
|
||||
@@ -5635,6 +5720,9 @@ packages:
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
crelt@1.0.6:
|
||||
resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==}
|
||||
|
||||
cross-fetch@4.1.0:
|
||||
resolution: {integrity: sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw==}
|
||||
|
||||
@@ -8668,6 +8756,9 @@ packages:
|
||||
strnum@2.1.2:
|
||||
resolution: {integrity: sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ==}
|
||||
|
||||
style-mod@4.1.3:
|
||||
resolution: {integrity: sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==}
|
||||
|
||||
style-to-js@1.1.21:
|
||||
resolution: {integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==}
|
||||
|
||||
@@ -9164,6 +9255,9 @@ packages:
|
||||
vscode-uri@3.0.8:
|
||||
resolution: {integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==}
|
||||
|
||||
w3c-keyname@2.2.8:
|
||||
resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==}
|
||||
|
||||
w3c-xmlserializer@5.0.0:
|
||||
resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -10485,6 +10579,59 @@ snapshots:
|
||||
|
||||
'@chevrotain/utils@11.0.3': {}
|
||||
|
||||
'@codemirror/autocomplete@6.20.1':
|
||||
dependencies:
|
||||
'@codemirror/language': 6.12.2
|
||||
'@codemirror/state': 6.6.0
|
||||
'@codemirror/view': 6.40.0
|
||||
'@lezer/common': 1.5.1
|
||||
|
||||
'@codemirror/commands@6.10.3':
|
||||
dependencies:
|
||||
'@codemirror/language': 6.12.2
|
||||
'@codemirror/state': 6.6.0
|
||||
'@codemirror/view': 6.40.0
|
||||
'@lezer/common': 1.5.1
|
||||
|
||||
'@codemirror/language@6.12.2':
|
||||
dependencies:
|
||||
'@codemirror/state': 6.6.0
|
||||
'@codemirror/view': 6.40.0
|
||||
'@lezer/common': 1.5.1
|
||||
'@lezer/highlight': 1.2.3
|
||||
'@lezer/lr': 1.4.8
|
||||
style-mod: 4.1.3
|
||||
|
||||
'@codemirror/lint@6.9.5':
|
||||
dependencies:
|
||||
'@codemirror/state': 6.6.0
|
||||
'@codemirror/view': 6.40.0
|
||||
crelt: 1.0.6
|
||||
|
||||
'@codemirror/search@6.6.0':
|
||||
dependencies:
|
||||
'@codemirror/state': 6.6.0
|
||||
'@codemirror/view': 6.40.0
|
||||
crelt: 1.0.6
|
||||
|
||||
'@codemirror/state@6.6.0':
|
||||
dependencies:
|
||||
'@marijn/find-cluster-break': 1.0.2
|
||||
|
||||
'@codemirror/theme-one-dark@6.1.3':
|
||||
dependencies:
|
||||
'@codemirror/language': 6.12.2
|
||||
'@codemirror/state': 6.6.0
|
||||
'@codemirror/view': 6.40.0
|
||||
'@lezer/highlight': 1.2.3
|
||||
|
||||
'@codemirror/view@6.40.0':
|
||||
dependencies:
|
||||
'@codemirror/state': 6.6.0
|
||||
crelt: 1.0.6
|
||||
style-mod: 4.1.3
|
||||
w3c-keyname: 2.2.8
|
||||
|
||||
'@csstools/color-helpers@6.0.1': {}
|
||||
|
||||
'@csstools/css-calc@3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)':
|
||||
@@ -12102,6 +12249,18 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- ws
|
||||
|
||||
'@lezer/common@1.5.1': {}
|
||||
|
||||
'@lezer/highlight@1.2.3':
|
||||
dependencies:
|
||||
'@lezer/common': 1.5.1
|
||||
|
||||
'@lezer/lr@1.4.8':
|
||||
dependencies:
|
||||
'@lezer/common': 1.5.1
|
||||
|
||||
'@marijn/find-cluster-break@1.0.2': {}
|
||||
|
||||
'@mermaid-js/parser@0.6.3':
|
||||
dependencies:
|
||||
langium: 3.3.1
|
||||
@@ -15752,6 +15911,33 @@ snapshots:
|
||||
'@typescript-eslint/types': 8.53.0
|
||||
eslint-visitor-keys: 4.2.1
|
||||
|
||||
'@uiw/codemirror-extensions-basic-setup@4.25.8(@codemirror/autocomplete@6.20.1)(@codemirror/commands@6.10.3)(@codemirror/language@6.12.2)(@codemirror/lint@6.9.5)(@codemirror/search@6.6.0)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)':
|
||||
dependencies:
|
||||
'@codemirror/autocomplete': 6.20.1
|
||||
'@codemirror/commands': 6.10.3
|
||||
'@codemirror/language': 6.12.2
|
||||
'@codemirror/lint': 6.9.5
|
||||
'@codemirror/search': 6.6.0
|
||||
'@codemirror/state': 6.6.0
|
||||
'@codemirror/view': 6.40.0
|
||||
|
||||
'@uiw/react-codemirror@4.25.8(@babel/runtime@7.28.6)(@codemirror/autocomplete@6.20.1)(@codemirror/language@6.12.2)(@codemirror/lint@6.9.5)(@codemirror/search@6.6.0)(@codemirror/state@6.6.0)(@codemirror/theme-one-dark@6.1.3)(@codemirror/view@6.40.0)(codemirror@6.0.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.6
|
||||
'@codemirror/commands': 6.10.3
|
||||
'@codemirror/state': 6.6.0
|
||||
'@codemirror/theme-one-dark': 6.1.3
|
||||
'@codemirror/view': 6.40.0
|
||||
'@uiw/codemirror-extensions-basic-setup': 4.25.8(@codemirror/autocomplete@6.20.1)(@codemirror/commands@6.10.3)(@codemirror/language@6.12.2)(@codemirror/lint@6.9.5)(@codemirror/search@6.6.0)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)
|
||||
codemirror: 6.0.2
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
transitivePeerDependencies:
|
||||
- '@codemirror/autocomplete'
|
||||
- '@codemirror/language'
|
||||
- '@codemirror/lint'
|
||||
- '@codemirror/search'
|
||||
|
||||
'@ungap/structured-clone@1.3.0': {}
|
||||
|
||||
'@unrs/resolver-binding-android-arm-eabi@1.11.1':
|
||||
@@ -16344,6 +16530,16 @@ snapshots:
|
||||
|
||||
code-block-writer@13.0.3: {}
|
||||
|
||||
codemirror@6.0.2:
|
||||
dependencies:
|
||||
'@codemirror/autocomplete': 6.20.1
|
||||
'@codemirror/commands': 6.10.3
|
||||
'@codemirror/language': 6.12.2
|
||||
'@codemirror/lint': 6.9.5
|
||||
'@codemirror/search': 6.6.0
|
||||
'@codemirror/state': 6.6.0
|
||||
'@codemirror/view': 6.40.0
|
||||
|
||||
color-convert@2.0.1:
|
||||
dependencies:
|
||||
color-name: 1.1.4
|
||||
@@ -16424,6 +16620,8 @@ snapshots:
|
||||
optionalDependencies:
|
||||
typescript: 5.5.4
|
||||
|
||||
crelt@1.0.6: {}
|
||||
|
||||
cross-fetch@4.1.0:
|
||||
dependencies:
|
||||
node-fetch: 2.7.0
|
||||
@@ -20158,6 +20356,8 @@ snapshots:
|
||||
|
||||
strnum@2.1.2: {}
|
||||
|
||||
style-mod@4.1.3: {}
|
||||
|
||||
style-to-js@1.1.21:
|
||||
dependencies:
|
||||
style-to-object: 1.0.14
|
||||
@@ -20647,6 +20847,8 @@ snapshots:
|
||||
|
||||
vscode-uri@3.0.8: {}
|
||||
|
||||
w3c-keyname@2.2.8: {}
|
||||
|
||||
w3c-xmlserializer@5.0.0:
|
||||
dependencies:
|
||||
xml-name-validator: 5.0.0
|
||||
|
||||
@@ -84,6 +84,7 @@ type DataType = (typeof DATA_TYPES)[keyof typeof DATA_TYPES];
|
||||
export const QUERY_PARAMETER_INPUT_TYPES = {
|
||||
TEXT: "text",
|
||||
TEXTAREA: "textarea",
|
||||
CODE_EDITOR: "code-editor",
|
||||
} as const;
|
||||
|
||||
export type QueryParameterInputType =
|
||||
@@ -102,6 +103,8 @@ export interface AttackPathQueryParameter {
|
||||
placeholder?: string;
|
||||
required?: boolean;
|
||||
input_type?: QueryParameterInputType;
|
||||
editor_language?: "openCypher";
|
||||
requirement_badge?: string;
|
||||
}
|
||||
|
||||
export interface AttackPathQueryAttribution {
|
||||
|
||||
@@ -1 +1,73 @@
|
||||
import "@testing-library/jest-dom/vitest";
|
||||
|
||||
class MockStorage implements Storage {
|
||||
private readonly store = new Map<string, string>();
|
||||
|
||||
get length() {
|
||||
return this.store.size;
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.store.clear();
|
||||
}
|
||||
|
||||
getItem(key: string) {
|
||||
return this.store.get(key) ?? null;
|
||||
}
|
||||
|
||||
key(index: number) {
|
||||
return Array.from(this.store.keys())[index] ?? null;
|
||||
}
|
||||
|
||||
removeItem(key: string) {
|
||||
this.store.delete(key);
|
||||
}
|
||||
|
||||
setItem(key: string, value: string) {
|
||||
this.store.set(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
const localStorageMock = new MockStorage();
|
||||
const sessionStorageMock = new MockStorage();
|
||||
|
||||
Object.defineProperty(globalThis, "localStorage", {
|
||||
value: localStorageMock,
|
||||
configurable: true,
|
||||
});
|
||||
|
||||
Object.defineProperty(window, "localStorage", {
|
||||
value: localStorageMock,
|
||||
configurable: true,
|
||||
});
|
||||
|
||||
Object.defineProperty(globalThis, "sessionStorage", {
|
||||
value: sessionStorageMock,
|
||||
configurable: true,
|
||||
});
|
||||
|
||||
Object.defineProperty(window, "sessionStorage", {
|
||||
value: sessionStorageMock,
|
||||
configurable: true,
|
||||
});
|
||||
|
||||
const emptyClientRects = [] as unknown as DOMRectList;
|
||||
const emptyRect = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
toJSON: () => ({}),
|
||||
} as DOMRect;
|
||||
|
||||
if (!Range.prototype.getClientRects) {
|
||||
Range.prototype.getClientRects = () => emptyClientRects;
|
||||
}
|
||||
|
||||
if (!Range.prototype.getBoundingClientRect) {
|
||||
Range.prototype.getBoundingClientRect = () => emptyRect;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user