mirror of
https://github.com/bunny-lab-io/Borealis.git
synced 2025-10-26 17:41:58 -06:00
Updated Design of Borealis Agent Node
This commit is contained in:
@@ -40,23 +40,55 @@ export default function NodeConfigurationSidebar({ drawerOpen, setDrawerOpen, ti
|
|||||||
const config = nodeData?.config || [];
|
const config = nodeData?.config || [];
|
||||||
const nodeId = nodeData?.nodeId;
|
const nodeId = nodeData?.nodeId;
|
||||||
|
|
||||||
|
const normalizeOptions = (opts = []) =>
|
||||||
|
opts.map((opt) => {
|
||||||
|
if (typeof opt === "string") {
|
||||||
|
return { value: opt, label: opt, disabled: false };
|
||||||
|
}
|
||||||
|
if (opt && typeof opt === "object") {
|
||||||
|
const val =
|
||||||
|
opt.value ??
|
||||||
|
opt.id ??
|
||||||
|
opt.handle ??
|
||||||
|
(typeof opt.label === "string" ? opt.label : "");
|
||||||
|
const label =
|
||||||
|
opt.label ??
|
||||||
|
opt.name ??
|
||||||
|
opt.title ??
|
||||||
|
(typeof val !== "undefined" ? String(val) : "");
|
||||||
|
return {
|
||||||
|
value: typeof val === "undefined" ? "" : String(val),
|
||||||
|
label: typeof label === "undefined" ? "" : String(label),
|
||||||
|
disabled: Boolean(opt.disabled)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return { value: String(opt ?? ""), label: String(opt ?? ""), disabled: false };
|
||||||
|
});
|
||||||
|
|
||||||
return config.map((field, index) => {
|
return config.map((field, index) => {
|
||||||
const value = nodeData?.[field.key] || "";
|
const value = nodeData?.[field.key] ?? "";
|
||||||
|
const isReadOnly = Boolean(field.readOnly);
|
||||||
|
|
||||||
// ---- DYNAMIC DROPDOWN SUPPORT ----
|
// ---- DYNAMIC DROPDOWN SUPPORT ----
|
||||||
if (field.type === "select") {
|
if (field.type === "select") {
|
||||||
let options = field.options || [];
|
let options = field.options || [];
|
||||||
|
|
||||||
// Handle dynamic options for things like Target Window
|
if (field.optionsKey && Array.isArray(nodeData?.[field.optionsKey])) {
|
||||||
if (field.dynamicOptions && nodeData?.windowList && Array.isArray(nodeData.windowList)) {
|
options = nodeData[field.optionsKey];
|
||||||
|
} else if (field.dynamicOptions && nodeData?.windowList && Array.isArray(nodeData.windowList)) {
|
||||||
options = nodeData.windowList
|
options = nodeData.windowList
|
||||||
.map(win => ({
|
.map((win) => ({
|
||||||
value: String(win.handle),
|
value: String(win.handle),
|
||||||
label: `${win.title} (${win.handle})`
|
label: `${win.title} (${win.handle})`
|
||||||
}))
|
}))
|
||||||
.sort((a, b) => a.label.localeCompare(b.label, undefined, { sensitivity: "base" }));
|
.sort((a, b) => a.label.localeCompare(b.label, undefined, { sensitivity: "base" }));
|
||||||
} else {
|
}
|
||||||
options = options.map(opt => ({ value: opt, label: opt }));
|
|
||||||
|
options = normalizeOptions(options);
|
||||||
|
|
||||||
|
// Handle dynamic options for things like Target Window
|
||||||
|
if (field.dynamicOptions && (!nodeData?.windowList || !Array.isArray(nodeData.windowList))) {
|
||||||
|
options = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -70,6 +102,7 @@ export default function NodeConfigurationSidebar({ drawerOpen, setDrawerOpen, ti
|
|||||||
size="small"
|
size="small"
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
|
if (isReadOnly) return;
|
||||||
const newValue = e.target.value;
|
const newValue = e.target.value;
|
||||||
if (!nodeId) return;
|
if (!nodeId) return;
|
||||||
effectiveSetNodes((nds) =>
|
effectiveSetNodes((nds) =>
|
||||||
@@ -134,7 +167,7 @@ export default function NodeConfigurationSidebar({ drawerOpen, setDrawerOpen, ti
|
|||||||
</MenuItem>
|
</MenuItem>
|
||||||
) : (
|
) : (
|
||||||
options.map((opt, idx) => (
|
options.map((opt, idx) => (
|
||||||
<MenuItem key={idx} value={opt.value}>
|
<MenuItem key={idx} value={opt.value} disabled={opt.disabled}>
|
||||||
{opt.label}
|
{opt.label}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
))
|
))
|
||||||
@@ -155,7 +188,13 @@ export default function NodeConfigurationSidebar({ drawerOpen, setDrawerOpen, ti
|
|||||||
size="small"
|
size="small"
|
||||||
fullWidth
|
fullWidth
|
||||||
value={value}
|
value={value}
|
||||||
|
disabled={isReadOnly}
|
||||||
|
InputProps={{
|
||||||
|
readOnly: isReadOnly,
|
||||||
|
sx: { color: "#ccc" }
|
||||||
|
}}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
|
if (isReadOnly) return;
|
||||||
const newValue = e.target.value;
|
const newValue = e.target.value;
|
||||||
if (!nodeId) return;
|
if (!nodeId) return;
|
||||||
effectiveSetNodes((nds) =>
|
effectiveSetNodes((nds) =>
|
||||||
|
|||||||
@@ -8,18 +8,18 @@ const BorealisAgentNode = ({ id, data }) => {
|
|||||||
const edges = useStore((state) => state.edges);
|
const edges = useStore((state) => state.edges);
|
||||||
const [agents, setAgents] = useState({});
|
const [agents, setAgents] = useState({});
|
||||||
const [sites, setSites] = useState([]);
|
const [sites, setSites] = useState([]);
|
||||||
const [selectedAgent, setSelectedAgent] = useState(data.agent_id || "");
|
|
||||||
const [selectedHost, setSelectedHost] = useState(data.agent_host || "");
|
|
||||||
const [selectedSiteId, setSelectedSiteId] = useState(
|
|
||||||
data.agent_site_id ? String(data.agent_site_id) : ""
|
|
||||||
);
|
|
||||||
const initialMode = (data.agent_mode || "currentuser").toLowerCase();
|
|
||||||
const [selectedMode, setSelectedMode] = useState(
|
|
||||||
initialMode === "system" ? "system" : "currentuser"
|
|
||||||
);
|
|
||||||
const [isConnected, setIsConnected] = useState(false);
|
const [isConnected, setIsConnected] = useState(false);
|
||||||
const [siteMapping, setSiteMapping] = useState({});
|
const [siteMapping, setSiteMapping] = useState({});
|
||||||
const prevRolesRef = useRef([]);
|
const prevRolesRef = useRef([]);
|
||||||
|
const selectionRef = useRef({ host: "", mode: "", agentId: "", siteId: "" });
|
||||||
|
|
||||||
|
const selectedSiteId = data?.agent_site_id ? String(data.agent_site_id) : "";
|
||||||
|
const selectedHost = data?.agent_host || "";
|
||||||
|
const selectedMode =
|
||||||
|
(data?.agent_mode || "currentuser").toString().toLowerCase() === "system"
|
||||||
|
? "system"
|
||||||
|
: "currentuser";
|
||||||
|
const selectedAgent = data?.agent_id || "";
|
||||||
|
|
||||||
// Group agents by hostname and execution context
|
// Group agents by hostname and execution context
|
||||||
const agentsByHostname = useMemo(() => {
|
const agentsByHostname = useMemo(() => {
|
||||||
@@ -127,18 +127,31 @@ const hostOptions = useMemo(() => {
|
|||||||
});
|
});
|
||||||
}, [hostOptions, selectedSiteId, siteMapping]);
|
}, [hostOptions, selectedSiteId, siteMapping]);
|
||||||
|
|
||||||
const hasSiteSelection = Boolean(selectedSiteId);
|
|
||||||
|
|
||||||
// Align selected site with known host mapping when available
|
// Align selected site with known host mapping when available
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedSiteId || !selectedHost) return;
|
if (selectedSiteId || !selectedHost) return;
|
||||||
const mapping = siteMapping[selectedHost];
|
const mapping = siteMapping[selectedHost];
|
||||||
if (!mapping || typeof mapping.site_id === "undefined" || mapping.site_id === null) return;
|
if (!mapping || typeof mapping.site_id === "undefined" || mapping.site_id === null) return;
|
||||||
setSelectedSiteId(String(mapping.site_id));
|
const mappedId = String(mapping.site_id);
|
||||||
}, [selectedHost, selectedSiteId, siteMapping]);
|
setNodes((nds) =>
|
||||||
|
nds.map((n) =>
|
||||||
|
n.id === id
|
||||||
|
? {
|
||||||
|
...n,
|
||||||
|
data: {
|
||||||
|
...n.data,
|
||||||
|
agent_site_id: mappedId,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: n
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}, [selectedHost, selectedSiteId, siteMapping, id, setNodes]);
|
||||||
|
|
||||||
// Ensure host selection stays aligned with available agents
|
// Ensure host selection stays aligned with available agents
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!selectedHost) return;
|
||||||
|
|
||||||
const hostExists = filteredHostOptions.some((opt) => opt.host === selectedHost);
|
const hostExists = filteredHostOptions.some((opt) => opt.host === selectedHost);
|
||||||
if (hostExists) return;
|
if (hostExists) return;
|
||||||
|
|
||||||
@@ -147,52 +160,78 @@ const hostOptions = useMemo(() => {
|
|||||||
const inferredHost = (info?.hostname || info?.agent_hostname || "").trim() || "unknown";
|
const inferredHost = (info?.hostname || info?.agent_hostname || "").trim() || "unknown";
|
||||||
const allowed = filteredHostOptions.some((opt) => opt.host === inferredHost);
|
const allowed = filteredHostOptions.some((opt) => opt.host === inferredHost);
|
||||||
if (allowed && inferredHost && inferredHost !== selectedHost) {
|
if (allowed && inferredHost && inferredHost !== selectedHost) {
|
||||||
setSelectedHost(inferredHost);
|
setNodes((nds) =>
|
||||||
|
nds.map((n) =>
|
||||||
|
n.id === id
|
||||||
|
? {
|
||||||
|
...n,
|
||||||
|
data: {
|
||||||
|
...n.data,
|
||||||
|
agent_host: inferredHost,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: n
|
||||||
|
)
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const fallbackHost = filteredHostOptions[0]?.host || "";
|
setNodes((nds) =>
|
||||||
if (fallbackHost !== selectedHost) {
|
nds.map((n) =>
|
||||||
setSelectedHost(fallbackHost);
|
n.id === id
|
||||||
}
|
? {
|
||||||
if (!fallbackHost && selectedAgent) {
|
...n,
|
||||||
setSelectedAgent("");
|
data: {
|
||||||
}
|
...n.data,
|
||||||
}, [filteredHostOptions, selectedHost, selectedAgent, agents]);
|
agent_host: "",
|
||||||
|
agent_id: "",
|
||||||
|
agent_mode: "currentuser",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: n
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}, [filteredHostOptions, selectedHost, selectedAgent, agents, id, setNodes]);
|
||||||
|
|
||||||
// Align agent selection with host/mode choice
|
const siteSelectOptions = useMemo(() => {
|
||||||
useEffect(() => {
|
const entries = Array.isArray(sites) ? [...sites] : [];
|
||||||
if (!selectedHost) {
|
entries.sort((a, b) =>
|
||||||
if (selectedMode !== "currentuser") setSelectedMode("currentuser");
|
(a?.name || "").localeCompare(b?.name || "", undefined, { sensitivity: "base" })
|
||||||
if (selectedAgent) setSelectedAgent("");
|
);
|
||||||
return;
|
const mapped = entries.map((site) => ({
|
||||||
}
|
value: String(site.id),
|
||||||
const contexts = agentsByHostname[selectedHost];
|
label: site.name || `Site ${site.id}`,
|
||||||
if (!contexts) {
|
}));
|
||||||
if (selectedMode !== "currentuser") setSelectedMode("currentuser");
|
return [{ value: "", label: "All Sites" }, ...mapped];
|
||||||
if (selectedAgent) setSelectedAgent("");
|
}, [sites]);
|
||||||
return;
|
|
||||||
}
|
const hostSelectOptions = useMemo(() => {
|
||||||
if (!contexts[selectedMode]) {
|
const mapped = filteredHostOptions.map(({ host, label }) => ({
|
||||||
const fallbackMode = contexts.currentuser
|
value: host,
|
||||||
? "currentuser"
|
label,
|
||||||
: contexts.system
|
}));
|
||||||
? "system"
|
return [{ value: "", label: "-- Select --" }, ...mapped];
|
||||||
: selectedMode;
|
}, [filteredHostOptions]);
|
||||||
if (fallbackMode !== selectedMode) {
|
|
||||||
setSelectedMode(fallbackMode);
|
const activeHostContexts = selectedHost ? agentsByHostname[selectedHost] : null;
|
||||||
return;
|
|
||||||
}
|
const modeSelectOptions = useMemo(
|
||||||
}
|
() => [
|
||||||
const activeContext = contexts[selectedMode];
|
{
|
||||||
const targetAgentId = activeContext?.agent_id || "";
|
value: "currentuser",
|
||||||
if (targetAgentId !== selectedAgent) {
|
label: "CURRENTUSER (Screen Capture / Macros)",
|
||||||
setSelectedAgent(targetAgentId);
|
disabled: !activeHostContexts?.currentuser,
|
||||||
}
|
},
|
||||||
}, [selectedHost, selectedMode, agentsByHostname, selectedAgent]);
|
{
|
||||||
|
value: "system",
|
||||||
|
label: "SYSTEM (Scripts)",
|
||||||
|
disabled: !activeHostContexts?.system,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[activeHostContexts]
|
||||||
|
);
|
||||||
|
|
||||||
// Sync node data with sidebar changes
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setNodes((nds) =>
|
setNodes((nds) =>
|
||||||
nds.map((n) =>
|
nds.map((n) =>
|
||||||
@@ -201,17 +240,126 @@ const hostOptions = useMemo(() => {
|
|||||||
...n,
|
...n,
|
||||||
data: {
|
data: {
|
||||||
...n.data,
|
...n.data,
|
||||||
agent_id: selectedAgent,
|
siteOptions: siteSelectOptions,
|
||||||
agent_host: selectedHost,
|
hostOptions: hostSelectOptions,
|
||||||
agent_mode: selectedMode,
|
modeOptions: modeSelectOptions,
|
||||||
agent_site_id: selectedSiteId || "",
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
: n
|
: n
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
setIsConnected(false);
|
}, [id, setNodes, siteSelectOptions, hostSelectOptions, modeSelectOptions]);
|
||||||
}, [selectedAgent, selectedHost, selectedMode, selectedSiteId, setNodes, id]);
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!selectedHost) {
|
||||||
|
if (selectedAgent || selectedMode !== "currentuser") {
|
||||||
|
setNodes((nds) =>
|
||||||
|
nds.map((n) =>
|
||||||
|
n.id === id
|
||||||
|
? {
|
||||||
|
...n,
|
||||||
|
data: {
|
||||||
|
...n.data,
|
||||||
|
agent_id: "",
|
||||||
|
agent_mode: "currentuser",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: n
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const contexts = agentsByHostname[selectedHost];
|
||||||
|
if (!contexts) {
|
||||||
|
if (selectedAgent || selectedMode !== "currentuser") {
|
||||||
|
setNodes((nds) =>
|
||||||
|
nds.map((n) =>
|
||||||
|
n.id === id
|
||||||
|
? {
|
||||||
|
...n,
|
||||||
|
data: {
|
||||||
|
...n.data,
|
||||||
|
agent_id: "",
|
||||||
|
agent_mode: "currentuser",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: n
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!contexts[selectedMode]) {
|
||||||
|
const fallbackMode = contexts.currentuser
|
||||||
|
? "currentuser"
|
||||||
|
: contexts.system
|
||||||
|
? "system"
|
||||||
|
: "currentuser";
|
||||||
|
const fallbackAgentId = contexts[fallbackMode]?.agent_id || "";
|
||||||
|
if (fallbackMode !== selectedMode || fallbackAgentId !== selectedAgent) {
|
||||||
|
setNodes((nds) =>
|
||||||
|
nds.map((n) =>
|
||||||
|
n.id === id
|
||||||
|
? {
|
||||||
|
...n,
|
||||||
|
data: {
|
||||||
|
...n.data,
|
||||||
|
agent_mode: fallbackMode,
|
||||||
|
agent_id: fallbackAgentId,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: n
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetAgentId = contexts[selectedMode]?.agent_id || "";
|
||||||
|
if (targetAgentId !== selectedAgent) {
|
||||||
|
setNodes((nds) =>
|
||||||
|
nds.map((n) =>
|
||||||
|
n.id === id
|
||||||
|
? {
|
||||||
|
...n,
|
||||||
|
data: {
|
||||||
|
...n.data,
|
||||||
|
agent_id: targetAgentId,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: n
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [selectedHost, selectedMode, agentsByHostname, selectedAgent, id, setNodes]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const prev = selectionRef.current;
|
||||||
|
const changed =
|
||||||
|
prev.host !== selectedHost ||
|
||||||
|
prev.mode !== selectedMode ||
|
||||||
|
prev.agentId !== selectedAgent ||
|
||||||
|
prev.siteId !== selectedSiteId;
|
||||||
|
if (!changed) return;
|
||||||
|
|
||||||
|
const selectionChangedAgent =
|
||||||
|
prev.agentId &&
|
||||||
|
(prev.agentId !== selectedAgent || prev.host !== selectedHost || prev.mode !== selectedMode);
|
||||||
|
if (selectionChangedAgent) {
|
||||||
|
setIsConnected(false);
|
||||||
|
prevRolesRef.current = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
selectionRef.current = {
|
||||||
|
host: selectedHost,
|
||||||
|
mode: selectedMode,
|
||||||
|
agentId: selectedAgent,
|
||||||
|
siteId: selectedSiteId,
|
||||||
|
};
|
||||||
|
}, [selectedHost, selectedMode, selectedAgent, selectedSiteId]);
|
||||||
|
|
||||||
// Attached Roles logic
|
// Attached Roles logic
|
||||||
const attachedRoleIds = useMemo(
|
const attachedRoleIds = useMemo(
|
||||||
@@ -287,8 +435,6 @@ const hostOptions = useMemo(() => {
|
|||||||
return status.charAt(0).toUpperCase() + status.slice(1);
|
return status.charAt(0).toUpperCase() + status.slice(1);
|
||||||
}, [agentsByHostname, selectedHost, selectedMode, selectedAgent]);
|
}, [agentsByHostname, selectedHost, selectedMode, selectedAgent]);
|
||||||
|
|
||||||
const activeHostContexts = selectedHost ? agentsByHostname[selectedHost] : null;
|
|
||||||
|
|
||||||
// Render (Sidebar handles config)
|
// Render (Sidebar handles config)
|
||||||
return (
|
return (
|
||||||
<div className="borealis-node">
|
<div className="borealis-node">
|
||||||
@@ -301,76 +447,40 @@ const hostOptions = useMemo(() => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="borealis-node-header">Device Agent</div>
|
<div className="borealis-node-header">Device Agent</div>
|
||||||
<div className="borealis-node-content" style={{ fontSize: "9px" }}>
|
<div
|
||||||
<label>Site:</label>
|
className="borealis-node-content"
|
||||||
<select
|
style={{
|
||||||
value={selectedSiteId}
|
fontSize: "9px",
|
||||||
onChange={(e) => setSelectedSiteId(e.target.value)}
|
display: "flex",
|
||||||
style={{ width: "100%", marginBottom: "6px", fontSize: "9px" }}
|
flexDirection: "column",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
textAlign: "center",
|
||||||
|
minHeight: "80px",
|
||||||
|
gap: "8px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ fontSize: "8px", color: "#666" }}>Right-Click to Configure Agent</div>
|
||||||
|
<button
|
||||||
|
onClick={isConnected ? handleDisconnect : handleConnect}
|
||||||
|
style={{
|
||||||
|
padding: "6px 14px",
|
||||||
|
fontSize: "10px",
|
||||||
|
background: isConnected ? "#3a3a3a" : "#0475c2",
|
||||||
|
color: "#fff",
|
||||||
|
border: "1px solid #0475c2",
|
||||||
|
borderRadius: "4px",
|
||||||
|
cursor: selectedAgent ? "pointer" : "not-allowed",
|
||||||
|
opacity: selectedAgent ? 1 : 0.5,
|
||||||
|
minWidth: "150px",
|
||||||
|
}}
|
||||||
|
disabled={!selectedAgent}
|
||||||
>
|
>
|
||||||
<option value="">All Sites</option>
|
{isConnected ? "Disconnect" : "Connect to Device"}
|
||||||
{sites.map((site) => (
|
</button>
|
||||||
<option key={site.id} value={String(site.id)}>
|
<div style={{ fontSize: "8px", color: "#777" }}>
|
||||||
{site.name}
|
{selectedHost ? `${selectedHost} · ${selectedMode.toUpperCase()}` : "No device selected"}
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<label>Device:</label>
|
|
||||||
<select
|
|
||||||
value={selectedHost}
|
|
||||||
onChange={(e) => setSelectedHost(e.target.value)}
|
|
||||||
style={{ width: "100%", marginBottom: "6px", fontSize: "9px" }}
|
|
||||||
disabled={hasSiteSelection && !filteredHostOptions.length}
|
|
||||||
>
|
|
||||||
<option value="">-- Select --</option>
|
|
||||||
{filteredHostOptions.map(({ host, label }) => (
|
|
||||||
<option key={host} value={host}>
|
|
||||||
{label}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<label>Available Agent Context(s):</label>
|
|
||||||
<select
|
|
||||||
value={selectedMode}
|
|
||||||
onChange={(e) => setSelectedMode(e.target.value)}
|
|
||||||
style={{ width: "100%", marginBottom: "2px", fontSize: "9px" }}
|
|
||||||
disabled={!selectedHost}
|
|
||||||
>
|
|
||||||
<option value="currentuser" disabled={!activeHostContexts?.currentuser}>
|
|
||||||
CURRENTUSER (Screen Capture / Macros)
|
|
||||||
</option>
|
|
||||||
<option value="system" disabled={!activeHostContexts?.system}>
|
|
||||||
SYSTEM (Scripts)
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<div style={{ fontSize: "6px", color: "#aaa", marginBottom: "6px" }}>
|
|
||||||
Agent ID:{" "}
|
|
||||||
{selectedAgent ? (
|
|
||||||
<span style={{ color: "#666" }}>{selectedAgent}</span>
|
|
||||||
) : (
|
|
||||||
<span style={{ color: "#666" }}>No Agent Selected</span>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isConnected ? (
|
|
||||||
<button
|
|
||||||
onClick={handleDisconnect}
|
|
||||||
style={{ width: "100%", fontSize: "9px", padding: "4px", marginTop: "4px" }}
|
|
||||||
>
|
|
||||||
Disconnect from Agent
|
|
||||||
</button>
|
|
||||||
) : (
|
|
||||||
<button
|
|
||||||
onClick={handleConnect}
|
|
||||||
style={{ width: "100%", fontSize: "9px", padding: "4px", marginTop: "4px" }}
|
|
||||||
disabled={!selectedAgent}
|
|
||||||
>
|
|
||||||
Connect to Device
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -390,37 +500,54 @@ Select and connect to a remote Borealis Agent.
|
|||||||
content: "Select and manage an Agent with dynamic roles",
|
content: "Select and manage an Agent with dynamic roles",
|
||||||
component: BorealisAgentNode,
|
component: BorealisAgentNode,
|
||||||
config: [
|
config: [
|
||||||
|
{
|
||||||
|
key: "agent_site_id",
|
||||||
|
label: "Site",
|
||||||
|
type: "select",
|
||||||
|
optionsKey: "siteOptions",
|
||||||
|
defaultValue: ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "agent_host",
|
||||||
|
label: "Device",
|
||||||
|
type: "select",
|
||||||
|
optionsKey: "hostOptions",
|
||||||
|
defaultValue: ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "agent_mode",
|
||||||
|
label: "Agent Context",
|
||||||
|
type: "select",
|
||||||
|
optionsKey: "modeOptions",
|
||||||
|
defaultValue: "currentuser"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: "agent_id",
|
key: "agent_id",
|
||||||
label: "Agent",
|
label: "Agent ID",
|
||||||
type: "text", // NOTE: UI populates via agent fetch, but config drives default for sidebar.
|
type: "text",
|
||||||
|
readOnly: true,
|
||||||
defaultValue: ""
|
defaultValue: ""
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
usage_documentation: `
|
usage_documentation: `
|
||||||
### Borealis Agent Node
|
### Borealis Agent Node
|
||||||
|
|
||||||
This node represents an available Borealis Agent (Python client) you can control from your workflow.
|
This node allows you to establish a connection with a device running a Borealis "Agent", so you can instruct the agent to do things from your workflow.
|
||||||
|
|
||||||
#### Features
|
#### Features
|
||||||
- **Select** a device and agent context (CURRENTUSER vs SYSTEM).
|
- **Select** a site, then a device, then finally an agent context (CURRENTUSER vs SYSTEM).
|
||||||
- **Connect/Disconnect** from the agent at any time.
|
- **Connect/Disconnect** from the agent at any time.
|
||||||
- **Attach roles** (by connecting "Agent Role" nodes to this node's output handle) to assign behaviors dynamically.
|
- **Attach roles** (by connecting "Agent Role" nodes to this node's output handle) to assign behaviors dynamically.
|
||||||
- **Live status** shows if the agent is available, connected, or offline.
|
|
||||||
|
|
||||||
#### How to Use
|
#### How to Use
|
||||||
1. **Drag in a Borealis Agent node.**
|
1. **Drag and drop in a Borealis Agent node.**
|
||||||
2. **Pick an agent** from the dropdown list (auto-populates from backend).
|
2. **Pick an agent** from the dropdown list (auto-populates from API backend).
|
||||||
3. **Click "Connect to Agent"** to provision it for the workflow.
|
3. **Click "Connect to Agent"**.
|
||||||
4. **Attach Agent Role Nodes** (e.g., Screenshot, Macro Keypress) to the "provisioner" output handle to define what the agent should do.
|
4. **Attach Agent Role Nodes** (e.g., Screenshot, Macro Keypress) to the "provisioner" output handle to define what the agent should do.
|
||||||
5. Agent will automatically update its roles as you change connected Role Nodes.
|
5. Agent will automatically update its roles as you change connected Role Nodes.
|
||||||
|
|
||||||
#### Output Handle
|
|
||||||
- "provisioner" (bottom): Connect Agent Role nodes here.
|
|
||||||
|
|
||||||
#### Good to Know
|
#### Good to Know
|
||||||
- If an agent disconnects or goes offline, its status will show "Reconnecting..." until it returns.
|
- If an agent disconnects or goes offline, its status will show "Reconnecting..." until it returns.
|
||||||
- Node config can be edited in the right sidebar.
|
|
||||||
- **Roles update LIVE**: Any time you change attached roles, the agent gets updated instantly.
|
- **Roles update LIVE**: Any time you change attached roles, the agent gets updated instantly.
|
||||||
|
|
||||||
`.trim()
|
`.trim()
|
||||||
|
|||||||
Reference in New Issue
Block a user