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:
@@ -0,0 +1,114 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/owen/vibeproxy/internal/auth"
|
||||
"github.com/owen/vibeproxy/internal/database"
|
||||
"github.com/owen/vibeproxy/internal/logging"
|
||||
"github.com/owen/vibeproxy/internal/models"
|
||||
)
|
||||
|
||||
func (h *Web) handleStreamsList(w http.ResponseWriter, r *http.Request) {
|
||||
streams, err := database.ListStreams(h.db)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
render(w, "streams", PageData{
|
||||
Active: "streams", Title: "TCP/UDP Streams", CSRFToken: h.csrf.Token(w),
|
||||
Flash: r.URL.Query().Get("flash"), FlashKind: r.URL.Query().Get("kind"),
|
||||
Data: streams,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *Web) handleStreamForm(w http.ResponseWriter, r *http.Request) {
|
||||
var s models.Stream
|
||||
isNew := true
|
||||
if idStr := r.PathValue("id"); idStr != "" {
|
||||
isNew = false
|
||||
id, _ := strconv.ParseInt(idStr, 10, 64)
|
||||
existing, err := database.GetStream(h.db, id)
|
||||
if err != nil {
|
||||
http.Error(w, "stream not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
s = *existing
|
||||
} else {
|
||||
s = models.Stream{Protocol: models.StreamTCP, ConnectTimeoutMS: 5000, IdleTimeoutMS: 300000, Enabled: true}
|
||||
}
|
||||
render(w, "stream_form", PageData{
|
||||
Active: "streams", Title: pageTitle(isNew, "Stream"), CSRFToken: h.csrf.Token(w),
|
||||
Data: map[string]any{"Stream": s, "IsNew": isNew},
|
||||
})
|
||||
}
|
||||
|
||||
func (h *Web) handleStreamCreate(w http.ResponseWriter, r *http.Request) { h.saveStreamForm(w, r, 0) }
|
||||
func (h *Web) handleStreamUpdate(w http.ResponseWriter, r *http.Request) {
|
||||
id, _ := strconv.ParseInt(r.PathValue("id"), 10, 64)
|
||||
h.saveStreamForm(w, r, id)
|
||||
}
|
||||
|
||||
func (h *Web) saveStreamForm(w http.ResponseWriter, r *http.Request, id int64) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
s := models.Stream{
|
||||
ID: id,
|
||||
Protocol: models.StreamProtocol(r.FormValue("protocol")),
|
||||
ListenIP: r.FormValue("listen_ip"),
|
||||
ListenPort: atoiDefault(r.FormValue("listen_port"), 0),
|
||||
DestinationHost: r.FormValue("destination_host"),
|
||||
DestinationPort: atoiDefault(r.FormValue("destination_port"), 0),
|
||||
ConnectTimeoutMS: atoiDefault(r.FormValue("connect_timeout_ms"), 5000),
|
||||
IdleTimeoutMS: atoiDefault(r.FormValue("idle_timeout_ms"), 300000),
|
||||
ProxyProtocol: r.FormValue("proxy_protocol") == "on",
|
||||
Enabled: r.FormValue("enabled") == "on",
|
||||
Comments: r.FormValue("comments"),
|
||||
}
|
||||
if s.ListenPort <= 0 || s.ListenPort > 65535 || s.DestinationHost == "" || s.DestinationPort <= 0 {
|
||||
render(w, "stream_form", PageData{
|
||||
Active: "streams", Title: pageTitle(id == 0, "Stream"), CSRFToken: h.csrf.Token(w),
|
||||
Flash: "Please provide a valid listen port and destination.", FlashKind: "danger",
|
||||
Data: map[string]any{"Stream": s, "IsNew": id == 0},
|
||||
})
|
||||
return
|
||||
}
|
||||
if _, err := database.SaveStream(h.db, &s); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
h.reloadStreams(r)
|
||||
logging.Audit(h.db, auth.ActorFromContext(r.Context()), "stream_changed", fmt.Sprintf("%s:%d", s.Protocol, s.ListenPort), "")
|
||||
http.Redirect(w, r, "/streams?flash="+flashMsg("Stream saved")+"&kind=success", http.StatusFound)
|
||||
}
|
||||
|
||||
func (h *Web) handleStreamDelete(w http.ResponseWriter, r *http.Request) {
|
||||
id, _ := strconv.ParseInt(r.PathValue("id"), 10, 64)
|
||||
if err := database.DeleteStream(h.db, id); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
h.reloadStreams(r)
|
||||
logging.Audit(h.db, auth.ActorFromContext(r.Context()), "stream_changed", fmt.Sprintf("id=%d", id), "deleted")
|
||||
http.Redirect(w, r, "/streams?flash="+flashMsg("Stream deleted")+"&kind=success", http.StatusFound)
|
||||
}
|
||||
|
||||
func (h *Web) handleStreamToggle(w http.ResponseWriter, r *http.Request) {
|
||||
id, _ := strconv.ParseInt(r.PathValue("id"), 10, 64)
|
||||
existing, err := database.GetStream(h.db, id)
|
||||
if err != nil {
|
||||
http.Error(w, "not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
if err := database.SetStreamEnabled(h.db, id, !existing.Enabled); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
h.reloadStreams(r)
|
||||
logging.Audit(h.db, auth.ActorFromContext(r.Context()), "stream_changed", fmt.Sprintf("id=%d", id), fmt.Sprintf("enabled=%v", !existing.Enabled))
|
||||
http.Redirect(w, r, "/streams", http.StatusFound)
|
||||
}
|
||||
Reference in New Issue
Block a user