@monoes/monobrowse 1.0.9 → 1.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/src/__tests__/adapters.test.js +5 -2
- package/dist/src/__tests__/adapters.test.js.map +1 -1
- package/dist/src/__tests__/batch-eval-parsing.test.js +4 -2
- package/dist/src/__tests__/batch-eval-parsing.test.js.map +1 -1
- package/dist/src/__tests__/box-model.test.js +9 -5
- package/dist/src/__tests__/box-model.test.js.map +1 -1
- package/dist/src/__tests__/browser-launch.test.d.ts +10 -0
- package/dist/src/__tests__/browser-launch.test.d.ts.map +1 -1
- package/dist/src/__tests__/browser-launch.test.js +17 -10
- package/dist/src/__tests__/browser-launch.test.js.map +1 -1
- package/dist/src/__tests__/cdp-client.test.js +9 -3
- package/dist/src/__tests__/cdp-client.test.js.map +1 -1
- package/dist/src/__tests__/close-browser.test.d.ts +13 -0
- package/dist/src/__tests__/close-browser.test.d.ts.map +1 -1
- package/dist/src/__tests__/close-browser.test.js +40 -26
- package/dist/src/__tests__/close-browser.test.js.map +1 -1
- package/dist/src/__tests__/console-log.test.js +17 -6
- package/dist/src/__tests__/console-log.test.js.map +1 -1
- package/dist/src/__tests__/find.test.js +26 -6
- package/dist/src/__tests__/find.test.js.map +1 -1
- package/dist/src/__tests__/frame-command.test.js +9 -3
- package/dist/src/__tests__/frame-command.test.js.map +1 -1
- package/dist/src/__tests__/open-url-navigate-error.test.js +1 -3
- package/dist/src/__tests__/open-url-navigate-error.test.js.map +1 -1
- package/dist/src/__tests__/output.test.js +1 -1
- package/dist/src/__tests__/output.test.js.map +1 -1
- package/dist/src/__tests__/reap-idle-browser.test.d.ts +15 -0
- package/dist/src/__tests__/reap-idle-browser.test.d.ts.map +1 -0
- package/dist/src/__tests__/reap-idle-browser.test.js +238 -0
- package/dist/src/__tests__/reap-idle-browser.test.js.map +1 -0
- package/dist/src/__tests__/ref-cache.test.js +18 -10
- package/dist/src/__tests__/ref-cache.test.js.map +1 -1
- package/dist/src/__tests__/session-state.test.d.ts +8 -0
- package/dist/src/__tests__/session-state.test.d.ts.map +1 -1
- package/dist/src/__tests__/session-state.test.js +10 -10
- package/dist/src/__tests__/session-state.test.js.map +1 -1
- package/dist/src/__tests__/snapshot.test.js +45 -15
- package/dist/src/__tests__/snapshot.test.js.map +1 -1
- package/dist/src/__tests__/storage-eval.test.js +14 -6
- package/dist/src/__tests__/storage-eval.test.js.map +1 -1
- package/dist/src/__tests__/tabs.test.js +25 -9
- package/dist/src/__tests__/tabs.test.js.map +1 -1
- package/dist/src/browser/action-builder/analyzer.d.ts +1 -1
- package/dist/src/browser/action-builder/analyzer.d.ts.map +1 -1
- package/dist/src/browser/action-builder/analyzer.js +25 -9
- package/dist/src/browser/action-builder/analyzer.js.map +1 -1
- package/dist/src/browser/action-builder/types.d.ts.map +1 -1
- package/dist/src/browser/actions.d.ts +1 -1
- package/dist/src/browser/actions.d.ts.map +1 -1
- package/dist/src/browser/actions.js +16 -6
- package/dist/src/browser/actions.js.map +1 -1
- package/dist/src/browser/adapters/gemini.d.ts.map +1 -1
- package/dist/src/browser/adapters/gemini.js.map +1 -1
- package/dist/src/browser/adapters/google.d.ts.map +1 -1
- package/dist/src/browser/adapters/google.js.map +1 -1
- package/dist/src/browser/adapters/index.d.ts.map +1 -1
- package/dist/src/browser/adapters/index.js +3 -3
- package/dist/src/browser/adapters/index.js.map +1 -1
- package/dist/src/browser/adapters/instagram.d.ts.map +1 -1
- package/dist/src/browser/adapters/instagram.js.map +1 -1
- package/dist/src/browser/adapters/linkedin.d.ts.map +1 -1
- package/dist/src/browser/adapters/linkedin.js.map +1 -1
- package/dist/src/browser/adapters/microsoft.d.ts.map +1 -1
- package/dist/src/browser/adapters/microsoft.js.map +1 -1
- package/dist/src/browser/adapters/x.d.ts.map +1 -1
- package/dist/src/browser/adapters/x.js.map +1 -1
- package/dist/src/browser/browser.d.ts +30 -0
- package/dist/src/browser/browser.d.ts.map +1 -1
- package/dist/src/browser/browser.js +122 -15
- package/dist/src/browser/browser.js.map +1 -1
- package/dist/src/browser/cdp.d.ts.map +1 -1
- package/dist/src/browser/cdp.js +21 -8
- package/dist/src/browser/cdp.js.map +1 -1
- package/dist/src/browser/console-log.d.ts.map +1 -1
- package/dist/src/browser/console-log.js +2 -2
- package/dist/src/browser/console-log.js.map +1 -1
- package/dist/src/browser/dashboard/server.d.ts.map +1 -1
- package/dist/src/browser/dashboard/server.js +29 -19
- package/dist/src/browser/dashboard/server.js.map +1 -1
- package/dist/src/browser/dashboard/ui.html +153 -165
- package/dist/src/browser/dialog.d.ts.map +1 -1
- package/dist/src/browser/dialog.js +3 -1
- package/dist/src/browser/dialog.js.map +1 -1
- package/dist/src/browser/emulation.d.ts.map +1 -1
- package/dist/src/browser/emulation.js +21 -6
- package/dist/src/browser/emulation.js.map +1 -1
- package/dist/src/browser/find.d.ts +4 -4
- package/dist/src/browser/find.d.ts.map +1 -1
- package/dist/src/browser/find.js +14 -6
- package/dist/src/browser/find.js.map +1 -1
- package/dist/src/browser/har.d.ts.map +1 -1
- package/dist/src/browser/har.js +14 -7
- package/dist/src/browser/har.js.map +1 -1
- package/dist/src/browser/index.d.ts +16 -16
- package/dist/src/browser/index.d.ts.map +1 -1
- package/dist/src/browser/index.js +16 -16
- package/dist/src/browser/index.js.map +1 -1
- package/dist/src/browser/network.d.ts.map +1 -1
- package/dist/src/browser/network.js +14 -4
- package/dist/src/browser/network.js.map +1 -1
- package/dist/src/browser/pdf.d.ts.map +1 -1
- package/dist/src/browser/pdf.js +3 -3
- package/dist/src/browser/pdf.js.map +1 -1
- package/dist/src/browser/profiler.d.ts.map +1 -1
- package/dist/src/browser/profiler.js +10 -5
- package/dist/src/browser/profiler.js.map +1 -1
- package/dist/src/browser/record.d.ts +1 -1
- package/dist/src/browser/record.d.ts.map +1 -1
- package/dist/src/browser/record.js +18 -7
- package/dist/src/browser/record.js.map +1 -1
- package/dist/src/browser/ref-cache.d.ts.map +1 -1
- package/dist/src/browser/ref-cache.js +9 -4
- package/dist/src/browser/ref-cache.js.map +1 -1
- package/dist/src/browser/screenshot.d.ts.map +1 -1
- package/dist/src/browser/screenshot.js +11 -7
- package/dist/src/browser/screenshot.js.map +1 -1
- package/dist/src/browser/session.d.ts +1 -1
- package/dist/src/browser/session.d.ts.map +1 -1
- package/dist/src/browser/session.js +27 -8
- package/dist/src/browser/session.js.map +1 -1
- package/dist/src/browser/snapshot.d.ts +1 -1
- package/dist/src/browser/snapshot.d.ts.map +1 -1
- package/dist/src/browser/snapshot.js +8 -4
- package/dist/src/browser/snapshot.js.map +1 -1
- package/dist/src/browser/storage.d.ts.map +1 -1
- package/dist/src/browser/storage.js.map +1 -1
- package/dist/src/browser/tabs.d.ts.map +1 -1
- package/dist/src/browser/tabs.js +9 -3
- package/dist/src/browser/tabs.js.map +1 -1
- package/dist/src/browser/trace.d.ts.map +1 -1
- package/dist/src/browser/trace.js +3 -3
- package/dist/src/browser/trace.js.map +1 -1
- package/dist/src/browser/types.d.ts.map +1 -1
- package/dist/src/browser/types.js +23 -4
- package/dist/src/browser/types.js.map +1 -1
- package/dist/src/browser/vitals.d.ts.map +1 -1
- package/dist/src/browser/vitals.js +1 -1
- package/dist/src/browser/vitals.js.map +1 -1
- package/dist/src/browser/wait.d.ts.map +1 -1
- package/dist/src/browser/wait.js +11 -5
- package/dist/src/browser/wait.js.map +1 -1
- package/dist/src/cli/action.d.ts.map +1 -1
- package/dist/src/cli/action.js +14 -11
- package/dist/src/cli/action.js.map +1 -1
- package/dist/src/cli/commands.d.ts.map +1 -1
- package/dist/src/cli/commands.js +453 -159
- package/dist/src/cli/commands.js.map +1 -1
- package/dist/src/cli/output.js +5 -5
- package/dist/src/cli/output.js.map +1 -1
- package/dist/src/cli/platform.d.ts.map +1 -1
- package/dist/src/cli/platform.js +15 -11
- package/dist/src/cli/platform.js.map +1 -1
- package/dist/src/cli/types.d.ts +1 -1
- package/dist/src/cli/types.d.ts.map +1 -1
- package/dist/src/cli.js +21 -9
- package/dist/src/cli.js.map +1 -1
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -2
- package/dist/src/index.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/__tests__/adapters.test.ts +15 -8
- package/src/__tests__/batch-eval-parsing.test.ts +4 -2
- package/src/__tests__/box-model.test.ts +9 -5
- package/src/__tests__/browser-launch.test.ts +26 -15
- package/src/__tests__/cdp-client.test.ts +25 -9
- package/src/__tests__/close-browser.test.ts +49 -26
- package/src/__tests__/console-log.test.ts +36 -13
- package/src/__tests__/find.test.ts +58 -20
- package/src/__tests__/frame-command.test.ts +12 -6
- package/src/__tests__/open-url-navigate-error.test.ts +2 -4
- package/src/__tests__/output.test.ts +1 -1
- package/src/__tests__/reap-idle-browser.test.ts +276 -0
- package/src/__tests__/ref-cache.test.ts +25 -12
- package/src/__tests__/session-state.test.ts +40 -28
- package/src/__tests__/snapshot.test.ts +61 -22
- package/src/__tests__/storage-eval.test.ts +30 -16
- package/src/__tests__/tabs.test.ts +26 -10
- package/src/browser/action-builder/analyzer.ts +39 -15
- package/src/browser/action-builder/types.ts +45 -8
- package/src/browser/actions.ts +288 -123
- package/src/browser/adapters/gemini.ts +7 -3
- package/src/browser/adapters/google.ts +5 -3
- package/src/browser/adapters/index.ts +5 -4
- package/src/browser/adapters/instagram.ts +4 -2
- package/src/browser/adapters/linkedin.ts +4 -2
- package/src/browser/adapters/microsoft.ts +9 -5
- package/src/browser/adapters/x.ts +7 -3
- package/src/browser/browser.ts +188 -42
- package/src/browser/cdp.ts +43 -11
- package/src/browser/console-log.ts +24 -7
- package/src/browser/dashboard/server.ts +44 -25
- package/src/browser/dashboard/ui.html +153 -165
- package/src/browser/dialog.ts +25 -8
- package/src/browser/emulation.ts +86 -36
- package/src/browser/find.ts +110 -47
- package/src/browser/har.ts +58 -26
- package/src/browser/index.ts +16 -16
- package/src/browser/network.ts +147 -46
- package/src/browser/pdf.ts +4 -4
- package/src/browser/profiler.ts +23 -10
- package/src/browser/record.ts +38 -19
- package/src/browser/ref-cache.ts +38 -17
- package/src/browser/screenshot.ts +58 -32
- package/src/browser/session.ts +51 -18
- package/src/browser/snapshot.ts +34 -20
- package/src/browser/storage.ts +73 -17
- package/src/browser/tabs.ts +29 -17
- package/src/browser/trace.ts +14 -10
- package/src/browser/types.ts +23 -4
- package/src/browser/vitals.ts +33 -16
- package/src/browser/wait.ts +47 -21
- package/src/cli/action.ts +52 -21
- package/src/cli/commands.ts +1093 -385
- package/src/cli/output.ts +5 -5
- package/src/cli/platform.ts +44 -23
- package/src/cli/types.ts +1 -1
- package/src/cli.ts +34 -18
- package/src/index.ts +2 -2
|
@@ -569,33 +569,33 @@ body { background: #0b0b14; color: #ccc; font-family: system-ui,-apple-system,sa
|
|
|
569
569
|
// NAV
|
|
570
570
|
// ═══════════════════════════════════════════════════════════════════
|
|
571
571
|
function navigateToPanel(target) {
|
|
572
|
-
document.querySelectorAll('.nav-item').forEach(
|
|
573
|
-
var navItem = document.querySelector(
|
|
572
|
+
document.querySelectorAll('.nav-item').forEach((n) => { n.classList.remove('active'); });
|
|
573
|
+
var navItem = document.querySelector(`[data-panel="${target}"]`);
|
|
574
574
|
if (navItem) navItem.classList.add('active');
|
|
575
|
-
document.querySelectorAll('.panel').forEach(
|
|
575
|
+
document.querySelectorAll('.panel').forEach((p) => { p.classList.remove('active'); });
|
|
576
576
|
var panelId = target === 'playbooks' ? 'pb-panel'
|
|
577
577
|
: target === 'builder' ? 'builder-panel'
|
|
578
|
-
: target
|
|
578
|
+
: `${target}-panel`;
|
|
579
579
|
var panel = document.getElementById(panelId);
|
|
580
580
|
if (panel) panel.classList.add('active');
|
|
581
581
|
if (target === 'builder') renderEdges();
|
|
582
582
|
location.hash = target;
|
|
583
583
|
}
|
|
584
584
|
|
|
585
|
-
document.querySelectorAll('.nav-item').forEach(
|
|
586
|
-
item.addEventListener('click',
|
|
585
|
+
document.querySelectorAll('.nav-item').forEach((item) => {
|
|
586
|
+
item.addEventListener('click', () => {
|
|
587
587
|
var target = item.getAttribute('data-panel');
|
|
588
588
|
navigateToPanel(target);
|
|
589
589
|
});
|
|
590
590
|
});
|
|
591
591
|
|
|
592
592
|
// Hash-based routing on load
|
|
593
|
-
(
|
|
593
|
+
(() => {
|
|
594
594
|
var hash = location.hash.replace('#', '');
|
|
595
595
|
var validPanels = ['playbooks', 'builder', 'agents', 'memory', 'sessions'];
|
|
596
596
|
if (hash && validPanels.indexOf(hash) !== -1) {
|
|
597
597
|
// defer until after renderEdges is defined
|
|
598
|
-
window.addEventListener('load',
|
|
598
|
+
window.addEventListener('load', () => { navigateToPanel(hash); });
|
|
599
599
|
}
|
|
600
600
|
})();
|
|
601
601
|
|
|
@@ -620,11 +620,11 @@ function updateHeader() {
|
|
|
620
620
|
else if (s === 'completed') done++;
|
|
621
621
|
else if (s === 'failed') fail++;
|
|
622
622
|
}
|
|
623
|
-
document.getElementById('p-run').textContent =
|
|
624
|
-
document.getElementById('p-done').textContent =
|
|
625
|
-
document.getElementById('p-fail').textContent =
|
|
623
|
+
document.getElementById('p-run').textContent = `● ${running}`;
|
|
624
|
+
document.getElementById('p-done').textContent = `✓ ${done}`;
|
|
625
|
+
document.getElementById('p-fail').textContent = `✗ ${fail}`;
|
|
626
626
|
document.getElementById('nb-run').textContent = String(running || Object.keys(runs).length);
|
|
627
|
-
document.getElementById('nb-run').className =
|
|
627
|
+
document.getElementById('nb-run').className = `nav-badge${running ? '' : ' green'}`;
|
|
628
628
|
var total = Object.keys(runs).length;
|
|
629
629
|
document.getElementById('pb-subtitle').textContent = total + (total === 1 ? ' run' : ' runs');
|
|
630
630
|
}
|
|
@@ -641,25 +641,25 @@ function statusLabel(r) {
|
|
|
641
641
|
}
|
|
642
642
|
function elapsed(r) {
|
|
643
643
|
var ms = (r.endedAt || r.completedAt || Date.now()) - r.startedAt;
|
|
644
|
-
if (ms < 1000) return ms
|
|
645
|
-
if (ms < 60000) return (ms / 1000).toFixed(1)
|
|
646
|
-
return Math.floor(ms / 60000)
|
|
644
|
+
if (ms < 1000) return `${ms}ms`;
|
|
645
|
+
if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
|
|
646
|
+
return `${Math.floor(ms / 60000)}m ${Math.floor((ms % 60000) / 1000)}s`;
|
|
647
647
|
}
|
|
648
648
|
function renderCards() {
|
|
649
649
|
var cards = document.getElementById('pb-cards');
|
|
650
650
|
var empty = document.getElementById('pb-empty');
|
|
651
|
-
var sorted = Object.values(runs).sort(
|
|
651
|
+
var sorted = Object.values(runs).sort((a, b) => b.startedAt - a.startedAt);
|
|
652
652
|
if (!sorted.length) { cards.innerHTML = ''; empty.style.display = 'flex'; return; }
|
|
653
653
|
empty.style.display = 'none';
|
|
654
|
-
cards.innerHTML = sorted.map(
|
|
654
|
+
cards.innerHTML = sorted.map((r) => {
|
|
655
655
|
var sl = statusLabel(r);
|
|
656
656
|
var isRunning = r.status === 'running';
|
|
657
657
|
var pct = progressPct(r);
|
|
658
658
|
var logLines = (runLogs[r.id] || []).slice(-4);
|
|
659
659
|
var logHtml = logLines.length
|
|
660
|
-
? logLines.map(
|
|
660
|
+
? logLines.map((l) => {
|
|
661
661
|
var cls = l.type === 'ok' ? 'log-ok' : l.type === 'err' ? 'log-err' : 'log-info';
|
|
662
|
-
return
|
|
662
|
+
return `<div><span class="log-ts">${esc(l.ts)}</span> <span class="${cls}">${esc(l.text)}</span></div>`;
|
|
663
663
|
}).join('')
|
|
664
664
|
: '<div style="color:#2a2a3a">no events yet</div>';
|
|
665
665
|
return '<div class="card' + (!isRunning ? ' done' : '') + '">' +
|
|
@@ -671,14 +671,14 @@ function renderCards() {
|
|
|
671
671
|
'<span class="mitem">⏱ ' + esc(elapsed(r)) + '</span>' +
|
|
672
672
|
'<span class="mitem">📦 ' + esc(r.itemsProcessed) + '/' + esc(r.itemsTotal || '?') + '</span>' +
|
|
673
673
|
'<span class="mitem" style="color:#3a3a5a">id: ' + esc(r.id.slice(0,8)) + '</span>' +
|
|
674
|
-
(isRunning ?
|
|
674
|
+
(isRunning ? `<button class="stop-btn" onclick="stopRun('${esc(r.id)}')">■ stop</button>` : '') +
|
|
675
675
|
'</div>' +
|
|
676
676
|
'<div class="progress-track"><div class="progress-fill" style="width:' + pct + '%"></div></div>' +
|
|
677
677
|
'<div class="log-area">' + logHtml + '</div>' +
|
|
678
678
|
'</div>';
|
|
679
679
|
}).join('');
|
|
680
680
|
}
|
|
681
|
-
function
|
|
681
|
+
function _stopRun(id) { fetch(`/stop/${id}`, { method: 'POST', headers: { 'x-monobrowse-token': (window.__MONOBROWSE_TOKEN__ || '') } }).catch(() => {}); }
|
|
682
682
|
|
|
683
683
|
// ═══════════════════════════════════════════════════════════════════
|
|
684
684
|
// NODE PALETTE DEFINITIONS
|
|
@@ -921,11 +921,11 @@ var canvas = {
|
|
|
921
921
|
};
|
|
922
922
|
var _nodeSeq = 1;
|
|
923
923
|
|
|
924
|
-
function uid() { return
|
|
924
|
+
function uid() { return `n${_nodeSeq++}_${Date.now().toString(36)}`; }
|
|
925
925
|
|
|
926
926
|
// Find node type def
|
|
927
927
|
function getNodeDef(type) {
|
|
928
|
-
return NODE_TYPES.find(
|
|
928
|
+
return NODE_TYPES.find((t) => t.type === type) || null;
|
|
929
929
|
}
|
|
930
930
|
|
|
931
931
|
// ═══════════════════════════════════════════════════════════════════
|
|
@@ -935,13 +935,11 @@ function buildPalette(filter) {
|
|
|
935
935
|
filter = (filter || '').toLowerCase();
|
|
936
936
|
var list = document.getElementById('palette-list');
|
|
937
937
|
var html = '';
|
|
938
|
-
CAT_ORDER.forEach(
|
|
939
|
-
var nodes = NODE_TYPES.filter(
|
|
940
|
-
return n.cat === cat && (!filter || n.label.toLowerCase().includes(filter) || n.type.toLowerCase().includes(filter));
|
|
941
|
-
});
|
|
938
|
+
CAT_ORDER.forEach((cat) => {
|
|
939
|
+
var nodes = NODE_TYPES.filter((n) => n.cat === cat && (!filter || n.label.toLowerCase().includes(filter) || n.type.toLowerCase().includes(filter)));
|
|
942
940
|
if (!nodes.length) return;
|
|
943
|
-
html +=
|
|
944
|
-
nodes.forEach(
|
|
941
|
+
html += `<div class="palette-cat-label">${cat}</div>`;
|
|
942
|
+
nodes.forEach((n) => {
|
|
945
943
|
html += '<div class="palette-node" draggable="true" data-type="' + n.type + '" data-label="' + esc(n.label) + '">' +
|
|
946
944
|
'<div class="palette-dot" style="background:' + n.color + '"></div>' +
|
|
947
945
|
'<span>' + esc(n.label) + '</span></div>';
|
|
@@ -949,15 +947,15 @@ function buildPalette(filter) {
|
|
|
949
947
|
});
|
|
950
948
|
list.innerHTML = html;
|
|
951
949
|
|
|
952
|
-
list.querySelectorAll('.palette-node').forEach(
|
|
953
|
-
el.addEventListener('dragstart',
|
|
950
|
+
list.querySelectorAll('.palette-node').forEach((el) => {
|
|
951
|
+
el.addEventListener('dragstart', (e) => {
|
|
954
952
|
e.dataTransfer.setData('text/plain', el.getAttribute('data-type'));
|
|
955
953
|
e.dataTransfer.effectAllowed = 'copy';
|
|
956
954
|
});
|
|
957
955
|
});
|
|
958
956
|
}
|
|
959
957
|
|
|
960
|
-
document.getElementById('palette-search').addEventListener('input',
|
|
958
|
+
document.getElementById('palette-search').addEventListener('input', (e) => {
|
|
961
959
|
buildPalette(e.target.value);
|
|
962
960
|
});
|
|
963
961
|
buildPalette('');
|
|
@@ -967,7 +965,7 @@ buildPalette('');
|
|
|
967
965
|
// ═══════════════════════════════════════════════════════════════════
|
|
968
966
|
function applyTransform() {
|
|
969
967
|
document.getElementById('canvas-nodes').style.transform =
|
|
970
|
-
|
|
968
|
+
`translate(${canvas.panX}px,${canvas.panY}px) scale(${canvas.zoom})`;
|
|
971
969
|
renderEdges();
|
|
972
970
|
}
|
|
973
971
|
|
|
@@ -995,7 +993,7 @@ document.getElementById('canvas-wrap').addEventListener('mousedown', function(e)
|
|
|
995
993
|
e.preventDefault();
|
|
996
994
|
}
|
|
997
995
|
});
|
|
998
|
-
document.addEventListener('mousemove',
|
|
996
|
+
document.addEventListener('mousemove', (e) => {
|
|
999
997
|
if (_panning) {
|
|
1000
998
|
canvas.panX = e.clientX - _panStart.x;
|
|
1001
999
|
canvas.panY = e.clientY - _panStart.y;
|
|
@@ -1004,7 +1002,7 @@ document.addEventListener('mousemove', function(e) {
|
|
|
1004
1002
|
if (_dragEdge.active) updateDragEdge(e);
|
|
1005
1003
|
if (_draggingNode) updateNodeDrag(e);
|
|
1006
1004
|
});
|
|
1007
|
-
document.addEventListener('mouseup',
|
|
1005
|
+
document.addEventListener('mouseup', (e) => {
|
|
1008
1006
|
_panning = false;
|
|
1009
1007
|
if (_draggingNode) endNodeDrag(e);
|
|
1010
1008
|
if (_dragEdge.active) endDragEdge(e);
|
|
@@ -1020,7 +1018,7 @@ document.getElementById('canvas-wrap').addEventListener('mousedown', function(e)
|
|
|
1020
1018
|
});
|
|
1021
1019
|
|
|
1022
1020
|
// Drop from palette
|
|
1023
|
-
document.getElementById('canvas-wrap').addEventListener('dragover',
|
|
1021
|
+
document.getElementById('canvas-wrap').addEventListener('dragover', (e) => { e.preventDefault(); });
|
|
1024
1022
|
document.getElementById('canvas-wrap').addEventListener('drop', function(e) {
|
|
1025
1023
|
e.preventDefault();
|
|
1026
1024
|
var type = e.dataTransfer.getData('text/plain');
|
|
@@ -1034,15 +1032,15 @@ document.getElementById('canvas-wrap').addEventListener('drop', function(e) {
|
|
|
1034
1032
|
});
|
|
1035
1033
|
|
|
1036
1034
|
// Zoom buttons
|
|
1037
|
-
document.getElementById('btn-zoom-in').addEventListener('click',
|
|
1035
|
+
document.getElementById('btn-zoom-in').addEventListener('click', () => {
|
|
1038
1036
|
canvas.zoom = Math.min(3, canvas.zoom * 1.2);
|
|
1039
1037
|
applyTransform();
|
|
1040
1038
|
});
|
|
1041
|
-
document.getElementById('btn-zoom-out').addEventListener('click',
|
|
1039
|
+
document.getElementById('btn-zoom-out').addEventListener('click', () => {
|
|
1042
1040
|
canvas.zoom = Math.max(0.2, canvas.zoom / 1.2);
|
|
1043
1041
|
applyTransform();
|
|
1044
1042
|
});
|
|
1045
|
-
document.getElementById('btn-fit').addEventListener('click',
|
|
1043
|
+
document.getElementById('btn-fit').addEventListener('click', () => {
|
|
1046
1044
|
fitCanvas();
|
|
1047
1045
|
});
|
|
1048
1046
|
|
|
@@ -1051,7 +1049,7 @@ function fitCanvas() {
|
|
|
1051
1049
|
var wrap = document.getElementById('canvas-wrap');
|
|
1052
1050
|
var W = wrap.clientWidth, H = wrap.clientHeight;
|
|
1053
1051
|
var minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
1054
|
-
canvas.nodes.forEach(
|
|
1052
|
+
canvas.nodes.forEach((n) => {
|
|
1055
1053
|
minX = Math.min(minX, n.x); minY = Math.min(minY, n.y);
|
|
1056
1054
|
maxX = Math.max(maxX, n.x + 200); maxY = Math.max(maxY, n.y + nodeH(n));
|
|
1057
1055
|
});
|
|
@@ -1096,8 +1094,8 @@ function addNode(type, x, y) {
|
|
|
1096
1094
|
}
|
|
1097
1095
|
|
|
1098
1096
|
function removeNode(id) {
|
|
1099
|
-
canvas.nodes = canvas.nodes.filter(
|
|
1100
|
-
canvas.edges = canvas.edges.filter(
|
|
1097
|
+
canvas.nodes = canvas.nodes.filter((n) => n.id !== id);
|
|
1098
|
+
canvas.edges = canvas.edges.filter((e) => e.from !== id && e.to !== id);
|
|
1101
1099
|
if (canvas.selectedId === id) { canvas.selectedId = null; renderPropsPanel(); }
|
|
1102
1100
|
renderNodes();
|
|
1103
1101
|
renderEdges();
|
|
@@ -1112,7 +1110,7 @@ var _dragNodeStart = null;
|
|
|
1112
1110
|
function startNodeDrag(e, nodeId) {
|
|
1113
1111
|
e.stopPropagation();
|
|
1114
1112
|
_draggingNode = nodeId;
|
|
1115
|
-
var n = canvas.nodes.find(
|
|
1113
|
+
var n = canvas.nodes.find((n) => n.id === nodeId);
|
|
1116
1114
|
_dragNodeStart = {
|
|
1117
1115
|
mx: e.clientX, my: e.clientY,
|
|
1118
1116
|
nx: n.x, ny: n.y,
|
|
@@ -1125,7 +1123,7 @@ function updateNodeDrag(e) {
|
|
|
1125
1123
|
if (!_draggingNode) return;
|
|
1126
1124
|
var dx = (e.clientX - _dragNodeStart.mx) / canvas.zoom;
|
|
1127
1125
|
var dy = (e.clientY - _dragNodeStart.my) / canvas.zoom;
|
|
1128
|
-
var n = canvas.nodes.find(
|
|
1126
|
+
var n = canvas.nodes.find((n) => n.id === _draggingNode);
|
|
1129
1127
|
if (n) { n.x = _dragNodeStart.nx + dx; n.y = _dragNodeStart.ny + dy; }
|
|
1130
1128
|
renderNodePosition(_draggingNode);
|
|
1131
1129
|
renderEdges();
|
|
@@ -1135,9 +1133,9 @@ function endNodeDrag(e) {
|
|
|
1135
1133
|
_dragNodeStart = null;
|
|
1136
1134
|
}
|
|
1137
1135
|
function renderNodePosition(id) {
|
|
1138
|
-
var el = document.querySelector(
|
|
1139
|
-
var n = canvas.nodes.find(
|
|
1140
|
-
if (el && n) { el.style.left = n.x
|
|
1136
|
+
var el = document.querySelector(`[data-node-id="${id}"]`);
|
|
1137
|
+
var n = canvas.nodes.find((n) => n.id === id);
|
|
1138
|
+
if (el && n) { el.style.left = `${n.x}px`; el.style.top = `${n.y}px`; }
|
|
1141
1139
|
}
|
|
1142
1140
|
|
|
1143
1141
|
// ═══════════════════════════════════════════════════════════════════
|
|
@@ -1164,7 +1162,7 @@ function updateDragEdge(e) {
|
|
|
1164
1162
|
var sx = _dragEdge.x, sy = _dragEdge.y;
|
|
1165
1163
|
var dx = Math.max(60, Math.abs(tx - sx) * 0.5);
|
|
1166
1164
|
document.getElementById('drag-edge').setAttribute('d',
|
|
1167
|
-
|
|
1165
|
+
`M${sx},${sy} C${sx+dx},${sy} ${tx-dx},${ty} ${tx},${ty}`);
|
|
1168
1166
|
}
|
|
1169
1167
|
function endDragEdge(e) {
|
|
1170
1168
|
document.getElementById('drag-edge').style.display = 'none';
|
|
@@ -1174,13 +1172,11 @@ function dropOnInputPort(e, toNodeId, toPortIdx) {
|
|
|
1174
1172
|
if (!_dragEdge.active) return;
|
|
1175
1173
|
e.stopPropagation();
|
|
1176
1174
|
if (_dragEdge.fromNodeId === toNodeId) { endDragEdge(e); return; }
|
|
1177
|
-
var fromNode = canvas.nodes.find(
|
|
1175
|
+
var fromNode = canvas.nodes.find((n) => n.id === _dragEdge.fromNodeId);
|
|
1178
1176
|
var def = fromNode && (fromNode._def || getNodeDef(fromNode.type));
|
|
1179
|
-
var handle = def
|
|
1177
|
+
var handle = def?.outputs[_dragEdge.fromPortIdx] ? def.outputs[_dragEdge.fromPortIdx].label : undefined;
|
|
1180
1178
|
// Remove existing edge from same out port if any
|
|
1181
|
-
canvas.edges = canvas.edges.filter(
|
|
1182
|
-
return !(ed.from === _dragEdge.fromNodeId && ed.fromPort === _dragEdge.fromPortIdx && ed.to === toNodeId && ed.toPort === toPortIdx);
|
|
1183
|
-
});
|
|
1179
|
+
canvas.edges = canvas.edges.filter((ed) => !(ed.from === _dragEdge.fromNodeId && ed.fromPort === _dragEdge.fromPortIdx && ed.to === toNodeId && ed.toPort === toPortIdx));
|
|
1184
1180
|
canvas.edges.push({
|
|
1185
1181
|
id: uid(),
|
|
1186
1182
|
from: _dragEdge.fromNodeId,
|
|
@@ -1194,12 +1190,12 @@ function dropOnInputPort(e, toNodeId, toPortIdx) {
|
|
|
1194
1190
|
}
|
|
1195
1191
|
|
|
1196
1192
|
function getOutPortPos(nodeId, portIdx) {
|
|
1197
|
-
var n = canvas.nodes.find(
|
|
1193
|
+
var n = canvas.nodes.find((n) => n.id === nodeId);
|
|
1198
1194
|
if (!n) return {x:0,y:0};
|
|
1199
1195
|
return { x: n.x + 200, y: n.y + 40 + 6 + portIdx * 26 + 13 };
|
|
1200
1196
|
}
|
|
1201
|
-
function
|
|
1202
|
-
var n = canvas.nodes.find(
|
|
1197
|
+
function _getInPortPos(nodeId, portIdx) {
|
|
1198
|
+
var n = canvas.nodes.find((n) => n.id === nodeId);
|
|
1203
1199
|
if (!n) return {x:0,y:0};
|
|
1204
1200
|
return { x: n.x, y: n.y + 40 + 6 + portIdx * 26 + 13 };
|
|
1205
1201
|
}
|
|
@@ -1210,14 +1206,14 @@ function getInPortPos(nodeId, portIdx) {
|
|
|
1210
1206
|
function renderNodes() {
|
|
1211
1207
|
var container = document.getElementById('canvas-nodes');
|
|
1212
1208
|
var existingIds = new Set();
|
|
1213
|
-
canvas.nodes.forEach(
|
|
1209
|
+
canvas.nodes.forEach((n) => { existingIds.add(n.id); });
|
|
1214
1210
|
// Remove nodes that no longer exist
|
|
1215
|
-
container.querySelectorAll('[data-node-id]').forEach(
|
|
1211
|
+
container.querySelectorAll('[data-node-id]').forEach((el) => {
|
|
1216
1212
|
if (!existingIds.has(el.getAttribute('data-node-id'))) el.remove();
|
|
1217
1213
|
});
|
|
1218
1214
|
// Add or update each node
|
|
1219
|
-
canvas.nodes.forEach(
|
|
1220
|
-
var existing = container.querySelector(
|
|
1215
|
+
canvas.nodes.forEach((n) => {
|
|
1216
|
+
var existing = container.querySelector(`[data-node-id="${n.id}"]`);
|
|
1221
1217
|
var def = n._def || getNodeDef(n.type) || { inputs:[{label:'in'}], outputs:[{label:'out'}], color:'#7c3aed' };
|
|
1222
1218
|
var color = def.color || '#7c3aed';
|
|
1223
1219
|
var rows = Math.max(def.inputs.length, def.outputs.length, 1);
|
|
@@ -1226,21 +1222,21 @@ function renderNodes() {
|
|
|
1226
1222
|
|
|
1227
1223
|
if (!existing) {
|
|
1228
1224
|
var el = document.createElement('div');
|
|
1229
|
-
el.className =
|
|
1225
|
+
el.className = `cn${isSelected ? ' selected' : ''}${n.runStatus === 'running' ? ' running' : ''}`;
|
|
1230
1226
|
el.setAttribute('data-node-id', n.id);
|
|
1231
|
-
el.style.left = n.x
|
|
1232
|
-
el.style.top = n.y
|
|
1233
|
-
el.style.height = h
|
|
1227
|
+
el.style.left = `${n.x}px`;
|
|
1228
|
+
el.style.top = `${n.y}px`;
|
|
1229
|
+
el.style.height = `${h}px`;
|
|
1234
1230
|
el.style.borderColor = isSelected ? color : (n.runStatus === 'error' ? '#ef444444' : n.runStatus === 'ok' ? '#10b98133' : 'rgba(124,58,237,0.15)');
|
|
1235
1231
|
|
|
1236
1232
|
// Status badge
|
|
1237
1233
|
var statusHtml = '';
|
|
1238
1234
|
if (n.runStatus) {
|
|
1239
|
-
var statusText = n.runStatus === 'ok' ?
|
|
1235
|
+
var statusText = n.runStatus === 'ok' ? `✓ ${n.runItems} item${n.runItems !== 1 ? 's' : ''}${n.runDuration ? ` · ${n.runDuration}ms` : ''}`
|
|
1240
1236
|
: n.runStatus === 'running' ? '⏳ running'
|
|
1241
1237
|
: n.runStatus === 'error' ? '✕ error'
|
|
1242
1238
|
: '— skipped';
|
|
1243
|
-
statusHtml =
|
|
1239
|
+
statusHtml = `<div class="cn-status ${n.runStatus}">${esc(statusText)}</div>`;
|
|
1244
1240
|
}
|
|
1245
1241
|
|
|
1246
1242
|
// Header
|
|
@@ -1279,11 +1275,11 @@ function renderNodes() {
|
|
|
1279
1275
|
el.innerHTML = statusHtml + headerHtml + portsHtml;
|
|
1280
1276
|
|
|
1281
1277
|
// Events
|
|
1282
|
-
el.querySelector('.cn-header').addEventListener('mousedown',
|
|
1278
|
+
el.querySelector('.cn-header').addEventListener('mousedown', (e) => {
|
|
1283
1279
|
if (e.target.closest('.cn-del')) return;
|
|
1284
1280
|
startNodeDrag(e, n.id);
|
|
1285
1281
|
});
|
|
1286
|
-
el.addEventListener('mousedown',
|
|
1282
|
+
el.addEventListener('mousedown', (e) => {
|
|
1287
1283
|
if (e.target.closest('.cn-del') || e.target.closest('.cn-port')) return;
|
|
1288
1284
|
e.stopPropagation();
|
|
1289
1285
|
canvas.selectedId = n.id;
|
|
@@ -1291,47 +1287,47 @@ function renderNodes() {
|
|
|
1291
1287
|
renderNodes();
|
|
1292
1288
|
});
|
|
1293
1289
|
var delBtn = el.querySelector('.cn-del');
|
|
1294
|
-
if (delBtn) delBtn.addEventListener('mousedown',
|
|
1290
|
+
if (delBtn) delBtn.addEventListener('mousedown', (e) => {
|
|
1295
1291
|
e.stopPropagation();
|
|
1296
1292
|
removeNode(n.id);
|
|
1297
1293
|
});
|
|
1298
|
-
el.querySelectorAll('.cn-port.out').forEach(
|
|
1299
|
-
port.addEventListener('mousedown',
|
|
1294
|
+
el.querySelectorAll('.cn-port.out').forEach((port) => {
|
|
1295
|
+
port.addEventListener('mousedown', (e) => {
|
|
1300
1296
|
e.stopPropagation();
|
|
1301
1297
|
var portIdx = parseInt(port.getAttribute('data-out-port'), 10);
|
|
1302
1298
|
startDragEdge(e, n.id, portIdx);
|
|
1303
1299
|
});
|
|
1304
1300
|
});
|
|
1305
|
-
el.querySelectorAll('.cn-port.in').forEach(
|
|
1306
|
-
port.addEventListener('mouseup',
|
|
1301
|
+
el.querySelectorAll('.cn-port.in').forEach((port) => {
|
|
1302
|
+
port.addEventListener('mouseup', (e) => {
|
|
1307
1303
|
var portIdx = parseInt(port.getAttribute('data-in-port'), 10);
|
|
1308
1304
|
dropOnInputPort(e, n.id, portIdx);
|
|
1309
1305
|
});
|
|
1310
|
-
port.addEventListener('mouseenter',
|
|
1311
|
-
port.addEventListener('mouseleave',
|
|
1306
|
+
port.addEventListener('mouseenter', () => { port.classList.add('highlight'); });
|
|
1307
|
+
port.addEventListener('mouseleave', () => { port.classList.remove('highlight'); });
|
|
1312
1308
|
});
|
|
1313
1309
|
|
|
1314
1310
|
container.appendChild(el);
|
|
1315
1311
|
} else {
|
|
1316
1312
|
// Update position, selection, status
|
|
1317
|
-
existing.style.left = n.x
|
|
1318
|
-
existing.style.top = n.y
|
|
1319
|
-
existing.className =
|
|
1313
|
+
existing.style.left = `${n.x}px`;
|
|
1314
|
+
existing.style.top = `${n.y}px`;
|
|
1315
|
+
existing.className = `cn${isSelected ? ' selected' : ''}${n.runStatus === 'running' ? ' running' : ''}`;
|
|
1320
1316
|
existing.style.borderColor = isSelected ? color : (n.runStatus === 'error' ? '#ef444444' : n.runStatus === 'ok' ? '#10b98133' : 'rgba(124,58,237,0.15)');
|
|
1321
1317
|
// Update status badge
|
|
1322
1318
|
var badge = existing.querySelector('.cn-status');
|
|
1323
1319
|
if (n.runStatus) {
|
|
1324
|
-
var statusText2 = n.runStatus === 'ok' ?
|
|
1320
|
+
var statusText2 = n.runStatus === 'ok' ? `✓ ${n.runItems} item${n.runItems !== 1 ? 's' : ''}${n.runDuration ? ` · ${n.runDuration}ms` : ''}`
|
|
1325
1321
|
: n.runStatus === 'running' ? '⏳ running'
|
|
1326
1322
|
: n.runStatus === 'error' ? '✕ error'
|
|
1327
1323
|
: '— skipped';
|
|
1328
1324
|
if (!badge) {
|
|
1329
1325
|
var newBadge = document.createElement('div');
|
|
1330
|
-
newBadge.className =
|
|
1326
|
+
newBadge.className = `cn-status ${n.runStatus}`;
|
|
1331
1327
|
newBadge.textContent = statusText2;
|
|
1332
1328
|
existing.insertBefore(newBadge, existing.firstChild);
|
|
1333
1329
|
} else {
|
|
1334
|
-
badge.className =
|
|
1330
|
+
badge.className = `cn-status ${n.runStatus}`;
|
|
1335
1331
|
badge.textContent = statusText2;
|
|
1336
1332
|
}
|
|
1337
1333
|
} else if (badge) {
|
|
@@ -1347,16 +1343,16 @@ function renderNodes() {
|
|
|
1347
1343
|
function renderEdges() {
|
|
1348
1344
|
var layer = document.getElementById('edge-layer');
|
|
1349
1345
|
layer.innerHTML = '';
|
|
1350
|
-
canvas.edges.forEach(
|
|
1351
|
-
var fromNode = canvas.nodes.find(
|
|
1352
|
-
var toNode = canvas.nodes.find(
|
|
1346
|
+
canvas.edges.forEach((edge) => {
|
|
1347
|
+
var fromNode = canvas.nodes.find((n) => n.id === edge.from);
|
|
1348
|
+
var toNode = canvas.nodes.find((n) => n.id === edge.to);
|
|
1353
1349
|
if (!fromNode || !toNode) return;
|
|
1354
1350
|
var sx = (fromNode.x + 200) * canvas.zoom + canvas.panX;
|
|
1355
1351
|
var sy = (fromNode.y + 40 + 6 + edge.fromPort * 26 + 13) * canvas.zoom + canvas.panY;
|
|
1356
1352
|
var tx = toNode.x * canvas.zoom + canvas.panX;
|
|
1357
1353
|
var ty = (toNode.y + 40 + 6 + edge.toPort * 26 + 13) * canvas.zoom + canvas.panY;
|
|
1358
1354
|
var dx = Math.max(60, Math.abs(tx - sx) * 0.5) * canvas.zoom;
|
|
1359
|
-
var d =
|
|
1355
|
+
var d = `M${sx},${sy} C${sx+dx},${sy} ${tx-dx},${ty} ${tx},${ty}`;
|
|
1360
1356
|
var path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
1361
1357
|
path.setAttribute('d', d);
|
|
1362
1358
|
path.setAttribute('stroke', '#7c3aed88');
|
|
@@ -1367,7 +1363,7 @@ function renderEdges() {
|
|
|
1367
1363
|
layer.appendChild(path);
|
|
1368
1364
|
|
|
1369
1365
|
// Remove edge on click
|
|
1370
|
-
(
|
|
1366
|
+
((edgeId, _midX, _midY) => {
|
|
1371
1367
|
path.style.cursor = 'pointer';
|
|
1372
1368
|
path.style.strokeWidth = '4';
|
|
1373
1369
|
path.style.stroke = 'transparent';
|
|
@@ -1377,8 +1373,8 @@ function renderEdges() {
|
|
|
1377
1373
|
hit.setAttribute('stroke-width', '8');
|
|
1378
1374
|
hit.setAttribute('fill', 'none');
|
|
1379
1375
|
hit.style.cursor = 'pointer';
|
|
1380
|
-
hit.addEventListener('click',
|
|
1381
|
-
canvas.edges = canvas.edges.filter(
|
|
1376
|
+
hit.addEventListener('click', () => {
|
|
1377
|
+
canvas.edges = canvas.edges.filter((e) => e.id !== edgeId);
|
|
1382
1378
|
renderEdges();
|
|
1383
1379
|
});
|
|
1384
1380
|
layer.appendChild(hit);
|
|
@@ -1391,7 +1387,7 @@ function renderEdges() {
|
|
|
1391
1387
|
// ═══════════════════════════════════════════════════════════════════
|
|
1392
1388
|
function renderPropsPanel() {
|
|
1393
1389
|
var body = document.getElementById('props-body');
|
|
1394
|
-
var node = canvas.nodes.find(
|
|
1390
|
+
var node = canvas.nodes.find((n) => n.id === canvas.selectedId);
|
|
1395
1391
|
if (!node) {
|
|
1396
1392
|
body.innerHTML = '<div class="empty" style="padding:20px;color:#333;font-size:11px;text-align:center">Select a node to edit its properties</div>';
|
|
1397
1393
|
return;
|
|
@@ -1401,41 +1397,37 @@ function renderPropsPanel() {
|
|
|
1401
1397
|
'<input class="prop-input" id="prop-node-name" value="' + esc(node.name || '') + '" /></div>';
|
|
1402
1398
|
html += '<div class="prop-row"><div class="prop-label">On error</div>' +
|
|
1403
1399
|
'<select class="prop-select" id="prop-on-error">' +
|
|
1404
|
-
['stop','skip'].map(
|
|
1405
|
-
return '<option value="' + v + '"' + (node.onError === v ? ' selected' : '') + '>' + v + '</option>';
|
|
1406
|
-
}).join('') + '</select></div>';
|
|
1400
|
+
['stop','skip'].map((v) => `<option value="${v}"${node.onError === v ? ' selected' : ''}>${v}</option>`).join('') + '</select></div>';
|
|
1407
1401
|
|
|
1408
|
-
fields.forEach(
|
|
1402
|
+
fields.forEach((f) => {
|
|
1409
1403
|
var val = node.config[f.key] !== undefined ? String(node.config[f.key]) : '';
|
|
1410
|
-
html +=
|
|
1404
|
+
html += `<div class="prop-row"><div class="prop-label">${esc(f.label)}</div>`;
|
|
1411
1405
|
if (f.type === 'select') {
|
|
1412
1406
|
html += '<select class="prop-select" data-key="' + f.key + '">' +
|
|
1413
|
-
(f.options || []).map(
|
|
1414
|
-
return '<option value="' + esc(o) + '"' + (val === o ? ' selected' : '') + '>' + esc(o) + '</option>';
|
|
1415
|
-
}).join('') + '</select>';
|
|
1407
|
+
(f.options || []).map((o) => `<option value="${esc(o)}"${val === o ? ' selected' : ''}>${esc(o)}</option>`).join('') + '</select>';
|
|
1416
1408
|
} else if (f.type === 'textarea') {
|
|
1417
|
-
html +=
|
|
1409
|
+
html += `<textarea class="prop-textarea" data-key="${f.key}">${esc(val)}</textarea>`;
|
|
1418
1410
|
} else {
|
|
1419
|
-
html +=
|
|
1411
|
+
html += `<input class="prop-input" data-key="${f.key}" value="${esc(val)}" />`;
|
|
1420
1412
|
}
|
|
1421
1413
|
html += '</div>';
|
|
1422
1414
|
});
|
|
1423
1415
|
|
|
1424
1416
|
body.innerHTML = html;
|
|
1425
1417
|
|
|
1426
|
-
body.querySelector('#prop-node-name').addEventListener('input',
|
|
1418
|
+
body.querySelector('#prop-node-name').addEventListener('input', (e) => {
|
|
1427
1419
|
node.name = e.target.value;
|
|
1428
|
-
var el = document.querySelector(
|
|
1420
|
+
var el = document.querySelector(`[data-node-id="${node.id}"] .cn-label`);
|
|
1429
1421
|
if (el) el.textContent = node.name;
|
|
1430
1422
|
});
|
|
1431
|
-
body.querySelector('#prop-on-error').addEventListener('change',
|
|
1423
|
+
body.querySelector('#prop-on-error').addEventListener('change', (e) => {
|
|
1432
1424
|
node.onError = e.target.value;
|
|
1433
1425
|
});
|
|
1434
|
-
body.querySelectorAll('[data-key]').forEach(
|
|
1435
|
-
el.addEventListener('input',
|
|
1426
|
+
body.querySelectorAll('[data-key]').forEach((el) => {
|
|
1427
|
+
el.addEventListener('input', () => {
|
|
1436
1428
|
node.config[el.getAttribute('data-key')] = el.value;
|
|
1437
1429
|
});
|
|
1438
|
-
el.addEventListener('change',
|
|
1430
|
+
el.addEventListener('change', () => {
|
|
1439
1431
|
node.config[el.getAttribute('data-key')] = el.value;
|
|
1440
1432
|
});
|
|
1441
1433
|
});
|
|
@@ -1445,16 +1437,14 @@ function renderPropsPanel() {
|
|
|
1445
1437
|
// PLAYBOOK SERIALIZATION
|
|
1446
1438
|
// ═══════════════════════════════════════════════════════════════════
|
|
1447
1439
|
function toPlaybookDef() {
|
|
1448
|
-
var id = canvas.playbookId || (
|
|
1440
|
+
var id = canvas.playbookId || (`pb_${Date.now().toString(36)}`);
|
|
1449
1441
|
canvas.playbookId = id;
|
|
1450
1442
|
var name = document.getElementById('pb-name-input').value.trim() || 'Untitled Playbook';
|
|
1451
1443
|
return {
|
|
1452
1444
|
id: id,
|
|
1453
1445
|
name: name,
|
|
1454
|
-
nodes: canvas.nodes.map(
|
|
1455
|
-
|
|
1456
|
-
}),
|
|
1457
|
-
connections: canvas.edges.map(function(e) {
|
|
1446
|
+
nodes: canvas.nodes.map((n) => ({ id: n.id, type: n.type, name: n.name, config: n.config, onError: n.onError || 'stop' })),
|
|
1447
|
+
connections: canvas.edges.map((e) => {
|
|
1458
1448
|
var res = { from: e.from, to: e.to };
|
|
1459
1449
|
if (e.handle && e.handle !== 'main' && e.handle !== 'out' && e.handle !== 'items') res.handle = e.handle;
|
|
1460
1450
|
return res;
|
|
@@ -1473,7 +1463,7 @@ function loadPlaybookDef(pb) {
|
|
|
1473
1463
|
|
|
1474
1464
|
// Lay out nodes in a simple column if no positions stored
|
|
1475
1465
|
var x = 100, y = 60;
|
|
1476
|
-
(pb.nodes || []).forEach(
|
|
1466
|
+
(pb.nodes || []).forEach((n, i) => {
|
|
1477
1467
|
var def = getNodeDef(n.type) || { inputs:[{label:'in'}], outputs:[{label:'out'}], color:'#7c3aed', label: n.type };
|
|
1478
1468
|
var node = {
|
|
1479
1469
|
id: n.id,
|
|
@@ -1492,13 +1482,13 @@ function loadPlaybookDef(pb) {
|
|
|
1492
1482
|
});
|
|
1493
1483
|
|
|
1494
1484
|
// Build edges from connections
|
|
1495
|
-
(pb.connections || []).forEach(
|
|
1496
|
-
var fromNode = canvas.nodes.find(
|
|
1497
|
-
var toNode = canvas.nodes.find(
|
|
1485
|
+
(pb.connections || []).forEach((c) => {
|
|
1486
|
+
var fromNode = canvas.nodes.find((n) => n.id === c.from);
|
|
1487
|
+
var toNode = canvas.nodes.find((n) => n.id === c.to);
|
|
1498
1488
|
if (!fromNode || !toNode) return;
|
|
1499
1489
|
var fromDef = fromNode._def || { outputs: [{label:'main'}] };
|
|
1500
1490
|
var handle = c.handle || (fromDef.outputs[0] ? fromDef.outputs[0].label : 'main');
|
|
1501
|
-
var fromPortIdx = fromDef.outputs.findIndex(
|
|
1491
|
+
var fromPortIdx = fromDef.outputs.findIndex((o) => o.label === handle);
|
|
1502
1492
|
if (fromPortIdx < 0) fromPortIdx = 0;
|
|
1503
1493
|
canvas.edges.push({
|
|
1504
1494
|
id: uid(),
|
|
@@ -1519,7 +1509,7 @@ function loadPlaybookDef(pb) {
|
|
|
1519
1509
|
// ═══════════════════════════════════════════════════════════════════
|
|
1520
1510
|
// TOOLBAR ACTIONS
|
|
1521
1511
|
// ═══════════════════════════════════════════════════════════════════
|
|
1522
|
-
document.getElementById('btn-new').addEventListener('click',
|
|
1512
|
+
document.getElementById('btn-new').addEventListener('click', () => {
|
|
1523
1513
|
canvas.nodes = [];
|
|
1524
1514
|
canvas.edges = [];
|
|
1525
1515
|
canvas.playbookId = null;
|
|
@@ -1531,25 +1521,25 @@ document.getElementById('btn-new').addEventListener('click', function() {
|
|
|
1531
1521
|
renderPropsPanel();
|
|
1532
1522
|
});
|
|
1533
1523
|
|
|
1534
|
-
document.getElementById('btn-save').addEventListener('click',
|
|
1524
|
+
document.getElementById('btn-save').addEventListener('click', () => {
|
|
1535
1525
|
var pb = toPlaybookDef();
|
|
1536
1526
|
fetch('/api/playbooks', {
|
|
1537
1527
|
method: 'POST',
|
|
1538
1528
|
headers: { 'Content-Type': 'application/json' },
|
|
1539
1529
|
body: JSON.stringify(pb),
|
|
1540
|
-
}).then(
|
|
1530
|
+
}).then((r) => r.json()).then((saved) => {
|
|
1541
1531
|
canvas.playbookId = saved.id;
|
|
1542
|
-
showToast(
|
|
1543
|
-
}).catch(
|
|
1544
|
-
showToast(
|
|
1532
|
+
showToast(`Playbook saved: ${saved.name}`);
|
|
1533
|
+
}).catch((e) => {
|
|
1534
|
+
showToast(`Save failed: ${e}`, true);
|
|
1545
1535
|
});
|
|
1546
1536
|
});
|
|
1547
1537
|
|
|
1548
|
-
document.getElementById('btn-open').addEventListener('click',
|
|
1538
|
+
document.getElementById('btn-open').addEventListener('click', () => {
|
|
1549
1539
|
openPlaybookList();
|
|
1550
1540
|
});
|
|
1551
1541
|
|
|
1552
|
-
document.getElementById('btn-run').addEventListener('click',
|
|
1542
|
+
document.getElementById('btn-run').addEventListener('click', () => {
|
|
1553
1543
|
if (!canvas.playbookId) {
|
|
1554
1544
|
// Auto-save first
|
|
1555
1545
|
var pb = toPlaybookDef();
|
|
@@ -1557,9 +1547,9 @@ document.getElementById('btn-run').addEventListener('click', function() {
|
|
|
1557
1547
|
method: 'POST',
|
|
1558
1548
|
headers: { 'Content-Type': 'application/json' },
|
|
1559
1549
|
body: JSON.stringify(pb),
|
|
1560
|
-
}).then(
|
|
1550
|
+
}).then(() => {
|
|
1561
1551
|
runCurrentPlaybook(pb.id);
|
|
1562
|
-
}).catch(
|
|
1552
|
+
}).catch((e) => { showToast(`Error: ${e}`, true); });
|
|
1563
1553
|
} else {
|
|
1564
1554
|
// Save then run
|
|
1565
1555
|
var pb2 = toPlaybookDef();
|
|
@@ -1567,29 +1557,29 @@ document.getElementById('btn-run').addEventListener('click', function() {
|
|
|
1567
1557
|
method: 'POST',
|
|
1568
1558
|
headers: { 'Content-Type': 'application/json' },
|
|
1569
1559
|
body: JSON.stringify(pb2),
|
|
1570
|
-
}).then(
|
|
1560
|
+
}).then(() => {
|
|
1571
1561
|
runCurrentPlaybook(pb2.id);
|
|
1572
|
-
}).catch(
|
|
1562
|
+
}).catch((e) => { showToast(`Error: ${e}`, true); });
|
|
1573
1563
|
}
|
|
1574
1564
|
});
|
|
1575
1565
|
|
|
1576
1566
|
function runCurrentPlaybook(playbookId) {
|
|
1577
1567
|
// Reset node statuses
|
|
1578
|
-
canvas.nodes.forEach(
|
|
1568
|
+
canvas.nodes.forEach((n) => { n.runStatus = null; n.runItems = 0; n.runDuration = null; });
|
|
1579
1569
|
renderNodes();
|
|
1580
|
-
fetch(
|
|
1581
|
-
.then(
|
|
1582
|
-
.then(
|
|
1570
|
+
fetch(`/api/playbooks/${playbookId}/run`, { method: 'POST' })
|
|
1571
|
+
.then((r) => r.json())
|
|
1572
|
+
.then((res) => {
|
|
1583
1573
|
if (res.ok) {
|
|
1584
1574
|
showToast('Playbook started — watch the Runs tab');
|
|
1585
1575
|
// Switch to runs panel
|
|
1586
|
-
document.querySelectorAll('.nav-item').forEach(
|
|
1576
|
+
document.querySelectorAll('.nav-item').forEach((n) => { n.classList.remove('active'); });
|
|
1587
1577
|
document.querySelector('[data-panel="playbooks"]').classList.add('active');
|
|
1588
|
-
document.querySelectorAll('.panel').forEach(
|
|
1578
|
+
document.querySelectorAll('.panel').forEach((p) => { p.classList.remove('active'); });
|
|
1589
1579
|
document.getElementById('pb-panel').classList.add('active');
|
|
1590
1580
|
}
|
|
1591
1581
|
})
|
|
1592
|
-
.catch(
|
|
1582
|
+
.catch((e) => { showToast(`Run failed: ${e}`, true); });
|
|
1593
1583
|
}
|
|
1594
1584
|
|
|
1595
1585
|
// ═══════════════════════════════════════════════════════════════════
|
|
@@ -1599,36 +1589,34 @@ function openPlaybookList() {
|
|
|
1599
1589
|
var overlay = document.getElementById('pb-list-overlay');
|
|
1600
1590
|
overlay.classList.add('show');
|
|
1601
1591
|
fetch('/api/playbooks')
|
|
1602
|
-
.then(
|
|
1603
|
-
.then(
|
|
1592
|
+
.then((r) => r.json())
|
|
1593
|
+
.then((list) => {
|
|
1604
1594
|
var body = document.getElementById('pblo-body');
|
|
1605
1595
|
if (!list.length) {
|
|
1606
1596
|
body.innerHTML = '<div class="pblo-empty">No saved playbooks yet. Build one and click Save!</div>';
|
|
1607
1597
|
return;
|
|
1608
1598
|
}
|
|
1609
|
-
body.innerHTML = list.map(
|
|
1610
|
-
return '<div class="pblo-item" data-pb-id="' + esc(pb.id) + '">' +
|
|
1599
|
+
body.innerHTML = list.map((pb) => '<div class="pblo-item" data-pb-id="' + esc(pb.id) + '">' +
|
|
1611
1600
|
'<span class="pblo-item-name">' + esc(pb.name) + '</span>' +
|
|
1612
1601
|
'<span class="pblo-item-meta">' + (pb.nodes ? pb.nodes.length : 0) + ' nodes</span>' +
|
|
1613
1602
|
'<button class="pblo-del" data-del-id="' + esc(pb.id) + '" title="Delete">✕</button>' +
|
|
1614
|
-
'</div>';
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
el.addEventListener('click', function(e) {
|
|
1603
|
+
'</div>').join('');
|
|
1604
|
+
body.querySelectorAll('.pblo-item').forEach((el) => {
|
|
1605
|
+
el.addEventListener('click', (e) => {
|
|
1618
1606
|
if (e.target.closest('.pblo-del')) return;
|
|
1619
1607
|
var pbId = el.getAttribute('data-pb-id');
|
|
1620
|
-
fetch(
|
|
1608
|
+
fetch(`/api/playbooks/${pbId}`).then((r) => r.json()).then((pb) => {
|
|
1621
1609
|
loadPlaybookDef(pb);
|
|
1622
1610
|
overlay.classList.remove('show');
|
|
1623
1611
|
});
|
|
1624
1612
|
});
|
|
1625
1613
|
});
|
|
1626
|
-
body.querySelectorAll('.pblo-del').forEach(
|
|
1627
|
-
btn.addEventListener('click',
|
|
1614
|
+
body.querySelectorAll('.pblo-del').forEach((btn) => {
|
|
1615
|
+
btn.addEventListener('click', (e) => {
|
|
1628
1616
|
e.stopPropagation();
|
|
1629
1617
|
var delId = btn.getAttribute('data-del-id');
|
|
1630
1618
|
if (!confirm('Delete this playbook?')) return;
|
|
1631
|
-
fetch(
|
|
1619
|
+
fetch(`/api/playbooks/${delId}`, { method: 'DELETE' }).then(() => {
|
|
1632
1620
|
if (canvas.playbookId === delId) {
|
|
1633
1621
|
canvas.playbookId = null;
|
|
1634
1622
|
canvas.nodes = [];
|
|
@@ -1645,12 +1633,12 @@ function openPlaybookList() {
|
|
|
1645
1633
|
});
|
|
1646
1634
|
});
|
|
1647
1635
|
})
|
|
1648
|
-
.catch(
|
|
1636
|
+
.catch(() => {
|
|
1649
1637
|
document.getElementById('pblo-body').innerHTML = '<div class="pblo-empty">Failed to load playbooks.</div>';
|
|
1650
1638
|
});
|
|
1651
1639
|
}
|
|
1652
1640
|
|
|
1653
|
-
document.getElementById('pblo-close-btn').addEventListener('click',
|
|
1641
|
+
document.getElementById('pblo-close-btn').addEventListener('click', () => {
|
|
1654
1642
|
document.getElementById('pb-list-overlay').classList.remove('show');
|
|
1655
1643
|
});
|
|
1656
1644
|
|
|
@@ -1663,7 +1651,7 @@ function showToast(msg, isError) {
|
|
|
1663
1651
|
t.style.cssText = 'position:fixed;bottom:20px;right:20px;z-index:9999;padding:8px 14px;border-radius:6px;font-size:11px;font-family:monospace;max-width:320px;pointer-events:none;transition:opacity .3s;' +
|
|
1664
1652
|
(isError ? 'background:#ef444420;border:1px solid #ef444466;color:#ef4444;' : 'background:#7c3aed20;border:1px solid #7c3aed66;color:#c4b5fd;');
|
|
1665
1653
|
document.body.appendChild(t);
|
|
1666
|
-
setTimeout(
|
|
1654
|
+
setTimeout(() => { t.style.opacity = '0'; setTimeout(() => { t.remove(); }, 300); }, 2500);
|
|
1667
1655
|
}
|
|
1668
1656
|
|
|
1669
1657
|
// ═══════════════════════════════════════════════════════════════════
|
|
@@ -1671,7 +1659,7 @@ function showToast(msg, isError) {
|
|
|
1671
1659
|
// ═══════════════════════════════════════════════════════════════════
|
|
1672
1660
|
function handleBuilderEvent(evt) {
|
|
1673
1661
|
if (!evt.nodeId) return;
|
|
1674
|
-
var node = canvas.nodes.find(
|
|
1662
|
+
var node = canvas.nodes.find((n) => n.id === evt.nodeId);
|
|
1675
1663
|
if (!node) return;
|
|
1676
1664
|
if (evt.eventType === 'step_started') {
|
|
1677
1665
|
node.runStatus = 'running';
|
|
@@ -1711,17 +1699,17 @@ function handleEvent(evt) {
|
|
|
1711
1699
|
r.status = 'running';
|
|
1712
1700
|
r.startedAt = evt.timestamp || Date.now();
|
|
1713
1701
|
} else if (evt.eventType === 'step_started') {
|
|
1714
|
-
runLogs[id].push({ ts: t, type: 'info', text:
|
|
1702
|
+
runLogs[id].push({ ts: t, type: 'info', text: `${evt.nodeName || '?'} — started` });
|
|
1715
1703
|
} else if (evt.eventType === 'step_completed') {
|
|
1716
1704
|
r.itemsProcessed = (r.itemsProcessed || 0) + (evt.itemTotal || 1);
|
|
1717
|
-
runLogs[id].push({ ts: t, type: 'ok', text:
|
|
1705
|
+
runLogs[id].push({ ts: t, type: 'ok', text: `${evt.nodeName || '?'} ✓ ${evt.durationMs ? `${evt.durationMs}ms` : ''}` });
|
|
1718
1706
|
} else if (evt.eventType === 'step_failed') {
|
|
1719
|
-
runLogs[id].push({ ts: t, type: 'err', text:
|
|
1707
|
+
runLogs[id].push({ ts: t, type: 'err', text: `${evt.nodeName || '?'} ✗ ${evt.error || ''}` });
|
|
1720
1708
|
} else if (evt.eventType === 'run_completed') {
|
|
1721
1709
|
r.status = 'completed';
|
|
1722
1710
|
r.endedAt = evt.timestamp || Date.now();
|
|
1723
1711
|
// Clear running status on nodes
|
|
1724
|
-
canvas.nodes.forEach(
|
|
1712
|
+
canvas.nodes.forEach((n) => {
|
|
1725
1713
|
if (n.runStatus === 'running') { n.runStatus = null; }
|
|
1726
1714
|
});
|
|
1727
1715
|
} else if (evt.eventType === 'run_failed') {
|
|
@@ -1763,9 +1751,9 @@ var _sseRetryDelay = 1000;
|
|
|
1763
1751
|
function connectSSE() {
|
|
1764
1752
|
setConnStatus('connecting');
|
|
1765
1753
|
var es = new EventSource('/events');
|
|
1766
|
-
es.onopen =
|
|
1767
|
-
es.onmessage =
|
|
1768
|
-
es.onerror =
|
|
1754
|
+
es.onopen = () => { setConnStatus('live'); _sseRetryDelay = 1000; };
|
|
1755
|
+
es.onmessage = (e) => { handleEvent(JSON.parse(e.data)); };
|
|
1756
|
+
es.onerror = () => {
|
|
1769
1757
|
setConnStatus('error');
|
|
1770
1758
|
es.close();
|
|
1771
1759
|
setTimeout(connectSSE, Math.min(_sseRetryDelay *= 2, 30000));
|
|
@@ -1774,12 +1762,12 @@ function connectSSE() {
|
|
|
1774
1762
|
|
|
1775
1763
|
function connect() {
|
|
1776
1764
|
setConnStatus('connecting');
|
|
1777
|
-
var sock = new WebSocket(
|
|
1778
|
-
sock.onopen =
|
|
1779
|
-
sock.onmessage =
|
|
1765
|
+
var sock = new WebSocket(`ws://${location.host}/ws`);
|
|
1766
|
+
sock.onopen = () => { setConnStatus('live'); };
|
|
1767
|
+
sock.onmessage = (e) => {
|
|
1780
1768
|
var msg = JSON.parse(e.data);
|
|
1781
1769
|
if (msg.type === 'history') {
|
|
1782
|
-
msg.runs.forEach(
|
|
1770
|
+
msg.runs.forEach((r) => {
|
|
1783
1771
|
runs[r.id] = r;
|
|
1784
1772
|
if (!runLogs[r.id]) runLogs[r.id] = [];
|
|
1785
1773
|
});
|
|
@@ -1790,13 +1778,13 @@ function connect() {
|
|
|
1790
1778
|
}
|
|
1791
1779
|
};
|
|
1792
1780
|
var wsFailedOnce = false;
|
|
1793
|
-
sock.onclose =
|
|
1781
|
+
sock.onclose = () => {
|
|
1794
1782
|
setConnStatus('error');
|
|
1795
1783
|
if (!wsFailedOnce) {
|
|
1796
1784
|
wsFailedOnce = true;
|
|
1797
|
-
fetch('/events', { method: 'HEAD' }).then(
|
|
1785
|
+
fetch('/events', { method: 'HEAD' }).then((r) => {
|
|
1798
1786
|
if (r.ok) { connectSSE(); } else { setTimeout(connect, 2500); }
|
|
1799
|
-
}).catch(
|
|
1787
|
+
}).catch(() => { setTimeout(connect, 2500); });
|
|
1800
1788
|
} else {
|
|
1801
1789
|
setTimeout(connect, 2500);
|
|
1802
1790
|
}
|
|
@@ -1805,7 +1793,7 @@ function connect() {
|
|
|
1805
1793
|
|
|
1806
1794
|
// ── init ───────────────────────────────────────────────────
|
|
1807
1795
|
connect();
|
|
1808
|
-
setInterval(
|
|
1796
|
+
setInterval(() => { if (Object.values(runs).some((r)=> r.status==='running')) renderCards(); }, 5000);
|
|
1809
1797
|
</script>
|
|
1810
1798
|
</body>
|
|
1811
1799
|
</html>
|