Initial vibeproxy implementation

Self-contained HTTP/HTTPS/TCP/UDP reverse proxy manager written in Go,
with SQLite storage, a Bootstrap 5 admin UI (Go templates, no JS
framework), and a REST API under /api/v1.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QTXFrNP1imCW7jtfL5SZrw
This commit is contained in:
2026-08-17 10:28:59 -05:00
co-authored by Claude Sonnet 5
commit ba1f7c4c03
105 changed files with 9142 additions and 0 deletions
+104
View File
@@ -0,0 +1,104 @@
// vibeproxy admin UI — minimal vanilla JS, no framework.
(function () {
"use strict";
// Generic delete-confirmation modal. Any element with
// data-confirm-delete-form="<form id>" and data-confirm-message="..."
// opens #confirmDeleteModal and, on confirm, submits the named form.
document.addEventListener("click", function (e) {
var trigger = e.target.closest("[data-confirm-delete-form]");
if (!trigger) return;
e.preventDefault();
var formId = trigger.getAttribute("data-confirm-delete-form");
var message = trigger.getAttribute("data-confirm-message") || "Are you sure? This cannot be undone.";
var modalEl = document.getElementById("confirmDeleteModal");
if (!modalEl) return;
modalEl.querySelector(".modal-body").textContent = message;
var confirmBtn = modalEl.querySelector("[data-confirm-action]");
confirmBtn.onclick = function () {
var form = document.getElementById(formId);
if (form) form.submit();
};
var modal = bootstrap.Modal.getOrCreateInstance(modalEl);
modal.show();
});
// Dynamic add/remove rows for repeatable form sections (backends,
// custom headers), driven by a <template id="{tableId}RowTemplate">.
document.querySelectorAll("[data-add-row]").forEach(function (btn) {
btn.addEventListener("click", function () {
var tableId = btn.getAttribute("data-add-row");
var table = document.getElementById(tableId);
var tmpl = document.getElementById(tableId + "RowTemplate");
if (!table || !tmpl) return;
var tbody = table.querySelector("tbody");
tbody.appendChild(tmpl.content.firstElementChild.cloneNode(true));
});
});
document.addEventListener("click", function (e) {
var btn = e.target.closest("[data-remove-row]");
if (!btn) return;
var tr = btn.closest("tr");
if (tr) tr.remove();
});
// Live client-side filter for any table with data-searchable="true",
// driven by an <input data-search-target="tableId">.
document.querySelectorAll("[data-search-target]").forEach(function (input) {
input.addEventListener("input", function () {
var table = document.getElementById(input.getAttribute("data-search-target"));
if (!table) return;
var q = input.value.trim().toLowerCase();
table.querySelectorAll("tbody tr").forEach(function (row) {
row.style.display = row.textContent.toLowerCase().indexOf(q) === -1 ? "none" : "";
});
});
});
// Domain / CIDR chip inputs: comma or newline separated list rendered
// into a hidden textarea, e.g. for proxy host domains.
document.querySelectorAll("[data-chip-input]").forEach(function (wrap) {
var hidden = wrap.querySelector("textarea");
var display = wrap.querySelector(".vp-chip-list");
var entry = wrap.querySelector("input[type=text]");
if (!hidden || !display || !entry) return;
function items() {
return hidden.value.split("\n").map(function (s) { return s.trim(); }).filter(Boolean);
}
function renderChips() {
display.innerHTML = "";
items().forEach(function (item, idx) {
var chip = document.createElement("span");
chip.className = "badge text-bg-secondary vp-chip";
chip.textContent = item;
var x = document.createElement("button");
x.type = "button";
x.className = "btn-close btn-close-white btn-sm ms-2";
x.setAttribute("aria-label", "Remove");
x.addEventListener("click", function () {
var list = items();
list.splice(idx, 1);
hidden.value = list.join("\n");
renderChips();
});
chip.appendChild(x);
display.appendChild(chip);
});
}
entry.addEventListener("keydown", function (e) {
if (e.key === "Enter" || e.key === ",") {
e.preventDefault();
var v = entry.value.trim().replace(/,$/, "");
if (v) {
var list = items();
list.push(v);
hidden.value = list.join("\n");
entry.value = "";
renderChips();
}
}
});
renderChips();
});
})();
File diff suppressed because one or more lines are too long