feat: enhance workflow tree view

This commit is contained in:
2025-08-10 01:28:23 -06:00
parent d040b4c742
commit 916457767e
2 changed files with 61 additions and 17 deletions

View File

@@ -22,12 +22,37 @@ import {
} from "@mui/x-tree-view"; } from "@mui/x-tree-view";
import { RenameWorkflowDialog, RenameFolderDialog } from "./Dialogs"; import { RenameWorkflowDialog, RenameFolderDialog } from "./Dialogs";
function buildTree(workflows) { function buildTree(workflows, folders) {
const map = {}; const map = {};
const root = []; const rootNode = {
id: "root",
label: "Workflows",
path: "",
isFolder: true,
children: []
};
map[rootNode.id] = rootNode;
(folders || []).forEach((f) => {
const parts = (f || "").split("/");
let children = rootNode.children;
let parentPath = "";
parts.forEach((part) => {
const path = parentPath ? `${parentPath}/${part}` : part;
let node = children.find((n) => n.id === path);
if (!node) {
node = { id: path, label: part, path, isFolder: true, children: [] };
children.push(node);
map[path] = node;
}
children = node.children;
parentPath = path;
});
});
(workflows || []).forEach((w) => { (workflows || []).forEach((w) => {
const parts = (w.rel_path || "").split("/"); const parts = (w.rel_path || "").split("/");
let children = root; let children = rootNode.children;
let parentPath = ""; let parentPath = "";
parts.forEach((part, idx) => { parts.forEach((part, idx) => {
const path = parentPath ? `${parentPath}/${part}` : part; const path = parentPath ? `${parentPath}/${part}` : part;
@@ -56,7 +81,8 @@ function buildTree(workflows) {
} }
}); });
}); });
return { root, map };
return { root: [rootNode], map };
} }
export default function WorkflowList({ onOpenWorkflow }) { export default function WorkflowList({ onOpenWorkflow }) {
@@ -96,7 +122,7 @@ export default function WorkflowList({ onOpenWorkflow }) {
const resp = await fetch("/api/storage/load_workflows"); const resp = await fetch("/api/storage/load_workflows");
if (!resp.ok) throw new Error(`HTTP ${resp.status}`); if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
const data = await resp.json(); const data = await resp.json();
const { root, map } = buildTree(data.workflows || []); const { root, map } = buildTree(data.workflows || [], data.folders || []);
setTree(root); setTree(root);
setNodeMap(map); setNodeMap(map);
} catch (err) { } catch (err) {
@@ -126,6 +152,13 @@ export default function WorkflowList({ onOpenWorkflow }) {
else setRenameOpen(true); else setRenameOpen(true);
}; };
const handleEdit = () => {
closeMenu();
if (selectedNode && !selectedNode.isFolder && onOpenWorkflow) {
onOpenWorkflow(selectedNode.workflow);
}
};
const handleDeleteWorkflow = async () => { const handleDeleteWorkflow = async () => {
closeMenu(); closeMenu();
if (!selectedNode) return; if (!selectedNode) return;
@@ -212,9 +245,9 @@ export default function WorkflowList({ onOpenWorkflow }) {
}} }}
> >
{n.isFolder ? ( {n.isFolder ? (
<FolderIcon sx={{ mr: 1, color: "#ffd54f" }} /> <FolderIcon sx={{ mr: 1, color: "#0475c2" }} />
) : ( ) : (
<DescriptionIcon sx={{ mr: 1, color: "#90caf9" }} /> <DescriptionIcon sx={{ mr: 1, color: "#0475c2" }} />
)} )}
<Typography sx={{ flexGrow: 1, color: "#e6edf3" }}>{n.label}</Typography> <Typography sx={{ flexGrow: 1, color: "#e6edf3" }}>{n.label}</Typography>
<IconButton <IconButton
@@ -243,7 +276,7 @@ export default function WorkflowList({ onOpenWorkflow }) {
}} }}
> >
<Box> <Box>
<Typography variant="h6" sx={{ color: "#58a6ff", mb: 0 }}> <Typography variant="h6" sx={{ color: "#0475c2", mb: 0 }}>
Workflows Workflows
</Typography> </Typography>
<Typography variant="body2" sx={{ color: "#aaa" }}> <Typography variant="body2" sx={{ color: "#aaa" }}>
@@ -255,10 +288,10 @@ export default function WorkflowList({ onOpenWorkflow }) {
startIcon={<CreateNewFolderIcon />} startIcon={<CreateNewFolderIcon />}
sx={{ sx={{
mr: 1, mr: 1,
color: "#58a6ff", color: "#0475c2",
borderColor: "#58a6ff", borderColor: "#0475c2",
textTransform: "none", textTransform: "none",
border: "1px solid #58a6ff", border: "1px solid #0475c2",
backgroundColor: "#1e1e1e", backgroundColor: "#1e1e1e",
"&:hover": { backgroundColor: "#1b1b1b" } "&:hover": { backgroundColor: "#1b1b1b" }
}} }}
@@ -273,10 +306,10 @@ export default function WorkflowList({ onOpenWorkflow }) {
<Button <Button
startIcon={<PlayCircleIcon />} startIcon={<PlayCircleIcon />}
sx={{ sx={{
color: "#58a6ff", color: "#0475c2",
borderColor: "#58a6ff", borderColor: "#0475c2",
textTransform: "none", textTransform: "none",
border: "1px solid #58a6ff", border: "1px solid #0475c2",
backgroundColor: "#1e1e1e", backgroundColor: "#1e1e1e",
"&:hover": { backgroundColor: "#1b1b1b" } "&:hover": { backgroundColor: "#1b1b1b" }
}} }}
@@ -300,6 +333,7 @@ export default function WorkflowList({ onOpenWorkflow }) {
sx={{ color: "#e6edf3" }} sx={{ color: "#e6edf3" }}
onNodeSelect={handleNodeSelect} onNodeSelect={handleNodeSelect}
apiRef={apiRef} apiRef={apiRef}
defaultExpanded={["root"]}
> >
{renderItems(tree)} {renderItems(tree)}
</SimpleTreeView> </SimpleTreeView>
@@ -313,9 +347,14 @@ export default function WorkflowList({ onOpenWorkflow }) {
{selectedNode?.isFolder && ( {selectedNode?.isFolder && (
<MenuItem onClick={handleCreateFolder}>New Folder</MenuItem> <MenuItem onClick={handleCreateFolder}>New Folder</MenuItem>
)} )}
{selectedNode && selectedNode.id !== "root" && (
<MenuItem onClick={handleRename}>Rename</MenuItem> <MenuItem onClick={handleRename}>Rename</MenuItem>
)}
{!selectedNode?.isFolder && ( {!selectedNode?.isFolder && (
<>
<MenuItem onClick={handleEdit}>Edit</MenuItem>
<MenuItem onClick={handleDeleteWorkflow}>Delete</MenuItem> <MenuItem onClick={handleDeleteWorkflow}>Delete</MenuItem>
</>
)} )}
</Menu> </Menu>
<RenameWorkflowDialog <RenameWorkflowDialog

View File

@@ -200,6 +200,7 @@ def load_workflows():
os.path.join(os.path.dirname(__file__), "..", "..", "Workflows") os.path.join(os.path.dirname(__file__), "..", "..", "Workflows")
) )
results: List[Dict] = [] results: List[Dict] = []
folders: List[str] = []
if not os.path.isdir(workflows_root): if not os.path.isdir(workflows_root):
return jsonify({ return jsonify({
@@ -209,6 +210,9 @@ def load_workflows():
}), 200 }), 200
for root, dirs, files in os.walk(workflows_root): for root, dirs, files in os.walk(workflows_root):
rel_root = os.path.relpath(root, workflows_root)
if rel_root != ".":
folders.append(rel_root.replace(os.sep, "/"))
for fname in files: for fname in files:
if not fname.lower().endswith(".json"): if not fname.lower().endswith(".json"):
continue continue
@@ -246,7 +250,8 @@ def load_workflows():
return jsonify({ return jsonify({
"root": workflows_root, "root": workflows_root,
"workflows": results "workflows": results,
"folders": folders
}) })