From 34a65efd2c9ed5129c3e100725b9064f1fd08129 Mon Sep 17 00:00:00 2001 From: dvirlabs Date: Fri, 13 Jun 2025 17:24:23 +0300 Subject: [PATCH] Update add node --- backend/__pycache__/main.cpython-313.pyc | Bin 1844 -> 2523 bytes backend/main.py | 49 +++++++++++++++++++ frontend/src/components/Diagram.jsx | 34 +++++++++++-- frontend/src/components/IconSelector.jsx | 58 +++++++++++++++++++++++ frontend/src/services/api.js | 5 ++ 5 files changed, 143 insertions(+), 3 deletions(-) create mode 100644 frontend/src/components/IconSelector.jsx diff --git a/backend/__pycache__/main.cpython-313.pyc b/backend/__pycache__/main.cpython-313.pyc index d66e1ade9a6ecb61092dd553e8854b189539e501..7e71e58d51f3475d2310807af3966672fab5354f 100644 GIT binary patch delta 1113 zcmZ`%&uRz4Sk*LUU-kQPfuIwNj{0z4gr+D4=$v`R3bizj^cC zy!po1Oc|X-LIWIM4NhMiQSKW3^eDMIFwwBVR?fse(wLTm0(lF@gb4@J3;OJ?E`JpZ zvaMRgj)W*!^oWuJJH`^W#thuE?^Sn~+xi%J&f?jBPuPj>#)1E^Ctli(f_jMBJF+k@ zzf3G?fxYuALF_q8?BpR@ui?REjOiMoyy$>nt z6_9SFm4idO1?@)&h|48hbGaN7_Zh^wQ+JDHqHk(ns%Y0WDS8q!Ln;`zh8URjAD zah;HA4y)8kWx-8!R2?45DkdhJkuOI>!K5p(vRfbWYqbR)7+&=PbGXlzOa3x*S9&aw zGq=7jIlfz|vCCJmT7$WK301eaSSz~=jUf8sZ|QlrFg1WEPLY47j&KjoBX-BEP~RAH zV8Iyeid8a4Qt%n&)B;XZaJmPeO3%wLwisZOIS7#&FxpT)r5~pNbjYE-O5aB8vI}6+ znSfZP6lkK3qE{`umjbEc`a$%>arBG+y$)HVb sTLY?G&s@!{#r8Zr_xQl0&zqSw^1(KxWU4K70GEygbo`kR*d!+L4;#?(c>n+a delta 364 zcmcaDyoHbNGcPX}0}$x*`(#XIp2#P`bdO=8hK)lEqY{HDNCF6oIAb`2L?LWsGKM9X zRi81K4NQVq?3Szw48a^l+%cRJ8$@J+IZYWM>VvtY5T@}0O_QCtO->?)&4ekKSBfEv z1#T06I*TUX&-GV^{$ZV@1}NEk$jPM*uEt}6v%iUWyT zjH$)UKw3dTp-2M6=P6DsON~#-OiV9I%q`LY3Q0}=$U2`TX5hZfz&Tl-(};;zdU6D(E!RgTHb$EwL!e3k0zXCz diff --git a/backend/main.py b/backend/main.py index 0143699..fb05c7b 100644 --- a/backend/main.py +++ b/backend/main.py @@ -1,8 +1,12 @@ from fastapi import FastAPI, HTTPException from fastapi.middleware.cors import CORSMiddleware +import uvicorn from models import DiagramItem import json import os +import requests +import xml.etree.ElementTree as ET +from typing import List, Dict app = FastAPI() @@ -14,8 +18,11 @@ app.add_middleware( allow_headers=["*"], ) +BASE_URL = "https://s3.dvirlabs.com/lab-icons" +S3_INDEX_URL = "https://s3.dvirlabs.com/lab-icons/?list-type=2" DATA_FILE = "diagram.json" + @app.get("/diagram/fetch") def fetch_diagram(): if not os.path.exists(DATA_FILE): @@ -23,6 +30,7 @@ def fetch_diagram(): with open(DATA_FILE, "r") as f: return json.load(f) + @app.post("/diagram/save") def save_diagram(payload: DiagramItem): try: @@ -31,3 +39,44 @@ def save_diagram(payload: DiagramItem): return {"status": "ok"} except Exception as e: raise HTTPException(status_code=500, detail=str(e)) + + +@app.get("/icons", response_model=Dict[str, List[str]]) +def list_icons(): + """ + Returns a dictionary of available icons grouped by folder (category). + Example: + { + "dev-tools": [ "https://s3.dvirlabs.com/lab-icons/dev-tools/gitea.svg", ... ], + "observability": [ ... ] + } + """ + resp = requests.get(S3_INDEX_URL) + if resp.status_code != 200: + raise HTTPException(status_code=500, detail="Failed to fetch icon list from S3") + + root = ET.fromstring(resp.content) + categories: Dict[str, List[str]] = {} + + for content in root.findall(".//{http://s3.amazonaws.com/doc/2006-03-01/}Contents"): + key = content.find("{http://s3.amazonaws.com/doc/2006-03-01/}Key").text + if not key.endswith(".svg"): + continue + parts = key.split('/') + if len(parts) == 2: + category, icon = parts + elif len(parts) > 2: + category = parts[0] + icon = parts[-1] + else: + category = "uncategorized" + icon = parts[0] + + url = f"{BASE_URL}/{key}" + categories.setdefault(category, []).append(url) + + return categories + + +if __name__ == "__main__": + uvicorn.run(app, host="0.0.0.0", port=8000) diff --git a/frontend/src/components/Diagram.jsx b/frontend/src/components/Diagram.jsx index bc67b8d..030608e 100644 --- a/frontend/src/components/Diagram.jsx +++ b/frontend/src/components/Diagram.jsx @@ -1,3 +1,4 @@ +// src/components/Diagram.jsx import { useEffect, useState, useCallback } from 'react'; import ReactFlow, { addEdge, @@ -9,6 +10,7 @@ import ReactFlow, { import 'reactflow/dist/style.css'; import { fetchDiagram, saveDiagram } from '../services/api'; import CustomNode from './CustomNode'; +import IconSelector from './IconSelector'; const nodeTypes = { custom: CustomNode, @@ -18,6 +20,9 @@ function Diagram() { const [nodes, setNodes, onNodesChange] = useNodesState([]); const [edges, setEdges, onEdgesChange] = useEdgesState([]); const [selectedNode, setSelectedNode] = useState(null); + const [showForm, setShowForm] = useState(false); + const [newLabel, setNewLabel] = useState(''); + const [selectedIcon, setSelectedIcon] = useState(''); useEffect(() => { fetchDiagram().then((data) => { @@ -44,9 +49,13 @@ function Diagram() { }; const handleAddNode = () => { + setShowForm(true); + }; + + const handleSubmitNode = () => { const id = (nodes.length + 1).toString(); - const icon = prompt('Enter icon URL:', 'https://s3.dvirlabs.com/lab-icons/default.svg') || 'https://s3.dvirlabs.com/lab-icons/default.svg'; - const label = prompt('Enter label:', `Node ${id}`) || `Node ${id}`; + const label = newLabel || `Node ${id}`; + const icon = selectedIcon || 'https://s3.dvirlabs.com/lab-icons/default.svg'; const newNode = { id, @@ -56,6 +65,9 @@ function Diagram() { }; setNodes((nds) => [...nds, newNode]); + setShowForm(false); + setNewLabel(''); + setSelectedIcon(''); }; const handleDeleteNode = () => { @@ -93,6 +105,22 @@ function Diagram() { + {showForm && ( +
+

Add Node

+ setNewLabel(e.target.value)} + style={{ marginBottom: 8, display: 'block', width: '100%' }} + /> + + + +
+ )} + { + fetchIconCategories().then(setCategories); + }, []); + + const handleCategoryChange = (e) => { + const category = e.target.value; + setSelectedCategory(category); + setSelectedIcon(''); + onSelect(''); + }; + + const handleIconChange = (e) => { + const icon = e.target.value; + setSelectedIcon(icon); + onSelect(icon); + }; + + return ( +
+ + + + {selectedCategory && ( + <> + + + {selectedIcon && ( + preview + )} + + )} +
+ ); +} + +export default IconSelector; diff --git a/frontend/src/services/api.js b/frontend/src/services/api.js index 252e06e..55f84e7 100644 --- a/frontend/src/services/api.js +++ b/frontend/src/services/api.js @@ -13,3 +13,8 @@ export async function saveDiagram(data) { }); return await res.json(); } + +export async function fetchIconCategories() { + const res = await fetch(`${API_BASE}/icons`); + return await res.json(); +}