Added Various API Functionality
This commit is contained in:
parent
6ff4170894
commit
bce39d355f
@ -13,6 +13,7 @@
|
|||||||
"@mui/icons-material": "7.0.2",
|
"@mui/icons-material": "7.0.2",
|
||||||
"@mui/material": "7.0.2",
|
"@mui/material": "7.0.2",
|
||||||
"normalize.css": "8.0.1",
|
"normalize.css": "8.0.1",
|
||||||
|
"prismjs": "1.30.0",
|
||||||
"react": "19.1.0",
|
"react": "19.1.0",
|
||||||
"react-color": "2.19.3",
|
"react-color": "2.19.3",
|
||||||
"react-dom": "19.1.0",
|
"react-dom": "19.1.0",
|
||||||
|
@ -0,0 +1,179 @@
|
|||||||
|
////////// PROJECT FILE SEPARATION LINE ////////// CODE AFTER THIS LINE ARE FROM: <ProjectRoot>/Data/WebUI/src/nodes/Data Analysis & Manipulation/Node_JSON_Display.jsx
|
||||||
|
|
||||||
|
import React, { useEffect, useState, useRef, useCallback } from "react";
|
||||||
|
import { Handle, Position, useReactFlow, useStore } from "reactflow";
|
||||||
|
// For syntax highlighting, ensure prismjs is installed: npm install prismjs
|
||||||
|
import Prism from "prismjs";
|
||||||
|
import "prismjs/components/prism-json";
|
||||||
|
import "prismjs/themes/prism-okaidia.css";
|
||||||
|
|
||||||
|
const JSONPrettyDisplayNode = ({ id, data }) => {
|
||||||
|
const { setNodes } = useReactFlow();
|
||||||
|
const edges = useStore((state) => state.edges);
|
||||||
|
const containerRef = useRef(null);
|
||||||
|
const resizingRef = useRef(false);
|
||||||
|
const startPosRef = useRef({ x: 0, y: 0 });
|
||||||
|
const startDimRef = useRef({ width: 0, height: 0 });
|
||||||
|
|
||||||
|
const [jsonData, setJsonData] = useState(data?.jsonData || {});
|
||||||
|
const initW = parseInt(data?.width || "300", 10);
|
||||||
|
const initH = parseInt(data?.height || "150", 10);
|
||||||
|
const [dimensions, setDimensions] = useState({ width: initW, height: initH });
|
||||||
|
const jsonRef = useRef(jsonData);
|
||||||
|
|
||||||
|
const persistDimensions = useCallback(() => {
|
||||||
|
const w = `${Math.round(dimensions.width)}px`;
|
||||||
|
const h = `${Math.round(dimensions.height)}px`;
|
||||||
|
setNodes((nds) =>
|
||||||
|
nds.map((n) =>
|
||||||
|
n.id === id
|
||||||
|
? { ...n, data: { ...n.data, width: w, height: h } }
|
||||||
|
: n
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}, [dimensions, id, setNodes]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const onMouseMove = (e) => {
|
||||||
|
if (!resizingRef.current) return;
|
||||||
|
const dx = e.clientX - startPosRef.current.x;
|
||||||
|
const dy = e.clientY - startPosRef.current.y;
|
||||||
|
setDimensions({
|
||||||
|
width: Math.max(100, startDimRef.current.width + dx),
|
||||||
|
height: Math.max(60, startDimRef.current.height + dy)
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const onMouseUp = () => {
|
||||||
|
if (resizingRef.current) {
|
||||||
|
resizingRef.current = false;
|
||||||
|
persistDimensions();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.addEventListener("mousemove", onMouseMove);
|
||||||
|
window.addEventListener("mouseup", onMouseUp);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("mousemove", onMouseMove);
|
||||||
|
window.removeEventListener("mouseup", onMouseUp);
|
||||||
|
};
|
||||||
|
}, [persistDimensions]);
|
||||||
|
|
||||||
|
const onResizeMouseDown = (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
resizingRef.current = true;
|
||||||
|
startPosRef.current = { x: e.clientX, y: e.clientY };
|
||||||
|
startDimRef.current = { ...dimensions };
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let rate = window.BorealisUpdateRate;
|
||||||
|
const tick = () => {
|
||||||
|
const edge = edges.find((e) => e.target === id);
|
||||||
|
if (edge && edge.source) {
|
||||||
|
const upstream = window.BorealisValueBus[edge.source];
|
||||||
|
if (typeof upstream === "object") {
|
||||||
|
if (JSON.stringify(upstream) !== JSON.stringify(jsonRef.current)) {
|
||||||
|
jsonRef.current = upstream;
|
||||||
|
setJsonData(upstream);
|
||||||
|
window.BorealisValueBus[id] = upstream;
|
||||||
|
setNodes((nds) =>
|
||||||
|
nds.map((n) =>
|
||||||
|
n.id === id ? { ...n, data: { ...n.data, jsonData: upstream } } : n
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
window.BorealisValueBus[id] = jsonRef.current;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const iv = setInterval(tick, rate);
|
||||||
|
const monitor = setInterval(() => {
|
||||||
|
if (window.BorealisUpdateRate !== rate) {
|
||||||
|
clearInterval(iv);
|
||||||
|
clearInterval(monitor);
|
||||||
|
}
|
||||||
|
}, 200);
|
||||||
|
return () => { clearInterval(iv); clearInterval(monitor); };
|
||||||
|
}, [id, edges, setNodes]);
|
||||||
|
|
||||||
|
// Generate highlighted HTML
|
||||||
|
const pretty = JSON.stringify(jsonData, null, 2);
|
||||||
|
const highlighted = Prism.highlight(pretty, Prism.languages.json, "json");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={containerRef}
|
||||||
|
className="borealis-node"
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
width: dimensions.width,
|
||||||
|
height: dimensions.height,
|
||||||
|
overflow: "visible",
|
||||||
|
position: "relative",
|
||||||
|
boxSizing: "border-box"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Handle type="target" position={Position.Left} className="borealis-handle" />
|
||||||
|
<Handle type="source" position={Position.Right} className="borealis-handle" />
|
||||||
|
|
||||||
|
<div className="borealis-node-header">Display JSON Data</div>
|
||||||
|
<div
|
||||||
|
className="borealis-node-content"
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
padding: "4px",
|
||||||
|
fontSize: "9px",
|
||||||
|
color: "#ccc",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
overflow: "hidden"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ marginBottom: "4px" }}>
|
||||||
|
Display prettified JSON from upstream.
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
width: "100%",
|
||||||
|
background: "#1e1e1e",
|
||||||
|
border: "1px solid #444",
|
||||||
|
borderRadius: "2px",
|
||||||
|
padding: "4px",
|
||||||
|
overflowY: "auto",
|
||||||
|
fontFamily: "monospace",
|
||||||
|
fontSize: "9px"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<pre
|
||||||
|
dangerouslySetInnerHTML={{ __html: highlighted }}
|
||||||
|
style={{ margin: 0 }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
onMouseDown={onResizeMouseDown}
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
width: "20px",
|
||||||
|
height: "20px",
|
||||||
|
right: "-4px",
|
||||||
|
bottom: "-4px",
|
||||||
|
cursor: "nwse-resize",
|
||||||
|
background: "transparent",
|
||||||
|
zIndex: 10
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
type: "Node_JSON_Pretty_Display",
|
||||||
|
label: "Display JSON Data",
|
||||||
|
description: "Display upstream JSON object as prettified JSON with syntax highlighting.",
|
||||||
|
content: "Display prettified multi-line JSON from upstream node.",
|
||||||
|
component: JSONPrettyDisplayNode
|
||||||
|
};
|
@ -0,0 +1,132 @@
|
|||||||
|
////////// PROJECT FILE SEPARATION LINE ////////// CODE AFTER THIS LINE ARE FROM: <ProjectRoot>/Data/WebUI/src/nodes/Data Analysis & Manipulation/Node_JSON_Value_Extractor.jsx
|
||||||
|
|
||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import { Handle, Position, useReactFlow } from "reactflow";
|
||||||
|
|
||||||
|
const JSONValueExtractorNode = ({ id, data }) => {
|
||||||
|
const { setNodes, getEdges } = useReactFlow();
|
||||||
|
const [keyName, setKeyName] = useState(data?.keyName || "");
|
||||||
|
const [value, setValue] = useState(data?.result || "");
|
||||||
|
|
||||||
|
const handleKeyChange = (e) => {
|
||||||
|
const newKey = e.target.value;
|
||||||
|
setKeyName(newKey);
|
||||||
|
setNodes((nds) =>
|
||||||
|
nds.map((n) =>
|
||||||
|
n.id === id
|
||||||
|
? { ...n, data: { ...n.data, keyName: newKey } }
|
||||||
|
: n
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let currentRate = window.BorealisUpdateRate;
|
||||||
|
let intervalId;
|
||||||
|
|
||||||
|
const runNodeLogic = () => {
|
||||||
|
const edges = getEdges();
|
||||||
|
const incoming = edges.filter((e) => e.target === id);
|
||||||
|
const sourceId = incoming[0]?.source;
|
||||||
|
let newValue = "Key Not Found";
|
||||||
|
|
||||||
|
if (sourceId && window.BorealisValueBus[sourceId] !== undefined) {
|
||||||
|
let upstream = window.BorealisValueBus[sourceId];
|
||||||
|
if (upstream && typeof upstream === "object" && keyName) {
|
||||||
|
const pathSegments = keyName.split(".");
|
||||||
|
let nodeVal = upstream;
|
||||||
|
for (let segment of pathSegments) {
|
||||||
|
if (
|
||||||
|
nodeVal != null &&
|
||||||
|
(typeof nodeVal === "object" || Array.isArray(nodeVal)) &&
|
||||||
|
segment in nodeVal
|
||||||
|
) {
|
||||||
|
nodeVal = nodeVal[segment];
|
||||||
|
} else {
|
||||||
|
nodeVal = undefined;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (nodeVal !== undefined) {
|
||||||
|
newValue = String(nodeVal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newValue !== value) {
|
||||||
|
setValue(newValue);
|
||||||
|
window.BorealisValueBus[id] = newValue;
|
||||||
|
setNodes((nds) =>
|
||||||
|
nds.map((n) =>
|
||||||
|
n.id === id
|
||||||
|
? { ...n, data: { ...n.data, result: newValue } }
|
||||||
|
: n
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
runNodeLogic();
|
||||||
|
intervalId = setInterval(runNodeLogic, currentRate);
|
||||||
|
|
||||||
|
const monitor = setInterval(() => {
|
||||||
|
const newRate = window.BorealisUpdateRate;
|
||||||
|
if (newRate !== currentRate) {
|
||||||
|
clearInterval(intervalId);
|
||||||
|
currentRate = newRate;
|
||||||
|
intervalId = setInterval(runNodeLogic, currentRate);
|
||||||
|
}
|
||||||
|
}, 250);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearInterval(intervalId);
|
||||||
|
clearInterval(monitor);
|
||||||
|
};
|
||||||
|
}, [keyName, id, setNodes, getEdges, value]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="borealis-node">
|
||||||
|
<div className="borealis-node-header">JSON Value Extractor</div>
|
||||||
|
<div className="borealis-node-content">
|
||||||
|
<label style={{ fontSize: "9px", display: "block", marginBottom: "4px" }}>
|
||||||
|
Key:
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={keyName}
|
||||||
|
onChange={handleKeyChange}
|
||||||
|
placeholder="e.g. name.en"
|
||||||
|
style={{
|
||||||
|
fontSize: "9px", padding: "4px", width: "100%",
|
||||||
|
background: "#1e1e1e", color: "#ccc",
|
||||||
|
border: "1px solid #444", borderRadius: "2px"
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<label style={{ fontSize: "9px", display: "block", margin: "8px 0 4px" }}>
|
||||||
|
Value:
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
readOnly
|
||||||
|
value={value}
|
||||||
|
rows={2}
|
||||||
|
style={{
|
||||||
|
fontSize: "9px", padding: "4px", width: "100%",
|
||||||
|
background: "#1e1e1e", color: "#ccc",
|
||||||
|
border: "1px solid #444", borderRadius: "2px",
|
||||||
|
resize: "none"
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Handle type="target" position={Position.Left} className="borealis-handle" />
|
||||||
|
<Handle type="source" position={Position.Right} className="borealis-handle" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
type: "JSON_Value_Extractor",
|
||||||
|
label: "JSON Value Extractor",
|
||||||
|
description: "Extract a nested value by dot-delimited path from upstream JSON data.",
|
||||||
|
content: "Provide a dot-separated key path (e.g. 'name.en'); outputs the extracted string or 'Key Not Found'.",
|
||||||
|
component: JSONValueExtractorNode
|
||||||
|
};
|
193
Data/Server/WebUI/src/nodes/Data Collection/Node_API_Request.jsx
Normal file
193
Data/Server/WebUI/src/nodes/Data Collection/Node_API_Request.jsx
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
////////// PROJECT FILE SEPARATION LINE ////////// CODE AFTER THIS LINE ARE FROM: <ProjectRoot>/Data/WebUI/src/nodes/Data Collection/Node_API_Request.jsx
|
||||||
|
|
||||||
|
import React, { useState, useEffect, useRef } from "react";
|
||||||
|
import { Handle, Position, useReactFlow } from "reactflow";
|
||||||
|
|
||||||
|
const APIRequestNode = ({ id, data }) => {
|
||||||
|
const { setNodes } = useReactFlow();
|
||||||
|
if (!window.BorealisValueBus) window.BorealisValueBus = {};
|
||||||
|
|
||||||
|
// Stored URL (actual fetch target)
|
||||||
|
const [url, setUrl] = useState(data?.url || "http://localhost:5000/health");
|
||||||
|
// Editable URL text
|
||||||
|
const [editUrl, setEditUrl] = useState(data?.url || "http://localhost:5000/health");
|
||||||
|
const [useProxy, setUseProxy] = useState(data?.useProxy || true);
|
||||||
|
const [body, setBody] = useState(data?.body || "");
|
||||||
|
const [intervalSec, setIntervalSec] = useState(data?.intervalSec ?? 10);
|
||||||
|
const [error, setError] = useState(null);
|
||||||
|
const [statusCode, setStatusCode] = useState(null);
|
||||||
|
const [statusText, setStatusText] = useState("");
|
||||||
|
const resultRef = useRef(null);
|
||||||
|
|
||||||
|
const handleUrlInputChange = (e) => setEditUrl(e.target.value);
|
||||||
|
const handleToggleProxy = (e) => {
|
||||||
|
const flag = e.target.checked;
|
||||||
|
setUseProxy(flag);
|
||||||
|
setNodes((nds) =>
|
||||||
|
nds.map((n) =>
|
||||||
|
n.id === id
|
||||||
|
? { ...n, data: { ...n.data, useProxy: flag } }
|
||||||
|
: n
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUpdateUrl = () => {
|
||||||
|
setUrl(editUrl);
|
||||||
|
setError(null);
|
||||||
|
setStatusCode(null);
|
||||||
|
setStatusText("");
|
||||||
|
resultRef.current = null;
|
||||||
|
window.BorealisValueBus[id] = undefined;
|
||||||
|
setNodes((nds) =>
|
||||||
|
nds.map((n) =>
|
||||||
|
n.id === id
|
||||||
|
? { ...n, data: { ...n.data, url: editUrl, result: undefined } }
|
||||||
|
: n
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// ... other handlers unchanged ...
|
||||||
|
const handleBodyChange = (e) => {
|
||||||
|
const newBody = e.target.value;
|
||||||
|
setBody(newBody);
|
||||||
|
setNodes((nds) =>
|
||||||
|
nds.map((n) =>
|
||||||
|
n.id === id
|
||||||
|
? { ...n, data: { ...n.data, body: newBody } }
|
||||||
|
: n
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const handleIntervalChange = (e) => {
|
||||||
|
const sec = parseInt(e.target.value, 10) || 1;
|
||||||
|
setIntervalSec(sec);
|
||||||
|
setNodes((nds) =>
|
||||||
|
nds.map((n) =>
|
||||||
|
n.id === id
|
||||||
|
? { ...n, data: { ...n.data, intervalSec: sec } }
|
||||||
|
: n
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let cancelled = false;
|
||||||
|
|
||||||
|
const runNodeLogic = async () => {
|
||||||
|
try {
|
||||||
|
setError(null);
|
||||||
|
let target = url;
|
||||||
|
if (useProxy) {
|
||||||
|
target = `/api/proxy?url=${encodeURIComponent(url)}`;
|
||||||
|
}
|
||||||
|
const options = {};
|
||||||
|
if (body.trim()) {
|
||||||
|
options.method = "POST";
|
||||||
|
options.headers = { "Content-Type": "application/json" };
|
||||||
|
options.body = body;
|
||||||
|
}
|
||||||
|
const res = await fetch(target, options);
|
||||||
|
setStatusCode(res.status);
|
||||||
|
setStatusText(res.statusText);
|
||||||
|
if (!res.ok) {
|
||||||
|
// clear on error
|
||||||
|
resultRef.current = null;
|
||||||
|
window.BorealisValueBus[id] = undefined;
|
||||||
|
setNodes((nds) =>
|
||||||
|
nds.map((n) =>
|
||||||
|
n.id === id
|
||||||
|
? { ...n, data: { ...n.data, result: undefined } }
|
||||||
|
: n
|
||||||
|
)
|
||||||
|
);
|
||||||
|
throw new Error(`HTTP ${res.status}`);
|
||||||
|
}
|
||||||
|
const json = await res.json();
|
||||||
|
const pretty = JSON.stringify(json, null, 2);
|
||||||
|
if (!cancelled && resultRef.current !== pretty) {
|
||||||
|
resultRef.current = pretty;
|
||||||
|
window.BorealisValueBus[id] = json;
|
||||||
|
setNodes((nds) =>
|
||||||
|
nds.map((n) =>
|
||||||
|
n.id === id
|
||||||
|
? { ...n, data: { ...n.data, result: pretty } }
|
||||||
|
: n
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("API Request node fetch error:", err);
|
||||||
|
setError(err.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
runNodeLogic();
|
||||||
|
const ms = Math.max(intervalSec, 1) * 1000;
|
||||||
|
const iv = setInterval(runNodeLogic, ms);
|
||||||
|
return () => {
|
||||||
|
cancelled = true;
|
||||||
|
clearInterval(iv);
|
||||||
|
};
|
||||||
|
}, [url, body, intervalSec, useProxy, id, setNodes]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="borealis-node">
|
||||||
|
<div className="borealis-node-header">API Request</div>
|
||||||
|
<div className="borealis-node-content">
|
||||||
|
{/* URL input */}
|
||||||
|
<label style={{ fontSize: "9px", display: "block", marginBottom: "4px" }}>Request URL:</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={editUrl}
|
||||||
|
onChange={handleUrlInputChange}
|
||||||
|
style={{ fontSize: "9px", padding: "4px", width: "100%", background: "#1e1e1e", color: "#ccc", border: "1px solid #444", borderRadius: "2px" }}
|
||||||
|
/>
|
||||||
|
<button onClick={handleUpdateUrl} style={{ fontSize: "9px", marginTop: "6px", padding: "2px 6px", background: "#333", color: "#ccc", border: "1px solid #444", borderRadius: "2px", cursor: "pointer" }}>
|
||||||
|
Update URL
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Proxy toggle */}
|
||||||
|
<div style={{ marginTop: "6px" }}>
|
||||||
|
<input
|
||||||
|
id={`${id}-proxy-toggle`}
|
||||||
|
type="checkbox"
|
||||||
|
checked={useProxy}
|
||||||
|
onChange={handleToggleProxy}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
htmlFor={`${id}-proxy-toggle`}
|
||||||
|
title="Query a remote API server using internal Borealis mechanisms to bypass CORS limitations."
|
||||||
|
style={{ fontSize: "8px", marginLeft: "4px", cursor: "help" }}
|
||||||
|
>
|
||||||
|
Remote API Endpoint
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* body & interval unchanged... */}
|
||||||
|
<label style={{ fontSize: "9px", display: "block", margin: "8px 0 4px" }}>Request Body:</label>
|
||||||
|
<textarea value={body} onChange={handleBodyChange} rows={4} style={{ fontSize: "9px", padding: "4px", width: "100%", background: "#1e1e1e", color: "#ccc", border: "1px solid #444", borderRadius: "2px", resize: "vertical" }} />
|
||||||
|
<label style={{ fontSize: "9px", display: "block", margin: "8px 0 4px" }}>Polling Interval (sec):</label>
|
||||||
|
<input type="number" min="1" value={intervalSec} onChange={handleIntervalChange} style={{ fontSize: "9px", padding: "4px", width: "100%", background: "#1e1e1e", color: "#ccc", border: "1px solid #444", borderRadius: "2px" }} />
|
||||||
|
|
||||||
|
{/* indicators unchanged... */}
|
||||||
|
{statusCode !== null && statusCode >= 200 && statusCode < 300 && (
|
||||||
|
<div style={{ color: "#6f6", fontSize: "8px", marginTop: "6px" }}>Status: {statusCode} {statusText}</div>
|
||||||
|
)}
|
||||||
|
{error && (
|
||||||
|
<div style={{ color: "#f66", fontSize: "8px", marginTop: "6px" }}>Error: {error}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<Handle type="source" position={Position.Right} className="borealis-handle" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
type: "API_Request",
|
||||||
|
label: "API Request",
|
||||||
|
description: "Fetch JSON from an API endpoint with optional body, proxy toggle, and polling interval.",
|
||||||
|
content: "Fetch JSON from HTTP or remote API via internal proxy to bypass CORS.",
|
||||||
|
component: APIRequestNode
|
||||||
|
};
|
@ -7,6 +7,7 @@ torchaudio --index-url https://download.pytorch.org/whl/cu121
|
|||||||
Flask
|
Flask
|
||||||
requests
|
requests
|
||||||
flask_socketio
|
flask_socketio
|
||||||
|
flask-cors
|
||||||
eventlet
|
eventlet
|
||||||
|
|
||||||
# GUI-related dependencies (Qt for GUI components)
|
# GUI-related dependencies (Qt for GUI components)
|
||||||
|
@ -4,8 +4,10 @@ import eventlet
|
|||||||
# Monkey-patch stdlib for cooperative sockets
|
# Monkey-patch stdlib for cooperative sockets
|
||||||
eventlet.monkey_patch()
|
eventlet.monkey_patch()
|
||||||
|
|
||||||
from flask import Flask, request, jsonify, Response, send_from_directory
|
import requests
|
||||||
|
from flask import Flask, request, jsonify, Response, send_from_directory, make_response
|
||||||
from flask_socketio import SocketIO, emit
|
from flask_socketio import SocketIO, emit
|
||||||
|
from flask_cors import CORS
|
||||||
|
|
||||||
import time
|
import time
|
||||||
import os # To Read Production ReactJS Server Folder
|
import os # To Read Production ReactJS Server Folder
|
||||||
@ -22,6 +24,9 @@ app = Flask(
|
|||||||
static_url_path=''
|
static_url_path=''
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Enable CORS on All Routes
|
||||||
|
CORS(app)
|
||||||
|
|
||||||
socketio = SocketIO(
|
socketio = SocketIO(
|
||||||
app,
|
app,
|
||||||
cors_allowed_origins="*",
|
cors_allowed_origins="*",
|
||||||
@ -108,6 +113,32 @@ def provision_agent():
|
|||||||
socketio.emit("agent_config", config)
|
socketio.emit("agent_config", config)
|
||||||
return jsonify({"status": "provisioned", "roles": roles})
|
return jsonify({"status": "provisioned", "roles": roles})
|
||||||
|
|
||||||
|
# ---------------------------------------------
|
||||||
|
# Borealis External API Proxy Endpoint
|
||||||
|
# ---------------------------------------------
|
||||||
|
@app.route("/api/proxy", methods=["GET", "POST", "OPTIONS"])
|
||||||
|
def proxy():
|
||||||
|
target = request.args.get("url")
|
||||||
|
if not target:
|
||||||
|
return {"error": "Missing ?url="}, 400
|
||||||
|
|
||||||
|
# Forward method, headers, body
|
||||||
|
upstream = requests.request(
|
||||||
|
method = request.method,
|
||||||
|
url = target,
|
||||||
|
headers = {k:v for k,v in request.headers if k.lower() != "host"},
|
||||||
|
data = request.get_data(),
|
||||||
|
timeout = 10
|
||||||
|
)
|
||||||
|
|
||||||
|
excluded = ["content-encoding","content-length","transfer-encoding","connection"]
|
||||||
|
headers = [(k,v) for k,v in upstream.raw.headers.items() if k.lower() not in excluded]
|
||||||
|
|
||||||
|
resp = make_response(upstream.content, upstream.status_code)
|
||||||
|
for k,v in headers:
|
||||||
|
resp.headers[k] = v
|
||||||
|
return resp
|
||||||
|
|
||||||
# ---------------------------------------------
|
# ---------------------------------------------
|
||||||
# Live Screenshot Viewer for Debugging
|
# Live Screenshot Viewer for Debugging
|
||||||
# ---------------------------------------------
|
# ---------------------------------------------
|
||||||
|
Loading…
x
Reference in New Issue
Block a user