api-chain-runner 2.5.5__tar.gz → 2.5.6__tar.gz
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.
- {api_chain_runner-2.5.5/api_chain_runner.egg-info → api_chain_runner-2.5.6}/PKG-INFO +1 -1
- {api_chain_runner-2.5.5 → api_chain_runner-2.5.6}/api_chain_runner/__init__.py +1 -1
- {api_chain_runner-2.5.5 → api_chain_runner-2.5.6}/api_chain_runner/ui/server.py +97 -3
- {api_chain_runner-2.5.5 → api_chain_runner-2.5.6}/api_chain_runner/ui/static/flow.js +87 -7
- {api_chain_runner-2.5.5 → api_chain_runner-2.5.6}/api_chain_runner/ui/static/style.css +44 -0
- {api_chain_runner-2.5.5 → api_chain_runner-2.5.6}/api_chain_runner/ui/templates/flow.html +25 -1
- {api_chain_runner-2.5.5 → api_chain_runner-2.5.6}/api_chain_runner/ui/templates/icons.html +4 -1
- {api_chain_runner-2.5.5 → api_chain_runner-2.5.6/api_chain_runner.egg-info}/PKG-INFO +1 -1
- {api_chain_runner-2.5.5 → api_chain_runner-2.5.6}/.gitignore +0 -0
- {api_chain_runner-2.5.5 → api_chain_runner-2.5.6}/MANIFEST.in +0 -0
- {api_chain_runner-2.5.5 → api_chain_runner-2.5.6}/PYPI_README.md +0 -0
- {api_chain_runner-2.5.5 → api_chain_runner-2.5.6}/README.md +0 -0
- {api_chain_runner-2.5.5 → api_chain_runner-2.5.6}/api_chain_runner/__main__.py +0 -0
- {api_chain_runner-2.5.5 → api_chain_runner-2.5.6}/api_chain_runner/executor.py +0 -0
- {api_chain_runner-2.5.5 → api_chain_runner-2.5.6}/api_chain_runner/generator.py +0 -0
- {api_chain_runner-2.5.5 → api_chain_runner-2.5.6}/api_chain_runner/logger.py +0 -0
- {api_chain_runner-2.5.5 → api_chain_runner-2.5.6}/api_chain_runner/models.py +0 -0
- {api_chain_runner-2.5.5 → api_chain_runner-2.5.6}/api_chain_runner/pause.py +0 -0
- {api_chain_runner-2.5.5 → api_chain_runner-2.5.6}/api_chain_runner/resolver.py +0 -0
- {api_chain_runner-2.5.5 → api_chain_runner-2.5.6}/api_chain_runner/runner.py +0 -0
- {api_chain_runner-2.5.5 → api_chain_runner-2.5.6}/api_chain_runner/store.py +0 -0
- {api_chain_runner-2.5.5 → api_chain_runner-2.5.6}/api_chain_runner/ui/__init__.py +0 -0
- {api_chain_runner-2.5.5 → api_chain_runner-2.5.6}/api_chain_runner/ui/static/create.js +0 -0
- {api_chain_runner-2.5.5 → api_chain_runner-2.5.6}/api_chain_runner/ui/static/docs.js +0 -0
- {api_chain_runner-2.5.5 → api_chain_runner-2.5.6}/api_chain_runner/ui/static/sidebar.js +0 -0
- {api_chain_runner-2.5.5 → api_chain_runner-2.5.6}/api_chain_runner/ui/static/theme.js +0 -0
- {api_chain_runner-2.5.5 → api_chain_runner-2.5.6}/api_chain_runner/ui/templates/docs.html +0 -0
- {api_chain_runner-2.5.5 → api_chain_runner-2.5.6}/api_chain_runner/ui/templates/editor.html +0 -0
- {api_chain_runner-2.5.5 → api_chain_runner-2.5.6}/api_chain_runner/ui/templates/index.html +0 -0
- {api_chain_runner-2.5.5 → api_chain_runner-2.5.6}/api_chain_runner.egg-info/SOURCES.txt +0 -0
- {api_chain_runner-2.5.5 → api_chain_runner-2.5.6}/api_chain_runner.egg-info/dependency_links.txt +0 -0
- {api_chain_runner-2.5.5 → api_chain_runner-2.5.6}/api_chain_runner.egg-info/entry_points.txt +0 -0
- {api_chain_runner-2.5.5 → api_chain_runner-2.5.6}/api_chain_runner.egg-info/requires.txt +0 -0
- {api_chain_runner-2.5.5 → api_chain_runner-2.5.6}/api_chain_runner.egg-info/top_level.txt +0 -0
- {api_chain_runner-2.5.5 → api_chain_runner-2.5.6}/pyproject.toml +0 -0
- {api_chain_runner-2.5.5 → api_chain_runner-2.5.6}/setup.cfg +0 -0
|
@@ -124,7 +124,7 @@ def _parse_chain(filepath: str) -> dict:
|
|
|
124
124
|
"url": step.get("url", ""),
|
|
125
125
|
"continue_on_error": step.get("continue_on_error", True),
|
|
126
126
|
"delay": step.get("delay", 0),
|
|
127
|
-
"has_polling": "polling"
|
|
127
|
+
"has_polling": isinstance(step.get("polling"), dict),
|
|
128
128
|
"has_payload": "payload" in step,
|
|
129
129
|
"has_files": "files" in step,
|
|
130
130
|
"has_unique_fields": "unique_fields" in step,
|
|
@@ -144,7 +144,7 @@ def _parse_chain(filepath: str) -> dict:
|
|
|
144
144
|
"failure_message": step.get("failure_message", ""),
|
|
145
145
|
"retry": step.get("retry"),
|
|
146
146
|
}
|
|
147
|
-
if "polling"
|
|
147
|
+
if isinstance(step.get("polling"), dict):
|
|
148
148
|
p = step["polling"]
|
|
149
149
|
step_info["polling"] = {
|
|
150
150
|
"key_path": p.get("key_path", ""),
|
|
@@ -167,6 +167,25 @@ def _read_raw_yaml(filepath: str) -> str:
|
|
|
167
167
|
return f.read()
|
|
168
168
|
|
|
169
169
|
|
|
170
|
+
def _validate_step_names(raw: dict) -> None:
|
|
171
|
+
"""Validate only the required and unique names in a candidate chain."""
|
|
172
|
+
chain = raw.get("chain") if isinstance(raw, dict) else None
|
|
173
|
+
if not isinstance(chain, list) or not chain:
|
|
174
|
+
raise ValueError("Configuration must contain a non-empty 'chain' list.")
|
|
175
|
+
|
|
176
|
+
seen = set()
|
|
177
|
+
for index, step in enumerate(chain):
|
|
178
|
+
if not isinstance(step, dict):
|
|
179
|
+
raise ValueError(f"Step at index {index} must be a mapping.")
|
|
180
|
+
name = step.get("name")
|
|
181
|
+
if not isinstance(name, str) or not name.strip():
|
|
182
|
+
raise ValueError(f"Step at index {index} must have a non-empty name.")
|
|
183
|
+
normalized = name.strip()
|
|
184
|
+
if normalized in seen:
|
|
185
|
+
raise ValueError(f"Duplicate step name '{normalized}'. Step names must be unique.")
|
|
186
|
+
seen.add(normalized)
|
|
187
|
+
|
|
188
|
+
|
|
170
189
|
def _format_yaml_for_readability(yaml_str: str) -> str:
|
|
171
190
|
"""Post-process YAML to add blank lines before each step for better readability."""
|
|
172
191
|
# Add newline before '- name:' if it's not already preceded by a blank line
|
|
@@ -504,7 +523,7 @@ def api_step_update(flow_path, step_index):
|
|
|
504
523
|
return jsonify({"error": str(exc)}), 400
|
|
505
524
|
|
|
506
525
|
# Apply updates to allowed fields
|
|
507
|
-
allowed = {"payload", "headers", "url", "unique_fields", "delay", "continue_on_error", "method", "files", "print_keys", "polling", "eval_keys", "eval_condition", "success_message", "failure_message", "manual", "instruction", "print_ref", "retry", "condition"}
|
|
526
|
+
allowed = {"name", "payload", "headers", "url", "unique_fields", "delay", "continue_on_error", "method", "files", "print_keys", "polling", "eval_keys", "eval_condition", "success_message", "failure_message", "manual", "instruction", "print_ref", "retry", "condition"}
|
|
508
527
|
for key, value in updates.items():
|
|
509
528
|
if key not in allowed:
|
|
510
529
|
continue
|
|
@@ -519,6 +538,11 @@ def api_step_update(flow_path, step_index):
|
|
|
519
538
|
else:
|
|
520
539
|
step[key] = value
|
|
521
540
|
|
|
541
|
+
try:
|
|
542
|
+
_validate_step_names(raw)
|
|
543
|
+
except ValueError as exc:
|
|
544
|
+
return jsonify({"error": str(exc)}), 400
|
|
545
|
+
|
|
522
546
|
# Write back preserving original formatting as much as possible
|
|
523
547
|
dumped = yaml.dump(
|
|
524
548
|
raw,
|
|
@@ -536,6 +560,76 @@ def api_step_update(flow_path, step_index):
|
|
|
536
560
|
return jsonify({"error": str(exc)}), 500
|
|
537
561
|
|
|
538
562
|
|
|
563
|
+
@app.route("/api/flow/<path:flow_path>/step/<int:step_index>", methods=["DELETE"])
|
|
564
|
+
def api_step_delete(flow_path, step_index):
|
|
565
|
+
"""Delete one step after checking the remaining step names."""
|
|
566
|
+
abs_path = os.path.join(_flow_dir, flow_path)
|
|
567
|
+
if not os.path.isfile(abs_path):
|
|
568
|
+
return jsonify({"error": "not found"}), 404
|
|
569
|
+
|
|
570
|
+
try:
|
|
571
|
+
with open(abs_path, "r", encoding="utf-8") as f:
|
|
572
|
+
raw = yaml.safe_load(f)
|
|
573
|
+
chain = raw.get("chain", []) if isinstance(raw, dict) else []
|
|
574
|
+
if step_index < 0 or step_index >= len(chain):
|
|
575
|
+
return jsonify({"error": "step index out of range"}), 400
|
|
576
|
+
|
|
577
|
+
candidate = dict(raw)
|
|
578
|
+
candidate["chain"] = list(chain)
|
|
579
|
+
candidate["chain"].pop(step_index)
|
|
580
|
+
_validate_step_names(candidate)
|
|
581
|
+
|
|
582
|
+
dumped = yaml.dump(
|
|
583
|
+
candidate,
|
|
584
|
+
default_flow_style=False,
|
|
585
|
+
allow_unicode=True,
|
|
586
|
+
sort_keys=False,
|
|
587
|
+
default_style='"',
|
|
588
|
+
)
|
|
589
|
+
with open(abs_path, "w", encoding="utf-8") as f:
|
|
590
|
+
f.write(_format_yaml_for_readability(dumped))
|
|
591
|
+
return jsonify({"success": True})
|
|
592
|
+
except ValueError as exc:
|
|
593
|
+
return jsonify({"error": str(exc)}), 400
|
|
594
|
+
except Exception as exc:
|
|
595
|
+
return jsonify({"error": str(exc)}), 500
|
|
596
|
+
|
|
597
|
+
|
|
598
|
+
@app.route("/api/flow/<path:flow_path>/step", methods=["POST"])
|
|
599
|
+
def api_step_add(flow_path):
|
|
600
|
+
"""Append one validated step to an existing flow."""
|
|
601
|
+
abs_path = os.path.join(_flow_dir, flow_path)
|
|
602
|
+
if not os.path.isfile(abs_path):
|
|
603
|
+
return jsonify({"error": "not found"}), 404
|
|
604
|
+
|
|
605
|
+
data = request.get_json(silent=True) or {}
|
|
606
|
+
new_step = data.get("step", data.get("updates"))
|
|
607
|
+
if not isinstance(new_step, dict):
|
|
608
|
+
return jsonify({"error": "step must be a mapping"}), 400
|
|
609
|
+
|
|
610
|
+
try:
|
|
611
|
+
with open(abs_path, "r", encoding="utf-8") as f:
|
|
612
|
+
raw = yaml.safe_load(f)
|
|
613
|
+
if not isinstance(raw, dict) or not isinstance(raw.get("chain"), list):
|
|
614
|
+
return jsonify({"error": "Configuration must contain a top-level 'chain' list."}), 400
|
|
615
|
+
candidate = dict(raw)
|
|
616
|
+
candidate["chain"] = list(raw["chain"]) + [dict(new_step)]
|
|
617
|
+
_validate_step_names(candidate)
|
|
618
|
+
|
|
619
|
+
dumped = yaml.dump(
|
|
620
|
+
candidate,
|
|
621
|
+
default_flow_style=False,
|
|
622
|
+
allow_unicode=True,
|
|
623
|
+
sort_keys=False,
|
|
624
|
+
default_style='"',
|
|
625
|
+
)
|
|
626
|
+
with open(abs_path, "w", encoding="utf-8") as f:
|
|
627
|
+
f.write(_format_yaml_for_readability(dumped))
|
|
628
|
+
return jsonify({"success": True})
|
|
629
|
+
except Exception as exc:
|
|
630
|
+
return jsonify({"error": str(exc)}), 400
|
|
631
|
+
|
|
632
|
+
|
|
539
633
|
@app.route("/api/flow/create", methods=["POST"])
|
|
540
634
|
def api_flow_create():
|
|
541
635
|
"""Create a new flow YAML file with initial steps."""
|
|
@@ -9,16 +9,24 @@
|
|
|
9
9
|
const detailName = document.getElementById("detail-name");
|
|
10
10
|
const detailBody = document.getElementById("detail-body");
|
|
11
11
|
const detailClose = document.getElementById("detail-close");
|
|
12
|
+
const detailDelete = document.getElementById("detail-delete");
|
|
13
|
+
const deleteConfirmModal = document.getElementById("delete-confirm-modal");
|
|
14
|
+
const deleteConfirmStepName = document.getElementById("delete-confirm-step-name");
|
|
15
|
+
const deleteConfirmClose = document.getElementById("delete-confirm-close");
|
|
16
|
+
const deleteConfirmCancel = document.getElementById("delete-confirm-cancel");
|
|
17
|
+
const deleteConfirmOk = document.getElementById("delete-confirm-ok");
|
|
12
18
|
const detailSave = document.getElementById("detail-save");
|
|
13
19
|
const detailSaveStatus = document.getElementById("detail-save-status");
|
|
14
20
|
const responsePanel = document.getElementById("response-panel");
|
|
15
21
|
const responseList = document.getElementById("response-list");
|
|
16
22
|
const responseClose = document.getElementById("response-close");
|
|
17
23
|
const responseDownloadCsv = document.getElementById("response-download-csv");
|
|
24
|
+
const addStepBtn = document.getElementById("add-step-btn");
|
|
18
25
|
|
|
19
26
|
const steps = CHAIN_DATA.steps;
|
|
20
27
|
const stepBoxes = [];
|
|
21
28
|
let currentStepIndex = -1;
|
|
29
|
+
let isCreatingStep = false;
|
|
22
30
|
let currentResponseResults = [];
|
|
23
31
|
|
|
24
32
|
// ── Render VERTICAL flow ─────────────────────────────────
|
|
@@ -82,9 +90,12 @@
|
|
|
82
90
|
}
|
|
83
91
|
|
|
84
92
|
// ── Detail drawer with editable fields ───────────────────
|
|
85
|
-
function showDetail(step, index) {
|
|
93
|
+
function showDetail(step, index, isNew = false) {
|
|
86
94
|
currentStepIndex = index;
|
|
87
|
-
|
|
95
|
+
isCreatingStep = isNew;
|
|
96
|
+
detailName.textContent = isNew ? "Add Step" : step.name;
|
|
97
|
+
detailDelete.classList.toggle("hidden", isNew);
|
|
98
|
+
detailSave.textContent = isNew ? "Save Step" : "Save Changes";
|
|
88
99
|
detailSaveStatus.textContent = "";
|
|
89
100
|
detailSaveStatus.className = "detail-save-status";
|
|
90
101
|
|
|
@@ -95,6 +106,7 @@
|
|
|
95
106
|
if (isManual) {
|
|
96
107
|
// ── Manual step UI ──
|
|
97
108
|
html += `<div class="detail-row"><div class="detail-label">Type</div><div class="detail-value">Manual Step</div></div>`;
|
|
109
|
+
html += inputRow("name", "Step Name", step.name || "");
|
|
98
110
|
html += editableRow("instruction", "Instruction", step.instruction || "");
|
|
99
111
|
html += buildListField("print_ref", "Print References", step.print_ref || [], "e.g. create-lead.leadId");
|
|
100
112
|
html += buildToggleSection("condition", "Conditions", (step.condition || []).length ? step.condition : null, buildConditionFields);
|
|
@@ -102,6 +114,7 @@
|
|
|
102
114
|
html += dropdownRow("continue_on_error", "Continue on Error", step.continue_on_error);
|
|
103
115
|
} else {
|
|
104
116
|
// ── API step UI ──
|
|
117
|
+
html += inputRow("name", "Step Name", step.name || "");
|
|
105
118
|
const methods = ["GET","POST","PUT","DELETE","PATCH","HEAD","OPTIONS"];
|
|
106
119
|
html += `<div class="detail-row"><div class="detail-label">Method</div>
|
|
107
120
|
<select class="detail-select" data-field="method">${methods.map(m => `<option${m===curMethod?" selected":""}>${m}</option>`).join("")}</select></div>`;
|
|
@@ -483,7 +496,7 @@
|
|
|
483
496
|
|
|
484
497
|
// ── Save step changes from drawer ────────────────────────
|
|
485
498
|
detailSave.addEventListener("click", async () => {
|
|
486
|
-
if (currentStepIndex < 0) return;
|
|
499
|
+
if (!isCreatingStep && currentStepIndex < 0) return;
|
|
487
500
|
const editables = detailBody.querySelectorAll(".detail-editable, .detail-editable-input, .detail-select");
|
|
488
501
|
const updates = {};
|
|
489
502
|
|
|
@@ -494,7 +507,7 @@
|
|
|
494
507
|
|
|
495
508
|
if (!raw && !["method", "url", "delay", "continue_on_error", "instruction"].includes(field)) continue;
|
|
496
509
|
|
|
497
|
-
if (["url", "method", "instruction", "eval_condition", "success_message", "failure_message"].includes(field)) {
|
|
510
|
+
if (["name", "url", "method", "instruction", "eval_condition", "success_message", "failure_message"].includes(field)) {
|
|
498
511
|
updates[field] = raw;
|
|
499
512
|
} else if (field === "delay") {
|
|
500
513
|
updates[field] = parseInt(raw) || 0;
|
|
@@ -511,6 +524,14 @@
|
|
|
511
524
|
}
|
|
512
525
|
}
|
|
513
526
|
|
|
527
|
+
if (!updates.name || !updates.name.trim()) {
|
|
528
|
+
detailSaveStatus.textContent = "Step name is required";
|
|
529
|
+
detailSaveStatus.className = "detail-save-status error";
|
|
530
|
+
detailBody.querySelector('[data-field="name"]')?.focus();
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
updates.name = updates.name.trim();
|
|
534
|
+
|
|
514
535
|
// Collect list fields (print_keys, print_ref)
|
|
515
536
|
detailBody.querySelectorAll(".list-field").forEach(container => {
|
|
516
537
|
const field = container.dataset.listField;
|
|
@@ -604,14 +625,18 @@
|
|
|
604
625
|
detailSaveStatus.className = "detail-save-status";
|
|
605
626
|
|
|
606
627
|
try {
|
|
607
|
-
const
|
|
628
|
+
const endpoint = isCreatingStep
|
|
629
|
+
? `/api/flow/${FLOW_PATH}/step`
|
|
630
|
+
: `/api/flow/${FLOW_PATH}/step/${currentStepIndex}`;
|
|
631
|
+
const body = isCreatingStep ? { step: updates } : { updates };
|
|
632
|
+
const res = await fetch(endpoint, {
|
|
608
633
|
method: "POST",
|
|
609
634
|
headers: { "Content-Type": "application/json" },
|
|
610
|
-
body: JSON.stringify(
|
|
635
|
+
body: JSON.stringify(body),
|
|
611
636
|
});
|
|
612
637
|
const data = await res.json();
|
|
613
638
|
if (data.success) {
|
|
614
|
-
detailSaveStatus.textContent = "✓ Saved";
|
|
639
|
+
detailSaveStatus.textContent = isCreatingStep ? "✓ Step added" : "✓ Saved";
|
|
615
640
|
detailSaveStatus.className = "detail-save-status success";
|
|
616
641
|
setTimeout(() => location.reload(), 1200);
|
|
617
642
|
} else {
|
|
@@ -624,6 +649,61 @@
|
|
|
624
649
|
}
|
|
625
650
|
});
|
|
626
651
|
|
|
652
|
+
detailDelete.addEventListener("click", () => {
|
|
653
|
+
if (isCreatingStep || currentStepIndex < 0) return;
|
|
654
|
+
deleteConfirmStepName.textContent = steps[currentStepIndex].name;
|
|
655
|
+
deleteConfirmModal.classList.remove("hidden");
|
|
656
|
+
deleteConfirmOk.focus();
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
function closeDeleteConfirmation() {
|
|
660
|
+
deleteConfirmModal.classList.add("hidden");
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
deleteConfirmClose.addEventListener("click", closeDeleteConfirmation);
|
|
664
|
+
deleteConfirmCancel.addEventListener("click", closeDeleteConfirmation);
|
|
665
|
+
deleteConfirmModal.addEventListener("click", (event) => {
|
|
666
|
+
if (event.target === deleteConfirmModal) closeDeleteConfirmation();
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
deleteConfirmOk.addEventListener("click", async () => {
|
|
670
|
+
if (isCreatingStep || currentStepIndex < 0) return;
|
|
671
|
+
closeDeleteConfirmation();
|
|
672
|
+
detailDelete.disabled = true;
|
|
673
|
+
deleteConfirmOk.disabled = true;
|
|
674
|
+
detailSaveStatus.textContent = "Deleting...";
|
|
675
|
+
detailSaveStatus.className = "detail-save-status";
|
|
676
|
+
try {
|
|
677
|
+
const res = await fetch(`/api/flow/${FLOW_PATH}/step/${currentStepIndex}`, { method: "DELETE" });
|
|
678
|
+
const data = await res.json();
|
|
679
|
+
if (!data.success) throw new Error(data.error || "Failed to delete step");
|
|
680
|
+
detailSaveStatus.textContent = "✓ Deleted";
|
|
681
|
+
detailSaveStatus.className = "detail-save-status success";
|
|
682
|
+
setTimeout(() => location.reload(), 700);
|
|
683
|
+
} catch (err) {
|
|
684
|
+
detailDelete.disabled = false;
|
|
685
|
+
deleteConfirmOk.disabled = false;
|
|
686
|
+
detailSaveStatus.textContent = "✗ " + err.message;
|
|
687
|
+
detailSaveStatus.className = "detail-save-status error";
|
|
688
|
+
}
|
|
689
|
+
});
|
|
690
|
+
|
|
691
|
+
addStepBtn.addEventListener("click", () => {
|
|
692
|
+
showDetail({
|
|
693
|
+
name: "",
|
|
694
|
+
method: "GET",
|
|
695
|
+
url: "",
|
|
696
|
+
manual: false,
|
|
697
|
+
continue_on_error: true,
|
|
698
|
+
delay: 0,
|
|
699
|
+
headers: {},
|
|
700
|
+
print_keys: [],
|
|
701
|
+
condition: [],
|
|
702
|
+
retry: false,
|
|
703
|
+
}, -1, true);
|
|
704
|
+
detailBody.querySelector('[data-field="name"]')?.focus();
|
|
705
|
+
});
|
|
706
|
+
|
|
627
707
|
// ── Run chain ────────────────────────────────────────────
|
|
628
708
|
let pollTimer = null;
|
|
629
709
|
let currentRunId = null;
|
|
@@ -136,6 +136,38 @@ body > svg[style*="display:none"] { position: absolute; width: 0; height: 0; ove
|
|
|
136
136
|
/* ── Flow Canvas (VERTICAL) ───────────────────────────────── */
|
|
137
137
|
.flow-canvas-wrapper { overflow: auto; padding-bottom: 1rem; margin-bottom: 1rem; }
|
|
138
138
|
.flow-canvas { display: flex; flex-direction: column; align-items: center; gap: 0; padding: 1.5rem 1rem; }
|
|
139
|
+
.add-step-btn {
|
|
140
|
+
display: flex;
|
|
141
|
+
flex-direction: column;
|
|
142
|
+
align-items: center;
|
|
143
|
+
gap: 0.35rem;
|
|
144
|
+
margin: 0 auto 1rem;
|
|
145
|
+
padding: 0.15rem 0.5rem;
|
|
146
|
+
border: 0;
|
|
147
|
+
background: transparent;
|
|
148
|
+
color: var(--accent);
|
|
149
|
+
cursor: pointer;
|
|
150
|
+
}
|
|
151
|
+
.add-step-btn:hover { color: var(--accent-hover); }
|
|
152
|
+
.add-step-plus {
|
|
153
|
+
width: 35px;
|
|
154
|
+
height: 35px;
|
|
155
|
+
display: inline-flex;
|
|
156
|
+
align-items: center;
|
|
157
|
+
justify-content: center;
|
|
158
|
+
flex-shrink: 0;
|
|
159
|
+
border: 2px solid rgba(255,255,255,0.18);
|
|
160
|
+
border-radius: 50%;
|
|
161
|
+
background: var(--accent);
|
|
162
|
+
color: #fff;
|
|
163
|
+
font-size: 1.3rem;
|
|
164
|
+
font-weight: 500;
|
|
165
|
+
line-height: 1;
|
|
166
|
+
box-shadow: 0 3px 10px rgba(124,143,255,0.25);
|
|
167
|
+
transition: transform 0.15s, background 0.15s;
|
|
168
|
+
}
|
|
169
|
+
.add-step-btn:hover .add-step-plus { background: var(--accent-hover); transform: scale(1.06); }
|
|
170
|
+
.add-step-label { font-size: 0.72rem; font-weight: 700; line-height: 1; }
|
|
139
171
|
.step-node { display: flex; flex-direction: column; align-items: center; position: relative; }
|
|
140
172
|
.step-node > .step-index { width: 280px; text-align: center; }
|
|
141
173
|
.step-row {
|
|
@@ -544,6 +576,11 @@ body > svg[style*="display:none"] { position: absolute; width: 0; height: 0; ove
|
|
|
544
576
|
.create-status { margin-top: 0.75rem; font-size: 0.78rem; }
|
|
545
577
|
.create-status.success { color: var(--green); }
|
|
546
578
|
.create-status.error { color: var(--red); }
|
|
579
|
+
.delete-confirm-panel { width: 420px; }
|
|
580
|
+
.delete-confirm-warning { margin-top: 0.5rem; color: var(--text-muted); font-size: 0.8rem; }
|
|
581
|
+
.delete-confirm-btn { background: var(--red); color: #fff; border-color: var(--red); }
|
|
582
|
+
.delete-confirm-btn:hover { filter: brightness(1.08); }
|
|
583
|
+
.delete-confirm-btn:disabled { opacity: 0.6; cursor: not-allowed; }
|
|
547
584
|
|
|
548
585
|
/* ── Flow Documentation ───────────────────────────────────── */
|
|
549
586
|
.doc-header-card { background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius); padding: 1.5rem; margin-bottom: 1.5rem; }
|
|
@@ -796,6 +833,10 @@ body > svg[style*="display:none"] { position: absolute; width: 0; height: 0; ove
|
|
|
796
833
|
.response-pre-wrap .bounded-value-actions .value-expand::after {
|
|
797
834
|
display: none;
|
|
798
835
|
}
|
|
836
|
+
.response-pre-wrap .bounded-value-actions .value-expand {
|
|
837
|
+
width: 30px;
|
|
838
|
+
height: 30px;
|
|
839
|
+
}
|
|
799
840
|
|
|
800
841
|
scroll owner while header, footer, close, and save controls stay reachable. */
|
|
801
842
|
.step-detail-panel {
|
|
@@ -813,6 +854,9 @@ body > svg[style*="display:none"] { position: absolute; width: 0; height: 0; ove
|
|
|
813
854
|
max-width: 100%;
|
|
814
855
|
}
|
|
815
856
|
.detail-header { gap: 0.75rem; }
|
|
857
|
+
.detail-header-actions { display: flex; align-items: center; gap: 0.35rem; }
|
|
858
|
+
.detail-delete { color: var(--red); transform: translateX(20px); }
|
|
859
|
+
.detail-delete:hover { background: var(--red-bg); color: var(--red); }
|
|
816
860
|
.detail-header h3 {
|
|
817
861
|
min-width: 0;
|
|
818
862
|
overflow-wrap: anywhere;
|
|
@@ -87,6 +87,10 @@
|
|
|
87
87
|
</div>
|
|
88
88
|
<div class="flow-canvas-wrapper">
|
|
89
89
|
<div id="flow-canvas" class="flow-canvas"></div>
|
|
90
|
+
<button id="add-step-btn" type="button" class="add-step-btn" aria-label="Add Step">
|
|
91
|
+
<span class="add-step-plus" aria-hidden="true">+</span>
|
|
92
|
+
<span class="add-step-label">Add Step</span>
|
|
93
|
+
</button>
|
|
90
94
|
</div>
|
|
91
95
|
|
|
92
96
|
<div id="response-panel" class="response-panel hidden">
|
|
@@ -114,7 +118,10 @@
|
|
|
114
118
|
<div class="step-detail-panel">
|
|
115
119
|
<div class="detail-header">
|
|
116
120
|
<h3 id="detail-name"></h3>
|
|
117
|
-
<
|
|
121
|
+
<div class="detail-header-actions">
|
|
122
|
+
<button id="detail-delete" type="button" class="btn-icon-only detail-delete hidden" aria-label="Delete step" data-tooltip="Delete step"><svg class="icon"><use href="#i-trash"/></svg></button>
|
|
123
|
+
<button id="detail-close" type="button" class="btn-icon-only" aria-label="Close step details"><svg class="icon"><use href="#i-x"/></svg></button>
|
|
124
|
+
</div>
|
|
118
125
|
</div>
|
|
119
126
|
<div id="detail-body" class="detail-body"></div>
|
|
120
127
|
<div class="detail-footer">
|
|
@@ -126,6 +133,23 @@
|
|
|
126
133
|
</div>
|
|
127
134
|
</div>
|
|
128
135
|
|
|
136
|
+
<div id="delete-confirm-modal" class="modal-overlay hidden" role="dialog" aria-modal="true" aria-labelledby="delete-confirm-title">
|
|
137
|
+
<div class="modal-panel delete-confirm-panel">
|
|
138
|
+
<div class="modal-header">
|
|
139
|
+
<h3 id="delete-confirm-title">Delete Step</h3>
|
|
140
|
+
<button id="delete-confirm-close" type="button" class="btn-icon-only" aria-label="Close delete confirmation"><svg class="icon"><use href="#i-x"/></svg></button>
|
|
141
|
+
</div>
|
|
142
|
+
<div class="modal-body">
|
|
143
|
+
<p>Are you sure you want to delete <strong id="delete-confirm-step-name"></strong>?</p>
|
|
144
|
+
<p class="delete-confirm-warning">This action cannot be undone.</p>
|
|
145
|
+
</div>
|
|
146
|
+
<div class="modal-footer">
|
|
147
|
+
<button id="delete-confirm-cancel" type="button" class="btn btn-ghost btn-sm">Cancel</button>
|
|
148
|
+
<button id="delete-confirm-ok" type="button" class="btn btn-sm delete-confirm-btn">Delete Step</button>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
|
|
129
153
|
<script id="chain-json" type="application/json">{{ chain | tojson | safe }}</script>
|
|
130
154
|
<script>
|
|
131
155
|
const CHAIN_DATA = JSON.parse(document.getElementById('chain-json').textContent);
|
|
@@ -34,7 +34,10 @@
|
|
|
34
34
|
</symbol>
|
|
35
35
|
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
|
|
36
36
|
</symbol>
|
|
37
|
-
<symbol id="i-
|
|
37
|
+
<symbol id="i-trash" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
38
|
+
<polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14H6L5 6"/><path d="M10 11v5M14 11v5"/><path d="M9 6V3h6v3"/>
|
|
39
|
+
</symbol>
|
|
40
|
+
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
|
|
38
41
|
<circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/>
|
|
39
42
|
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/>
|
|
40
43
|
<line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/>
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{api_chain_runner-2.5.5 → api_chain_runner-2.5.6}/api_chain_runner.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{api_chain_runner-2.5.5 → api_chain_runner-2.5.6}/api_chain_runner.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|