From 3e7f13ef2a3638f198115c9df76f8152ad90a2b4 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Mon, 7 Oct 2024 16:20:31 +0300 Subject: [PATCH 1/2] add dump in the rw and ro permission and provide to ro user to run queries --- app/api/graph/[graph]/route.ts | 8 ++++++-- app/api/user/options.ts | 4 ++-- app/components/EditorComponent.tsx | 12 +++++++----- app/graph/GraphDataPanel.tsx | 15 ++++++++++----- app/graph/GraphView.tsx | 9 +++++++-- app/graph/Selector.tsx | 6 ++++-- app/graph/page.tsx | 10 +++++++--- lib/utils.ts | 4 ++-- 8 files changed, 45 insertions(+), 23 deletions(-) diff --git a/app/api/graph/[graph]/route.ts b/app/api/graph/[graph]/route.ts index 1f62a8a..5e30c7d 100644 --- a/app/api/graph/[graph]/route.ts +++ b/app/api/graph/[graph]/route.ts @@ -132,14 +132,18 @@ export async function GET(request: NextRequest, { params }: { params: { graph: s const query = request.nextUrl.searchParams.get("query") const create = request.nextUrl.searchParams.get("create") + const role = request.nextUrl.searchParams.get("role") if (!query) throw new Error("Missing parameter 'query'") if (create === "false" && (await client.list()).some((g) => g === graphId)) return NextResponse.json({}, { status: 200 }) - + const graph = client.selectGraph(graphId) - const result = await graph.query(query) + + const result = role === "Read-Only" + ? await graph.roQuery(query) + : await graph.query(query) if (!result) throw new Error("something went wrong") diff --git a/app/api/user/options.ts b/app/api/user/options.ts index d4655a4..146d2f5 100644 --- a/app/api/user/options.ts +++ b/app/api/user/options.ts @@ -7,7 +7,7 @@ export interface CreateUser { export const ROLE = new Map( [ ["Admin", ["on", "~*", "&*", "+@all"]], - ["Read-Write", ["on", "~*", "resetchannels", "-@all", "+graph.explain", "+graph.list", "+ping", "+graph.ro_query", "+info", "+graph.delete", "+graph.query", "+graph.profile"]], - ["Read-Only", ["on", "~*", "resetchannels", "-@all", "+graph.explain", "+graph.list", "+ping", "+graph.ro_query", "+info"]] + ["Read-Write", ["on", "~*", "resetchannels", "-@all", "+graph.explain", "+graph.list", "+ping", "+graph.ro_query", "+info", "+dump", "+graph.delete", "+graph.query", "+graph.profile"]], + ["Read-Only", ["on", "~*", "resetchannels", "-@all", "+graph.explain", "+graph.list", "+ping", "+graph.ro_query", "+info", "+dump"]] ] ) \ No newline at end of file diff --git a/app/components/EditorComponent.tsx b/app/components/EditorComponent.tsx index 9fc35dc..d9fbdfe 100644 --- a/app/components/EditorComponent.tsx +++ b/app/components/EditorComponent.tsx @@ -6,6 +6,7 @@ import { useEffect, useRef, useState } from "react" import * as monaco from "monaco-editor"; import { prepareArg, securedFetch } from "@/lib/utils"; import { Maximize2 } from "lucide-react"; +import { Session } from "next-auth"; import { Graph } from "../api/graph/model"; import Button from "./ui/Button"; @@ -25,6 +26,7 @@ interface Props { runQuery: (query: string) => void graph: Graph isCollapsed: boolean + data: Session | null } const monacoOptions: monaco.editor.IStandaloneEditorConstructionOptions = { @@ -196,7 +198,7 @@ const getEmptySuggestions = (): Suggestions => ({ functions: [] }) -export default function EditorComponent({ currentQuery, historyQueries, setCurrentQuery, maximize, runQuery, graph, isCollapsed }: Props) { +export default function EditorComponent({ currentQuery, historyQueries, setCurrentQuery, maximize, runQuery, graph, isCollapsed, data }: Props) { const [query, setQuery] = useState(currentQuery) const [monacoInstance, setMonacoInstance] = useState() @@ -272,7 +274,7 @@ export default function EditorComponent({ currentQuery, historyQueries, setCurre const addLabelsSuggestions = async (sug: monaco.languages.CompletionItem[]) => { const labelsQuery = `CALL db.labels()` - await securedFetch(`api/graph/${prepareArg(graph.Id)}/?query=${prepareArg(labelsQuery)}`, { + await securedFetch(`api/graph/${prepareArg(graph.Id)}/?query=${prepareArg(labelsQuery)}&role=${data!.user.role}`, { method: "GET" }).then((res) => res.json()).then((json) => { json.result.data.forEach(({ label }: { label: string }) => { @@ -290,7 +292,7 @@ export default function EditorComponent({ currentQuery, historyQueries, setCurre const addRelationshipTypesSuggestions = async (sug: monaco.languages.CompletionItem[]) => { const relationshipTypeQuery = `CALL db.relationshipTypes()` - await securedFetch(`api/graph/${prepareArg(graph.Id)}/?query=${prepareArg(relationshipTypeQuery)}`, { + await securedFetch(`api/graph/${prepareArg(graph.Id)}/?query=${prepareArg(relationshipTypeQuery)}&role=${data!.user.role}`, { method: "GET" }).then((res) => res.json()).then((json) => { json.result.data.forEach(({ relationshipType }: { relationshipType: string }) => { @@ -308,7 +310,7 @@ export default function EditorComponent({ currentQuery, historyQueries, setCurre const addPropertyKeysSuggestions = async (sug: monaco.languages.CompletionItem[]) => { const propertyKeysQuery = `CALL db.propertyKeys()` - await securedFetch(`api/graph/${prepareArg(graph.Id)}/?query=${prepareArg(propertyKeysQuery)}`, { + await securedFetch(`api/graph/${prepareArg(graph.Id)}/?query=${prepareArg(propertyKeysQuery)}&role=${data!.user.role}`, { method: "GET" }).then((res) => res.json()).then((json) => { json.result.data.forEach(({ propertyKey }: { propertyKey: string }) => { @@ -325,7 +327,7 @@ export default function EditorComponent({ currentQuery, historyQueries, setCurre const addFunctionsSuggestions = async (functions: monaco.languages.CompletionItem[]) => { const proceduresQuery = `CALL dbms.procedures() YIELD name` - await securedFetch(`api/graph/${prepareArg(graph.Id)}/?query=${prepareArg(proceduresQuery)}`, { + await securedFetch(`api/graph/${prepareArg(graph.Id)}/?query=${prepareArg(proceduresQuery)}&role=${data!.user.role}`, { method: "GET" }).then((res) => res.json()).then((json) => { [...json.result.data.map(({ name }: { name: string }) => name), ...FUNCTIONS].forEach((name: string) => { diff --git a/app/graph/GraphDataPanel.tsx b/app/graph/GraphDataPanel.tsx index b515590..518c365 100644 --- a/app/graph/GraphDataPanel.tsx +++ b/app/graph/GraphDataPanel.tsx @@ -5,6 +5,7 @@ import { ElementDataDefinition, prepareArg, securedFetch, Toast } from "@/lib/utils"; import { Dispatch, SetStateAction, useEffect, useState } from "react"; import { ChevronRight, PlusCircle, Trash2 } from "lucide-react"; +import { Session } from "next-auth"; import Button from "../components/ui/Button"; import Input from "../components/ui/Input"; import { Graph } from "../api/graph/model"; @@ -16,6 +17,7 @@ interface Props { onExpand: () => void; graph: Graph; onDeleteElement?: () => Promise; + data: Session | null; } const excludedProperties = new Set([ @@ -26,9 +28,10 @@ const excludedProperties = new Set([ "label", "target", "source", + "name", ]); -export default function GraphDataPanel({ obj, setObj, onExpand, onDeleteElement, graph }: Props) { +export default function GraphDataPanel({ obj, setObj, onExpand, onDeleteElement, graph, data }: Props) { const [attributes, setAttributes] = useState([]); const [editable, setEditable] = useState(""); @@ -40,7 +43,7 @@ export default function GraphDataPanel({ obj, setObj, onExpand, onDeleteElement, const type = !("source" in obj) useEffect(() => { - setAttributes(Object.keys(obj).filter((key) => !excludedProperties.has(key))); + setAttributes(Object.keys(obj).filter((key) => !excludedProperties.has(key) || (key === "name" && obj.name !== obj.id))); setLabel(type ? obj.category : obj.label); }, [obj, type]); @@ -52,9 +55,9 @@ export default function GraphDataPanel({ obj, setObj, onExpand, onDeleteElement, })).ok if (success) { - graph.Elements.forEach(({ data }) => { - if (data.id !== id) return - data[key] = val + graph.Elements.forEach(({ data: d }) => { + if (d.id !== id) return + d[key] = val }) setObj((prev) => ({ ...prev, [key]: val })) setNewVal("") @@ -202,6 +205,7 @@ export default function GraphDataPanel({ obj, setObj, onExpand, onDeleteElement, label="Add Value" icon={} onClick={() => setIsAddValue(true)} + disabled={data!.user.role === "Read-Only"} /> @@ -211,6 +215,7 @@ export default function GraphDataPanel({ obj, setObj, onExpand, onDeleteElement, icon={} label="Delete" onClick={onDeleteElement} + disabled={data!.user.role === "Read-Only"} /> diff --git a/app/graph/GraphView.tsx b/app/graph/GraphView.tsx index afeac76..3fb29ea 100644 --- a/app/graph/GraphView.tsx +++ b/app/graph/GraphView.tsx @@ -9,6 +9,7 @@ import { ImperativePanelHandle } from "react-resizable-panels"; import { ChevronLeft, Maximize2, Minimize2 } from "lucide-react" import { cn, ElementDataDefinition, prepareArg, securedFetch } from "@/lib/utils"; import dynamic from "next/dynamic"; +import { Session } from "next-auth"; import { Category, Graph } from "../api/graph/model"; import DataPanel from "./GraphDataPanel"; import Labels from "./labels"; @@ -95,7 +96,7 @@ function getStyle() { const getElementId = (element: ElementDataDefinition) => element.source ? { id: element.id?.slice(1), query: "()-[e]-()" } : { id: element.id, query: "(e)" } -const GraphView = forwardRef(({ graph, selectedElement, setSelectedElement, runQuery, historyQuery, historyQueries, fetchCount }: { +const GraphView = forwardRef(({ graph, selectedElement, setSelectedElement, runQuery, historyQuery, historyQueries, fetchCount, data }: { graph: Graph selectedElement: ElementDataDefinition | undefined setSelectedElement: Dispatch> @@ -103,6 +104,7 @@ const GraphView = forwardRef(({ graph, selectedElement, setSelectedElement, runQ historyQuery: string historyQueries: string[] fetchCount: () => void + data: Session | null }, ref) => { const [query, setQuery] = useState("") @@ -349,13 +351,15 @@ const GraphView = forwardRef(({ graph, selectedElement, setSelectedElement, runQ historyQueries={historyQueries} runQuery={runQuery} setCurrentQuery={setQuery} + data={data} />
{ isCollapsed && graph.Id && @@ -436,6 +440,7 @@ const GraphView = forwardRef(({ graph, selectedElement, setSelectedElement, runQ onExpand={onExpand} graph={graph} onDeleteElement={handelDeleteElement} + data={data} /> } diff --git a/app/graph/Selector.tsx b/app/graph/Selector.tsx index 4bd53a0..dc33cf1 100644 --- a/app/graph/Selector.tsx +++ b/app/graph/Selector.tsx @@ -6,6 +6,7 @@ import { Editor } from "@monaco-editor/react"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"; import { editor } from "monaco-editor"; import { Toast, cn, prepareArg, securedFetch } from "@/lib/utils"; +import { Session } from "next-auth"; import Combobox from "../components/ui/combobox"; import { Graph, Query } from "../api/graph/model"; import UploadGraph from "../components/graph/UploadGraph"; @@ -15,7 +16,7 @@ import Duplicate from "./Duplicate"; import SchemaView from "../schema/SchemaView"; import View from "./View"; -export default function Selector({ onChange, graphName, queries, runQuery, edgesCount, nodesCount, setGraph, graph }: { +export default function Selector({ onChange, graphName, queries, runQuery, edgesCount, nodesCount, setGraph, graph, data }: { /* eslint-disable react/require-default-props */ onChange: (selectedGraphName: string) => void graphName: string @@ -25,6 +26,7 @@ export default function Selector({ onChange, graphName, queries, runQuery, edges nodesCount: number setGraph: (graph: Graph) => void graph: Graph + data: Session | null }) { const [options, setOptions] = useState([]); @@ -64,7 +66,7 @@ export default function Selector({ onChange, graphName, queries, runQuery, edges const handleOnChange = async (name: string) => { if (runQuery) { const q = 'MATCH (n)-[e]-(m) return n,e,m' - const result = await securedFetch(`api/graph/${prepareArg(name)}_schema/?query=${prepareArg(q)}&create=false`, { + const result = await securedFetch(`api/graph/${prepareArg(name)}_schema/?query=${prepareArg(q)}&create=false&role=${data!.user.role}`, { method: "GET" }) diff --git a/app/graph/page.tsx b/app/graph/page.tsx index 9605a7c..c12625a 100644 --- a/app/graph/page.tsx +++ b/app/graph/page.tsx @@ -3,6 +3,7 @@ import { useCallback, useEffect, useState } from "react"; import { Toast, prepareArg, securedFetch } from "@/lib/utils"; import { ElementDataDefinition } from "cytoscape"; +import { useSession } from "next-auth/react"; import Selector from "./Selector"; import Header from "../components/Header"; import { Graph, Query } from "../api/graph/model"; @@ -17,6 +18,7 @@ export default function Page() { const [queries, setQueries] = useState([]) const [historyQuery, setHistoryQuery] = useState("") const [selectedElement, setSelectedElement] = useState(); + const { data } = useSession() const fetchCount = useCallback(async () => { @@ -24,11 +26,11 @@ export default function Page() { const q1 = "MATCH (n) RETURN COUNT(n) as nodes" const q2 = "MATCH ()-[e]->() RETURN COUNT(e) as edges" - const nodes = await (await securedFetch(`api/graph/${prepareArg(graphName)}/?query=${q1}`, { + const nodes = await (await securedFetch(`api/graph/${prepareArg(graphName)}/?query=${q1}&role=${data!.user.role}`, { method: "GET" })).json() - const edges = await (await securedFetch(`api/graph/${prepareArg(graphName)}/?query=${q2}`, { + const edges = await (await securedFetch(`api/graph/${prepareArg(graphName)}/?query=${q2}&role=${data!.user.role}`, { method: "GET" })).json() @@ -56,7 +58,7 @@ export default function Page() { return null } - const result = await securedFetch(`api/graph/${prepareArg(graphName)}/?query=${prepareArg(query)}`, { + const result = await securedFetch(`api/graph/${prepareArg(graphName)}/?query=${prepareArg(query)}&role=${data!.user.role}`, { method: "GET" }) @@ -99,6 +101,7 @@ export default function Page() { nodesCount={nodesCount} setGraph={setGraph} graph={graph} + data={data} /> text)} fetchCount={fetchCount} + data={data} />
diff --git a/lib/utils.ts b/lib/utils.ts index 0adaec4..422f10c 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -23,8 +23,8 @@ export async function securedFetch(input: string | URL | globalThis.Request, ini const response = await fetch(input, init) const { status } = response if (status >= 300) { - response.json().then((err) => { - Toast(err.message) + response.text().then((message) => { + Toast(message) }).then(() => { if (status === 401 || status >= 500) { signOut({ callbackUrl: '/login' }) From 82842bbdfbae262ca03e425eb26777db933b8386 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Mon, 7 Oct 2024 16:44:39 +0300 Subject: [PATCH 2/2] add the same for schema and disabled edit properties for ro --- app/components/EditorComponent.tsx | 8 ++++---- app/graph/GraphDataPanel.tsx | 6 +++--- app/graph/GraphView.tsx | 2 +- app/graph/Selector.tsx | 4 ++-- app/graph/page.tsx | 6 +++--- app/schema/SchemaDataPanel.tsx | 16 ++++++++++------ app/schema/SchemaView.tsx | 12 ++++++++---- app/schema/page.tsx | 11 +++++++---- 8 files changed, 38 insertions(+), 27 deletions(-) diff --git a/app/components/EditorComponent.tsx b/app/components/EditorComponent.tsx index d9fbdfe..8ec7cf5 100644 --- a/app/components/EditorComponent.tsx +++ b/app/components/EditorComponent.tsx @@ -274,7 +274,7 @@ export default function EditorComponent({ currentQuery, historyQueries, setCurre const addLabelsSuggestions = async (sug: monaco.languages.CompletionItem[]) => { const labelsQuery = `CALL db.labels()` - await securedFetch(`api/graph/${prepareArg(graph.Id)}/?query=${prepareArg(labelsQuery)}&role=${data!.user.role}`, { + await securedFetch(`api/graph/${prepareArg(graph.Id)}/?query=${prepareArg(labelsQuery)}&role=${data?.user.role}`, { method: "GET" }).then((res) => res.json()).then((json) => { json.result.data.forEach(({ label }: { label: string }) => { @@ -292,7 +292,7 @@ export default function EditorComponent({ currentQuery, historyQueries, setCurre const addRelationshipTypesSuggestions = async (sug: monaco.languages.CompletionItem[]) => { const relationshipTypeQuery = `CALL db.relationshipTypes()` - await securedFetch(`api/graph/${prepareArg(graph.Id)}/?query=${prepareArg(relationshipTypeQuery)}&role=${data!.user.role}`, { + await securedFetch(`api/graph/${prepareArg(graph.Id)}/?query=${prepareArg(relationshipTypeQuery)}&role=${data?.user.role}`, { method: "GET" }).then((res) => res.json()).then((json) => { json.result.data.forEach(({ relationshipType }: { relationshipType: string }) => { @@ -310,7 +310,7 @@ export default function EditorComponent({ currentQuery, historyQueries, setCurre const addPropertyKeysSuggestions = async (sug: monaco.languages.CompletionItem[]) => { const propertyKeysQuery = `CALL db.propertyKeys()` - await securedFetch(`api/graph/${prepareArg(graph.Id)}/?query=${prepareArg(propertyKeysQuery)}&role=${data!.user.role}`, { + await securedFetch(`api/graph/${prepareArg(graph.Id)}/?query=${prepareArg(propertyKeysQuery)}&role=${data?.user.role}`, { method: "GET" }).then((res) => res.json()).then((json) => { json.result.data.forEach(({ propertyKey }: { propertyKey: string }) => { @@ -327,7 +327,7 @@ export default function EditorComponent({ currentQuery, historyQueries, setCurre const addFunctionsSuggestions = async (functions: monaco.languages.CompletionItem[]) => { const proceduresQuery = `CALL dbms.procedures() YIELD name` - await securedFetch(`api/graph/${prepareArg(graph.Id)}/?query=${prepareArg(proceduresQuery)}&role=${data!.user.role}`, { + await securedFetch(`api/graph/${prepareArg(graph.Id)}/?query=${prepareArg(proceduresQuery)}&role=${data?.user.role}`, { method: "GET" }).then((res) => res.json()).then((json) => { [...json.result.data.map(({ name }: { name: string }) => name), ...FUNCTIONS].forEach((name: string) => { diff --git a/app/graph/GraphDataPanel.tsx b/app/graph/GraphDataPanel.tsx index 518c365..f2c0f25 100644 --- a/app/graph/GraphDataPanel.tsx +++ b/app/graph/GraphDataPanel.tsx @@ -119,7 +119,7 @@ export default function GraphDataPanel({ obj, setObj, onExpand, onDeleteElement,
{ - editable === key ? + editable === key && data?.user.role !== "Read-Only" ? ref?.focus()} variant="Small" @@ -205,7 +205,7 @@ export default function GraphDataPanel({ obj, setObj, onExpand, onDeleteElement, label="Add Value" icon={} onClick={() => setIsAddValue(true)} - disabled={data!.user.role === "Read-Only"} + disabled={data?.user.role === "Read-Only"} />
@@ -215,7 +215,7 @@ export default function GraphDataPanel({ obj, setObj, onExpand, onDeleteElement, icon={} label="Delete" onClick={onDeleteElement} - disabled={data!.user.role === "Read-Only"} + disabled={data?.user.role === "Read-Only"} /> diff --git a/app/graph/GraphView.tsx b/app/graph/GraphView.tsx index 3fb29ea..0bc1245 100644 --- a/app/graph/GraphView.tsx +++ b/app/graph/GraphView.tsx @@ -356,7 +356,7 @@ const GraphView = forwardRef(({ graph, selectedElement, setSelectedElement, runQ
{ if (runQuery) { const q = 'MATCH (n)-[e]-(m) return n,e,m' - const result = await securedFetch(`api/graph/${prepareArg(name)}_schema/?query=${prepareArg(q)}&create=false&role=${data!.user.role}`, { + const result = await securedFetch(`api/graph/${prepareArg(name)}_schema/?query=${prepareArg(q)}&create=false&role=${data?.user.role}`, { method: "GET" }) @@ -258,7 +258,7 @@ export default function Selector({ onChange, graphName, queries, runQuery, edges /> - +
diff --git a/app/graph/page.tsx b/app/graph/page.tsx index c12625a..e1e8eb5 100644 --- a/app/graph/page.tsx +++ b/app/graph/page.tsx @@ -26,11 +26,11 @@ export default function Page() { const q1 = "MATCH (n) RETURN COUNT(n) as nodes" const q2 = "MATCH ()-[e]->() RETURN COUNT(e) as edges" - const nodes = await (await securedFetch(`api/graph/${prepareArg(graphName)}/?query=${q1}&role=${data!.user.role}`, { + const nodes = await (await securedFetch(`api/graph/${prepareArg(graphName)}/?query=${q1}&role=${data?.user.role}`, { method: "GET" })).json() - const edges = await (await securedFetch(`api/graph/${prepareArg(graphName)}/?query=${q2}&role=${data!.user.role}`, { + const edges = await (await securedFetch(`api/graph/${prepareArg(graphName)}/?query=${q2}&role=${data?.user.role}`, { method: "GET" })).json() @@ -58,7 +58,7 @@ export default function Page() { return null } - const result = await securedFetch(`api/graph/${prepareArg(graphName)}/?query=${prepareArg(query)}&role=${data!.user.role}`, { + const result = await securedFetch(`api/graph/${prepareArg(graphName)}/?query=${prepareArg(query)}&role=${data?.user.role}`, { method: "GET" }) diff --git a/app/schema/SchemaDataPanel.tsx b/app/schema/SchemaDataPanel.tsx index 7453937..fb4cbbd 100644 --- a/app/schema/SchemaDataPanel.tsx +++ b/app/schema/SchemaDataPanel.tsx @@ -5,6 +5,7 @@ import { useEffect, useState } from "react"; import { cn, ElementDataDefinition, Toast } from "@/lib/utils"; import { ChevronRight, Trash2 } from "lucide-react"; import { Checkbox } from "@/components/ui/checkbox"; +import { Session } from "next-auth"; import Input from "../components/ui/Input"; import Button from "../components/ui/Button"; import Combobox from "../components/ui/combobox"; @@ -31,11 +32,12 @@ interface Props { onSetAttribute: (key: string, val: Attribute) => Promise onRemoveAttribute: (key: string) => Promise onSetCategory: (label: string) => Promise + data: Session | null } const emptyAttribute = (): Attribute => [undefined, "", false, false] -export default function SchemaCreateElement({ obj, onExpand, onDelete, onSetAttribute, onRemoveAttribute, onSetCategory }: Props) { +export default function SchemaCreateElement({ obj, onExpand, onDelete, onSetAttribute, onRemoveAttribute, onSetCategory, data }: Props) { const [attribute, setAttribute] = useState(emptyAttribute()) const [newVal, setVal] = useState("") @@ -199,6 +201,7 @@ export default function SchemaCreateElement({ obj, onExpand, onDelete, onSetAttr label="Add Attribute" variant="Secondary" onClick={() => setIsAddValue(prev => !prev)} + disabled={data?.user.role === "Read-Only"} /> @@ -224,7 +227,7 @@ export default function SchemaCreateElement({ obj, onExpand, onDelete, onSetAttr /> } { - editable === `${index}-key` ? + editable === `${index}-key` && data?.user.role !== "Read-Only" ? ref?.focus()} className="w-28" @@ -245,7 +248,7 @@ export default function SchemaCreateElement({ obj, onExpand, onDelete, onSetAttr { - editable === `${index}-0` ? + editable === `${index}-0` && data?.user.role !== "Read-Only" ? { @@ -276,7 +279,7 @@ export default function SchemaCreateElement({ obj, onExpand, onDelete, onSetAttr { - editable === `${index}-1` ? + editable === `${index}-1` && data?.user.role !== "Read-Only" ? ref?.focus()} className="w-28" @@ -294,7 +297,7 @@ export default function SchemaCreateElement({ obj, onExpand, onDelete, onSetAttr { - editable === `${index}-2` ? + editable === `${index}-2` && data?.user.role !== "Read-Only" ? ref?.focus()} className="h-6 w-6 border-[#57577B] data-[state=checked]:bg-[#57577B]" @@ -317,7 +320,7 @@ export default function SchemaCreateElement({ obj, onExpand, onDelete, onSetAttr { - editable === `${index}-3` ? + editable === `${index}-3` && data?.user.role !== "Read-Only" ? ref?.focus()} className="h-6 w-6 border-[#57577B] data-[state=checked]:bg-[#57577B]" @@ -410,6 +413,7 @@ export default function SchemaCreateElement({ obj, onExpand, onDelete, onSetAttr label="Delete" variant="Secondary" onClick={onDelete} + disabled={data?.user.role === "Read-Only"} /> diff --git a/app/schema/SchemaView.tsx b/app/schema/SchemaView.tsx index 0adf7a0..c6df1af 100644 --- a/app/schema/SchemaView.tsx +++ b/app/schema/SchemaView.tsx @@ -8,6 +8,7 @@ import { ImperativePanelHandle } from "react-resizable-panels" import { useEffect, useRef, useState } from "react" import fcose from "cytoscape-fcose"; import { ElementDataDefinition, Toast, cn, prepareArg, securedFetch } from "@/lib/utils" +import { Session } from "next-auth" import Toolbar from "../graph/toolbar" import SchemaDataPanel, { Attribute } from "./SchemaDataPanel" import Labels from "../graph/labels" @@ -19,6 +20,7 @@ import CreateElement from "./SchemaCreateElement" interface Props { schema: Graph fetchCount?: () => void + data: Session | null } const LAYOUT = { @@ -104,7 +106,7 @@ const getCreateQuery = (type: boolean, selectedNodes: NodeDataDefinition[], attr return `MATCH (a), (b) WHERE ID(a) = ${selectedNodes[0].id} AND ID(b) = ${selectedNodes[1].id} CREATE (a)-[e${label ? `:${label}` : ""}${attributes?.length > 0 ? ` {${attributes.map(([k, [t, d, u, un]]) => `${k}: ["${t}", "${d}", "${u}", "${un}"]`).join(",")}}` : ""}]->(b) RETURN e` } -export default function SchemaView({ schema, fetchCount }: Props) { +export default function SchemaView({ schema, fetchCount, data }: Props) { const [selectedElement, setSelectedElement] = useState(); const [selectedElements, setSelectedElements] = useState([]); const [selectedNodes, setSelectedNodes] = useState([]); @@ -362,11 +364,11 @@ export default function SchemaView({ schema, fetchCount }: Props) { })).ok if (success) { - schema.Elements.forEach(({ data }) => { - if (data.id !== id) return + schema.Elements.forEach(({ data: d }) => { + if (d.id !== id) return // eslint-disable-next-line no-param-reassign - data.category = category + d.category = category setSelectedElement({ ...selectedElement, category }) @@ -471,6 +473,7 @@ export default function SchemaView({ schema, fetchCount }: Props) { }} onDeleteElement={handelDeleteElement} chartRef={chartRef} + addDisabled={data?.user.role === "Read-Only"} /> { isCollapsed && @@ -552,6 +555,7 @@ export default function SchemaView({ schema, fetchCount }: Props) { onSetAttribute={handelSetAttribute} onDelete={handelDeleteElement} onSetCategory={handelSetCategory} + data={data} /> : (isAddEntity || isAddRelation) && (Graph.empty()) const [edgesCount, setEdgesCount] = useState(0) const [nodesCount, setNodesCount] = useState(0) + const { data } = useSession() const fetchCount = useCallback(async () => { const name = `${schemaName}_schema` const q1 = "MATCH (n) RETURN COUNT(n) as nodes" const q2 = "MATCH ()-[e]->() RETURN COUNT(e) as edges" - const nodes = await (await securedFetch(`api/graph/${prepareArg(name)}/?query=${q1}`, { + const nodes = await (await securedFetch(`api/graph/${prepareArg(name)}/?query=${q1}&role=${data?.user.role}`, { method: "GET" })).json() - const edges = await (await securedFetch(`api/graph/${prepareArg(name)}/?query=${q2}`, { + const edges = await (await securedFetch(`api/graph/${prepareArg(name)}/?query=${q2}&role=${data?.user.role}`, { method: "GET" })).json() @@ -36,7 +38,7 @@ export default function Page() { useEffect(() => { if (!schemaName) return const run = async () => { - const result = await securedFetch(`/api/graph/${prepareArg(schemaName)}_schema/?query=${defaultQuery()}`, { + const result = await securedFetch(`/api/graph/${prepareArg(schemaName)}_schema/?query=${defaultQuery()}&role=${data?.user.role}`, { method: "GET" }) if (!result.ok) { @@ -64,8 +66,9 @@ export default function Page() { graphName={schemaName} graph={schema} setGraph={setSchema} + data={data} /> - + )