From b51363bf724dcd5ed27facc72e8b082ce9d18a16 Mon Sep 17 00:00:00 2001 From: iRedTea Date: Tue, 13 Oct 2026 20:31:41 +0300 Subject: [PATCH] feat: optional label on a forward rule Co-Authored-By: Claude Opus 5.5 --- internal/server/db.go | 18 ++++++++++-------- internal/server/handlers_panel_fwd.go | 14 +++++++++++++- internal/server/web/templates/forwards.html | 7 ++++++- 3 files changed, 29 insertions(+), 10 deletions(-) diff --git a/internal/server/db.go b/internal/server/db.go index b1f4607..30698d0 100644 --- a/internal/server/db.go +++ b/internal/server/db.go @@ -106,6 +106,7 @@ CREATE TABLE IF NOT EXISTS forward_rules ( `ALTER TABLE agents ADD COLUMN run_user TEXT NOT NULL DEFAULT ''`, `ALTER TABLE agents ADD COLUMN login_user TEXT NOT NULL DEFAULT ''`, `ALTER TABLE forward_rules ADD COLUMN no_ufw INTEGER NOT NULL DEFAULT 0`, + `ALTER TABLE forward_rules ADD COLUMN label TEXT NOT NULL DEFAULT ''`, } { if _, err := db.sql.Exec(alter); err != nil && !strings.Contains(err.Error(), "duplicate column") { return err @@ -329,7 +330,8 @@ type ForwardRule struct { HostPort int ListenAddr string Proto string - NoUfw bool // the panel opens no ufw rule for this forward + NoUfw bool // the panel opens no ufw rule for this forward + Label string // optional note shown next to the rule } func (db *DB) CreateForwardRule(r ForwardRule) (int64, error) { @@ -337,9 +339,9 @@ func (db *DB) CreateForwardRule(r ForwardRule) (int64, error) { r.Proto = "http" } res, err := db.sql.Exec( - `INSERT INTO forward_rules(agent_id, client_port, host_port, listen_addr, proto, no_ufw, created_at) - VALUES(?, ?, ?, ?, ?, ?, ?)`, - r.AgentID, r.ClientPort, r.HostPort, r.ListenAddr, r.Proto, r.NoUfw, time.Now().Unix()) + `INSERT INTO forward_rules(agent_id, client_port, host_port, listen_addr, proto, no_ufw, label, created_at) + VALUES(?, ?, ?, ?, ?, ?, ?, ?)`, + r.AgentID, r.ClientPort, r.HostPort, r.ListenAddr, r.Proto, r.NoUfw, r.Label, time.Now().Unix()) if err != nil { return 0, err } @@ -348,7 +350,7 @@ func (db *DB) CreateForwardRule(r ForwardRule) (int64, error) { func (db *DB) ListForwardRules() ([]ForwardRule, error) { rows, err := db.sql.Query( - `SELECT id, agent_id, client_port, host_port, listen_addr, proto, no_ufw FROM forward_rules ORDER BY id`) + `SELECT id, agent_id, client_port, host_port, listen_addr, proto, no_ufw, label FROM forward_rules ORDER BY id`) if err != nil { return nil, err } @@ -356,7 +358,7 @@ func (db *DB) ListForwardRules() ([]ForwardRule, error) { var out []ForwardRule for rows.Next() { var r ForwardRule - if err := rows.Scan(&r.ID, &r.AgentID, &r.ClientPort, &r.HostPort, &r.ListenAddr, &r.Proto, &r.NoUfw); err != nil { + if err := rows.Scan(&r.ID, &r.AgentID, &r.ClientPort, &r.HostPort, &r.ListenAddr, &r.Proto, &r.NoUfw, &r.Label); err != nil { return nil, err } out = append(out, r) @@ -381,8 +383,8 @@ func (db *DB) ListForwardRulesByAgent(agentID string) ([]ForwardRule, error) { func (db *DB) GetForwardRule(id int64) (*ForwardRule, error) { r := &ForwardRule{} err := db.sql.QueryRow( - `SELECT id, agent_id, client_port, host_port, listen_addr, proto, no_ufw FROM forward_rules WHERE id = ?`, id). - Scan(&r.ID, &r.AgentID, &r.ClientPort, &r.HostPort, &r.ListenAddr, &r.Proto, &r.NoUfw) + `SELECT id, agent_id, client_port, host_port, listen_addr, proto, no_ufw, label FROM forward_rules WHERE id = ?`, id). + Scan(&r.ID, &r.AgentID, &r.ClientPort, &r.HostPort, &r.ListenAddr, &r.Proto, &r.NoUfw, &r.Label) if err == sql.ErrNoRows { return nil, nil } diff --git a/internal/server/handlers_panel_fwd.go b/internal/server/handlers_panel_fwd.go index 778ff7a..6a8202f 100644 --- a/internal/server/handlers_panel_fwd.go +++ b/internal/server/handlers_panel_fwd.go @@ -87,7 +87,7 @@ func (s *Server) handleForwardAdd(w http.ResponseWriter, r *http.Request) { return } rule := ForwardRule{AgentID: id, ClientPort: clientPort, HostPort: hostPort, ListenAddr: listenAddr, Proto: protoKind, - NoUfw: r.FormValue("no_ufw") != ""} + NoUfw: r.FormValue("no_ufw") != "", Label: forwardLabel(r.FormValue("label"))} ruleID, err := s.db.CreateForwardRule(rule) if err != nil { http.Redirect(w, r, "/agents/"+id+"/forwards?err=db", http.StatusSeeOther) @@ -130,3 +130,15 @@ func (s *Server) removeForward(rid int64) { _ = s.db.DeleteForwardRule(rid) s.ufw.revokeForRule(rid) } + +// forwardLabelMax bounds a rule's label, in characters. +const forwardLabelMax = 80 + +// forwardLabel tidies the optional note of a rule: one line, bounded length. +func forwardLabel(v string) string { + v = strings.Join(strings.Fields(v), " ") + if r := []rune(v); len(r) > forwardLabelMax { + v = string(r[:forwardLabelMax]) + } + return v +} diff --git a/internal/server/web/templates/forwards.html b/internal/server/web/templates/forwards.html index fa650d8..9bc5fd0 100644 --- a/internal/server/web/templates/forwards.html +++ b/internal/server/web/templates/forwards.html @@ -40,7 +40,7 @@

Правила

{{if .Rules}} - + {{range .Rules}} @@ -48,6 +48,7 @@ +
ТипХост (порт)→ Клиент (порт)Слушать на
ТипХост (порт)→ Клиент (порт)Слушать наМетка
:{{.HostPort}} 127.0.0.1:{{.ClientPort}} {{.ListenAddr}}{{if and .NoUfw $.UfwEnabled}} · без ufw{{end}}{{.Label}}
@@ -89,6 +90,10 @@ +
+ + +
{{if .UfwEnabled}}