From fce8b7c91187efecf48d7924b36633e49e2e10fc Mon Sep 17 00:00:00 2001 From: Nicole Rappe Date: Fri, 28 Mar 2025 21:17:56 -0600 Subject: [PATCH] Initial Commit --- .gitignore | 2 + Data/WebUI/public/index.html | 43 ++++++ Data/WebUI/src/App.js | 139 +++++++++++++++++ Data/WebUI/src/components/FlowEditor.css | 23 +++ Data/WebUI/src/components/FlowEditor.jsx | 68 +++++++++ Data/server.py | 131 ++++++++++++++++ Launch-Borealis.ps1 | 149 ++++++++++++++++++ Launch-Borealis.sh | 185 +++++++++++++++++++++++ requirements.txt | 21 +++ 9 files changed, 761 insertions(+) create mode 100644 .gitignore create mode 100644 Data/WebUI/public/index.html create mode 100644 Data/WebUI/src/App.js create mode 100644 Data/WebUI/src/components/FlowEditor.css create mode 100644 Data/WebUI/src/components/FlowEditor.jsx create mode 100644 Data/server.py create mode 100644 Launch-Borealis.ps1 create mode 100644 Launch-Borealis.sh create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f417792 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.vs/ +Borealis-Workflow-Automation-Tool/ \ No newline at end of file diff --git a/Data/WebUI/public/index.html b/Data/WebUI/public/index.html new file mode 100644 index 0000000..da117a9 --- /dev/null +++ b/Data/WebUI/public/index.html @@ -0,0 +1,43 @@ + + + + + + + + + + + + + Borealis + + + +
+ + + diff --git a/Data/WebUI/src/App.js b/Data/WebUI/src/App.js new file mode 100644 index 0000000..a26f26b --- /dev/null +++ b/Data/WebUI/src/App.js @@ -0,0 +1,139 @@ +import React from "react"; +import FlowEditor from "./components/FlowEditor"; +import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; +import { + AppBar, + Toolbar, + Typography, + Box, + Menu, + MenuItem, + Button, + CssBaseline, + ThemeProvider, + createTheme +} from "@mui/material"; + +const darkTheme = createTheme({ + palette: { + mode: "dark", + background: { + default: "#121212", + paper: "#1e1e1e" + }, + text: { + primary: "#ffffff" + } + } +}); + +export default function App() { + const [workflowsAnchorEl, setWorkflowsAnchorEl] = React.useState(null); + const [aboutAnchorEl, setAboutAnchorEl] = React.useState(null); + + const handleWorkflowsMenuOpen = (event) => { + setWorkflowsAnchorEl(event.currentTarget); + }; + + const handleAboutMenuOpen = (event) => { + setAboutAnchorEl(event.currentTarget); + }; + + const handleWorkflowsMenuClose = () => { + setWorkflowsAnchorEl(null); + }; + + const handleAboutMenuClose = () => { + setAboutAnchorEl(null); + }; + + return ( + + + {/* + Main container that: + - fills 100% viewport height + - organizes content with flexbox (vertical) + */} + + {/* --- TOP BAR --- */} + + + + Borealis - Workflow Automation Tool + + + {/* Workflows Menu */} + + + Save Workflow + Load Workflow + Close Workflow + + + {/* About Menu */} + + + Gitea Project + Credits + + + + + {/* --- REACT FLOW EDITOR --- */} + {/* + flexGrow={1} ⇒ This box expands to fill remaining vertical space + overflow="hidden" ⇒ No scroll bars, so React Flow does internal panning + mt: 1 ⇒ Add top margin so the gradient starts closer to the AppBar. + */} + + { + document.getElementById("nodeCount").innerText = count; + }} + /> + + + {/* --- STATUS BAR at BOTTOM --- */} + + Nodes: 0 | Update Rate: 500ms | Flask API Server:{" "} + + http://127.0.0.1:5000/data/api/nodes + + + + + ); +} diff --git a/Data/WebUI/src/components/FlowEditor.css b/Data/WebUI/src/components/FlowEditor.css new file mode 100644 index 0000000..4990c4f --- /dev/null +++ b/Data/WebUI/src/components/FlowEditor.css @@ -0,0 +1,23 @@ +/* FlowEditor background container */ +.flow-editor-container { + position: relative; + width: 100vw; + height: 100vh; +} + + /* Blue Gradient Overlay */ + .flow-editor-container::before { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + pointer-events: none; /* Ensures grid and nodes remain fully interactive */ + background: linear-gradient( to bottom, rgba(9, 44, 68, 0.9) 0%, /* Deep blue at the top */ + rgba(30, 30, 30, 0) 45%, /* Fade out towards center */ + rgba(30, 30, 30, 0) 75%, /* No gradient in the middle */ + rgba(9, 44, 68, 0.7) 100% /* Deep blue at the bottom */ + ); + z-index: -1; /* Ensures it stays behind the React Flow elements */ + } diff --git a/Data/WebUI/src/components/FlowEditor.jsx b/Data/WebUI/src/components/FlowEditor.jsx new file mode 100644 index 0000000..4c72a61 --- /dev/null +++ b/Data/WebUI/src/components/FlowEditor.jsx @@ -0,0 +1,68 @@ +import React, { useState, useEffect, useCallback } from "react"; +import ReactFlow, { + addEdge, + Controls, + Background, +} from "reactflow"; +import "reactflow/dist/style.css"; +import "./FlowEditor.css"; + +const fetchNodes = async () => { + const response = await fetch("/api/workflow"); + return response.json(); +}; + +const saveWorkflow = async (workflow) => { + await fetch("/api/workflow", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(workflow), + }); +}; + +export default function FlowEditor() { + const [elements, setElements] = useState([]); + + useEffect(() => { + fetchNodes().then((data) => { + // Data should contain nodes and edges arrays + const newElements = [...data.nodes, ...data.edges]; + setElements(newElements); + }); + }, []); + + const onConnect = useCallback( + (params) => { + const newEdge = { id: `e${params.source}-${params.target}`, ...params }; + setElements((els) => [...els, newEdge]); + + // Separate nodes/edges for saving: + const nodes = elements.filter((el) => el.type); + const edges = elements.filter((el) => !el.type); + + saveWorkflow({ + nodes, + edges: [...edges, newEdge], + }); + }, + [elements] + ); + + return ( +
+ + + + +
+ ); +} diff --git a/Data/server.py b/Data/server.py new file mode 100644 index 0000000..377227e --- /dev/null +++ b/Data/server.py @@ -0,0 +1,131 @@ +from flask import Flask, send_from_directory, jsonify, request, abort +import os +import importlib +import inspect +import uuid +from OdenGraphQt import BaseNode + +# Determine the absolute path for the React build folder +build_folder = os.path.join(os.getcwd(), "web-interface", "build") +if not os.path.exists(build_folder): + print("WARNING: web-interface build folder not found. Please build your React app.") + +app = Flask(__name__, static_folder=build_folder, static_url_path="/") + +# Directory where nodes are stored +NODES_PACKAGE = "Nodes" + +# In-memory workflow storage +workflow_data = { + "nodes": [], + "edges": [] # Store connections separately +} + +def import_nodes_from_folder(package_name): + """Dynamically import node classes from the given package and list them.""" + nodes_by_category = {} + package = importlib.import_module(package_name) + package_path = package.__path__[0] + + for root, _, files in os.walk(package_path): + rel_path = os.path.relpath(root, package_path).replace(os.sep, ".") + module_prefix = f"{package_name}.{rel_path}" if rel_path != "." else package_name + category_name = os.path.basename(root) + + for file in files: + if file.endswith(".py") and file != "__init__.py": + module_name = f"{module_prefix}.{file[:-3]}" + try: + module = importlib.import_module(module_name) + for name, obj in inspect.getmembers(module, inspect.isclass): + if issubclass(obj, BaseNode) and obj.__module__ == module.__name__: + if category_name not in nodes_by_category: + nodes_by_category[category_name] = [] + nodes_by_category[category_name].append(obj.NODE_NAME) + except Exception as e: + print(f"Failed to import {module_name}: {e}") + + return nodes_by_category + +@app.route("/") +def serve_frontend(): + """Serve the React app.""" + index_path = os.path.join(build_folder, "index.html") + if os.path.exists(index_path): + return send_from_directory(app.static_folder, "index.html") + return "

Borealis React App Code Not Found

Please re-deploy Borealis Workflow Automation Tool

", 404 + +@app.route("/api/nodes", methods=["GET"]) +def get_available_nodes(): + """Return available node types.""" + nodes = import_nodes_from_folder(NODES_PACKAGE) + return jsonify(nodes) + +@app.route("/api/workflow", methods=["GET", "POST"]) +def handle_workflow(): + """Retrieve or update the workflow.""" + global workflow_data + if request.method == "GET": + return jsonify(workflow_data) + elif request.method == "POST": + data = request.get_json() + if not data: + abort(400, "Invalid workflow data") + workflow_data = data + return jsonify({"status": "success", "workflow": workflow_data}) + +@app.route("/api/node", methods=["POST"]) +def create_node(): + """Create a new node with a unique UUID.""" + data = request.get_json() + if not data or "nodeType" not in data: + abort(400, "Invalid node data") + + node_id = str(uuid.uuid4()) # Generate a unique ID + node = { + "id": node_id, + "type": data["nodeType"], + "position": data.get("position", {"x": 100, "y": 100}), + "properties": data.get("properties", {}) + } + workflow_data["nodes"].append(node) + return jsonify({"status": "success", "node": node}) + +@app.route("/api/node/", methods=["PUT", "DELETE"]) +def modify_node(node_id): + """Update or delete a node.""" + global workflow_data + if request.method == "PUT": + data = request.get_json() + for node in workflow_data["nodes"]: + if node["id"] == node_id: + node["position"] = data.get("position", node["position"]) + node["properties"] = data.get("properties", node["properties"]) + return jsonify({"status": "success", "node": node}) + abort(404, "Node not found") + + elif request.method == "DELETE": + workflow_data["nodes"] = [n for n in workflow_data["nodes"] if n["id"] != node_id] + return jsonify({"status": "success", "deletedNode": node_id}) + +@app.route("/api/edge", methods=["POST"]) +def create_edge(): + """Create a new connection (edge) between nodes.""" + data = request.get_json() + if not data or "source" not in data or "target" not in data: + abort(400, "Invalid edge data") + + edge_id = str(uuid.uuid4()) + edge = {"id": edge_id, "source": data["source"], "target": data["target"]} + workflow_data["edges"].append(edge) + return jsonify({"status": "success", "edge": edge}) + +@app.route("/api/edge/", methods=["DELETE"]) +def delete_edge(edge_id): + """Delete an edge by ID.""" + global workflow_data + workflow_data["edges"] = [e for e in workflow_data["edges"] if e["id"] != edge_id] + return jsonify({"status": "success", "deletedEdge": edge_id}) + +if __name__ == "__main__": + app.run(host="0.0.0.0", port=5000, debug=False) diff --git a/Launch-Borealis.ps1 b/Launch-Borealis.ps1 new file mode 100644 index 0000000..36a0b1b --- /dev/null +++ b/Launch-Borealis.ps1 @@ -0,0 +1,149 @@ +# Start_Windows - WebServer.ps1 +# Run this script with: +# Set-ExecutionPolicy Unrestricted -Scope Process; .\Start_Windows -WebServer.ps1 + +# ---------------------- Initialization & Visuals ---------------------- +$symbols = @{ + Success = [char]0x2705 + Running = [char]0x23F3 + Fail = [char]0x274C + Info = [char]0x2139 +} + +function Write-ProgressStep { + param ( + [string]$Message, + [string]$Status = $symbols["Info"] # Ensure proper lookup + ) + Write-Host "`r$Status $Message... " -NoNewline +} + +function Run-Step { + param ( + [string]$Message, + [scriptblock]$Script + ) + Write-ProgressStep -Message $Message -Status "$($symbols.Running)" + try { + & $Script + if ($LASTEXITCODE -eq 0 -or $?) { + Write-Host "`r$($symbols.Success) $Message " # Fix symbol lookup + } else { + throw "Non-zero exit code" + } + } catch { + Write-Host "`r$($symbols.Fail) $Message - Failed: $_ " -ForegroundColor Red + exit 1 + } +} + +Clear-Host +Write-Host "Deploying Borealis - Workflow Automation Tool..." -ForegroundColor Green +Write-Host "====================================================================================" + +# ---------------------- Node.js Check ---------------------- +if (-not (Get-Command node -ErrorAction SilentlyContinue)) { + Write-Host "`r$($symbols.Fail) Node.js is not installed. Please install Node.js and try again." -ForegroundColor Red + exit 1 +} + +# ---------------------- Path Definitions ---------------------- +$venvFolder = "Borealis-Workflow-Automation-Tool" +$dataSource = "Data" +$dataDestination = "$venvFolder\Borealis" +$customUIPath = "$dataSource\WebUI" +$webUIDestination = "$venvFolder\web-interface" + +# ---------------------- Create Python Virtual Environment ---------------------- +Run-Step "Create Virtual Python Environment" { + if (!(Test-Path "$venvFolder\Scripts\Activate")) { + python -m venv $venvFolder | Out-Null + } +} + +# ---------------------- Copy Server Data ---------------------- +Run-Step "Copy Borealis Server Data into Virtual Python Environment" { + if (Test-Path $dataSource) { + if (Test-Path $dataDestination) { + Remove-Item -Recurse -Force $dataDestination | Out-Null + } + New-Item -Path $dataDestination -ItemType Directory -Force | Out-Null + Copy-Item -Path "$dataSource\*" -Destination $dataDestination -Recurse + } else { + Write-Host "`r$($symbols.Info) Warning: Data folder not found, skipping copy." -ForegroundColor Yellow + } +} + +# ---------------------- React UI Deployment ---------------------- +Run-Step "Create a new ReactJS App in $webUIDestination" { + if (-not (Test-Path $webUIDestination)) { + npx create-react-app $webUIDestination | Out-Null + } +} + +Run-Step "Overwrite ReactJS App Files with Borealis ReactJS Files" { + if (Test-Path $customUIPath) { + Copy-Item -Path "$customUIPath\*" -Destination $webUIDestination -Recurse -Force + } else { + Write-Host "`r$($symbols.Info) No custom UI found, using default React app." -ForegroundColor Yellow + } +} + +Run-Step "Remove Existing ReactJS Build Folder (If Exists)" { + if (Test-Path "$webUIDestination\build") { + Remove-Item -Path "$webUIDestination\build" -Recurse -Force + } +} + +# ---------------------- Activate Python Virtual Environment ---------------------- +Run-Step "Activate Virtual Python Environment" { + . "$venvFolder\Scripts\Activate" +} + +# ---------------------- Install Python Dependencies ---------------------- +Run-Step "Install Python Dependencies into Virtual Python Environment" { + if (Test-Path "requirements.txt") { + pip install -q -r requirements.txt 2>&1 | Out-Null + } else { + Write-Host "`r$($symbols.Info) No requirements.txt found, skipping Python packages." -ForegroundColor Yellow + } +} + +# ---------------------- Build React App ---------------------- +Run-Step "Install NPM into ReactJS App" { + $packageJsonPath = Join-Path $webUIDestination "package.json" + if (Test-Path $packageJsonPath) { + Push-Location $webUIDestination + $env:npm_config_loglevel = "silent" + npm install --silent --no-fund --audit=false 2>&1 | Out-Null + Pop-Location + } +} + +Run-Step "Install React Flow into ReactJS App" { + Push-Location $webUIDestination + npm install reactflow --no-fund --audit=false | Out-Null + Pop-Location +} + +Run-Step "Install Material UI Libraries into ReactJS App" { + Push-Location $webUIDestination + $env:npm_config_loglevel = "silent" # Force NPM to be completely silent + npm install --silent @mui/material @mui/icons-material @emotion/react @emotion/styled --no-fund --audit=false 2>&1 | Out-Null + Pop-Location +} + +Run-Step "Build ReactJS App" { + Push-Location $webUIDestination + npm run build | Out-Null + Pop-Location +} + +# ---------------------- Launch Flask Server ---------------------- +Push-Location $venvFolder +Write-Host "`nLaunching Borealis..." -ForegroundColor Green +Write-Host "====================================================================================" +Write-Host "$($symbols.Running) Starting the Python Flask server..." -NoNewline +python "Borealis\server.py" +Write-Host "`r$($symbols.Success) Borealis Launched Successfully!" +Pop-Location diff --git a/Launch-Borealis.sh b/Launch-Borealis.sh new file mode 100644 index 0000000..d6eb4c2 --- /dev/null +++ b/Launch-Borealis.sh @@ -0,0 +1,185 @@ +#!/usr/bin/env bash +# -------------------------------------------------------------------- +# Deploying Borealis - Workflow Automation Tool +# +# This script deploys the Borealis Workflow Automation Tool by: +# - Detecting the Linux distro and installing required system dependencies. +# - Creating a Python virtual environment. +# - Copying server data. +# - Setting up a React UI application. +# - Installing Python and Node dependencies. +# - Building the React app. +# - Launching the Flask server. +# +# Usage: +# chmod +x deploy_borealis.sh +# ./deploy_borealis.sh +# -------------------------------------------------------------------- + +# ---------------------- Initialization & Visuals ---------------------- +GREEN="\033[0;32m" +YELLOW="\033[1;33m" +RED="\033[0;31m" +RESET="\033[0m" +CHECKMARK="✅" +HOURGLASS="⏳" +CROSSMARK="❌" +INFO="ℹ️" + +# Function to run a step with progress visuals and error checking +run_step() { + local message="$1" + shift + echo -ne "${HOURGLASS} ${message}... " + if "$@"; then + echo -e "\r${CHECKMARK} ${message}" + else + echo -e "\r${CROSSMARK} ${message} - Failed${RESET}" + exit 1 + fi +} + +echo -e "${GREEN}Deploying Borealis - Workflow Automation Tool...${RESET}" +echo "====================================================================================" + +# ---------------------- Detect Linux Distribution ---------------------- +detect_distro() { + # This function detects the Linux distribution by sourcing /etc/os-release. + if [ -f /etc/os-release ]; then + . /etc/os-release + DISTRO_ID=$ID + else + DISTRO_ID="unknown" + fi + echo -e "${INFO} Detected OS: ${DISTRO_ID}" +} +detect_distro + +# ---------------------- Install System Dependencies ---------------------- +install_core_dependencies() { + # Install required packages based on detected Linux distribution. + case "$DISTRO_ID" in + ubuntu|debian) + sudo apt update -qq + sudo apt install -y python3 python3-venv python3-pip nodejs npm git curl + ;; + rhel|centos|fedora|rocky) + # For Fedora and similar distributions, the venv module is built-in so we omit python3-venv. + sudo dnf install -y python3 python3-pip nodejs npm git curl + ;; + arch) + sudo pacman -Sy --noconfirm python python-venv python-pip nodejs npm git curl + ;; + *) + echo -e "${RED}${CROSSMARK} Unsupported Linux distribution: ${DISTRO_ID}${RESET}" + exit 1 + ;; + esac +} +run_step "Install System Dependencies" install_core_dependencies + +# ---------------------- Path Setup ---------------------- +# Variables and path definitions +venvFolder="Borealis-Workflow-Automation-Tool" +dataSource="Data" +dataDestination="${venvFolder}/Borealis" +customUIPath="${dataSource}/WebUI" +webUIDestination="${venvFolder}/web-interface" + +# ---------------------- Create Python Virtual Environment ---------------------- +run_step "Create Virtual Python Environment" bash -c " + # Check if virtual environment already exists; if not, create one. + if [ ! -f '${venvFolder}/bin/activate' ]; then + python3 -m venv '${venvFolder}' + fi +" + +# ---------------------- Copy Borealis Data ---------------------- +run_step "Copy Borealis Server Data into Virtual Python Environment" bash -c " + # If the Data folder exists, remove any existing server data folder and copy fresh data. + if [ -d \"$dataSource\" ]; then + rm -rf \"$dataDestination\" + mkdir -p \"$dataDestination\" + cp -r \"$dataSource/\"* \"$dataDestination\" + else + echo -e \"\r${INFO} Warning: Data folder not found, skipping copy.${RESET}\" + fi + true +" + +# ---------------------- React UI Setup ---------------------- +run_step "Create a new ReactJS App in ${webUIDestination}" bash -c " + # Create a React app if the destination folder does not exist. + if [ ! -d \"$webUIDestination\" ]; then + # Set CI=true and add --loglevel=error to suppress funding and audit messages + CI=true npx create-react-app \"$webUIDestination\" --silent --use-npm --loglevel=error + fi +" + +run_step "Overwrite React App with Custom Files" bash -c " + # If custom UI files exist, copy them into the React app folder. + if [ -d \"$customUIPath\" ]; then + cp -r \"$customUIPath/\"* \"$webUIDestination\" + else + echo -e \"\r${INFO} No custom UI found, using default React app.${RESET}\" + fi + true +" + +run_step "Remove Existing React Build (if any)" bash -c " + # Remove the build folder if it exists to ensure a fresh build. + if [ -d \"$webUIDestination/build\" ]; then + rm -rf \"$webUIDestination/build\" + fi + true +" + +# ---------------------- Activate Python Virtual Environment ---------------------- +# Activate the Python virtual environment for subsequent commands. +source "${venvFolder}/bin/activate" + +# ---------------------- Install Python Dependencies ---------------------- +run_step "Install Python Dependencies into Virtual Python Environment" bash -c " + # Install Python packages if a requirements.txt file is present. + if [ -f \"requirements.txt\" ]; then + pip install -q -r requirements.txt + else + echo -e \"\r${INFO} No requirements.txt found, skipping Python packages.${RESET}\" + fi + true +" + +# ---------------------- Install Node Dependencies & Build React UI ---------------------- +run_step "Install React App Dependencies" bash -c " + # Install npm dependencies if package.json exists. + if [ -f \"$webUIDestination/package.json\" ]; then + cd \"$webUIDestination\" + # Add --loglevel=error to suppress npm's funding and audit messages + npm install --silent --no-fund --audit=false --loglevel=error + cd - + fi +" + +run_step "Install React Flow and UI Libraries" bash -c " + # Install additional React libraries. + cd \"$webUIDestination\" + npm install reactflow --silent --no-fund --audit=false --loglevel=error + npm install --silent @mui/material @mui/icons-material @emotion/react @emotion/styled --no-fund --audit=false --loglevel=error + cd - +" + +run_step "Build React App" bash -c " + # Build the React app to create production-ready files. + cd \"$webUIDestination\" + npm run build --silent --loglevel=error + cd - +" + +# ---------------------- Launch Flask Server ---------------------- +cd "${venvFolder}" +echo -e "\n${GREEN}Launching Borealis...${RESET}" +echo "====================================================================================" +echo -ne "${HOURGLASS} Starting Flask server... " +python3 Borealis/server.py +echo -e "\r${CHECKMARK} Borealis Launched Successfully!" + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..f0b52a9 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,21 @@ +# PyTorch and related deep learning libraries (GPU Supported Functionality) +torch --index-url https://download.pytorch.org/whl/cu121 +torchvision --index-url https://download.pytorch.org/whl/cu121 +torchaudio --index-url https://download.pytorch.org/whl/cu121 + +# Flask for API handling +Flask +requests + +# GUI-related dependencies (Qt for GUI components) +Qt.py +qtpy +OdenGraphQt +PyQt5 + +# Computer Vision & OCR dependencies +numpy # Numerical operations +opencv-python # Computer vision processing +pytesseract # OCR engine +easyocr # Deep-learning-based OCR +Pillow # Image processing \ No newline at end of file