Major changes: logic add to panel, local variables, panel histor, users management...

This commit is contained in:
Martino Ferrari
2026-06-18 17:37:04 +02:00
parent 71430bc3b0
commit aba394b84d
54 changed files with 6104 additions and 1166 deletions
+30 -1
View File
@@ -6,11 +6,13 @@ import (
"errors"
"log/slog"
"net/http"
"strings"
"sync"
"time"
"github.com/coder/websocket"
"github.com/uopi/uopi/internal/access"
"github.com/uopi/uopi/internal/broker"
"github.com/uopi/uopi/internal/datasource"
"github.com/uopi/uopi/internal/metrics"
@@ -87,9 +89,21 @@ type histPoint struct {
type wsHandler struct {
broker *broker.Broker
log *slog.Logger
// userHeader is the HTTP header from which the per-session end-user identity
// is read (set by a trusted auth proxy). Empty disables per-user identity.
userHeader string
// policy enforces the global access level (blacklist) on signal writes.
policy *access.Policy
}
func (h *wsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Capture the end-user identity from the trusted proxy header (if enabled)
// before the connection is upgraded, while the original headers are present.
var user string
if h.userHeader != "" {
user = strings.TrimSpace(r.Header.Get(h.userHeader))
}
conn, err := websocket.Accept(w, r, &websocket.AcceptOptions{
// Permissive origin check for LAN / SSH-tunnel use; tighten when auth lands.
InsecureSkipVerify: true,
@@ -107,6 +121,8 @@ func (h *wsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
c := &wsClient{
conn: conn,
broker: h.broker,
user: user,
policy: h.policy,
outCh: make(chan []byte, 512),
updateCh: make(chan broker.Update, 1024),
subs: make(map[broker.SignalRef]func()),
@@ -136,6 +152,8 @@ type wsClient struct {
conn *websocket.Conn
broker *broker.Broker
log *slog.Logger
user string // end-user identity from the trusted proxy header ("" if none)
policy *access.Policy // global access-level enforcement
outCh chan []byte // serialised outgoing messages
updateCh chan broker.Update // raw updates from the broker
@@ -277,6 +295,14 @@ func (c *wsClient) handleUnsubscribe(refs []sigRef) {
}
func (c *wsClient) handleWrite(ctx context.Context, msg inMsg) {
// Enforce the user's global access level: blacklisted read-only / no-access
// users may not write signals, regardless of the channel's own writability.
if c.policy != nil && c.policy.Level(c.policy.ResolveUser(c.user)) < access.LevelWrite {
c.log.Warn("write: access denied", "ds", msg.DS, "name", msg.Name, "user", c.user)
c.sendError(ctx, "ACCESS_DENIED", "you do not have write access")
return
}
ds, ok := c.broker.Source(msg.DS)
if !ok {
c.log.Warn("write: unknown data source", "ds", msg.DS, "name", msg.Name)
@@ -305,8 +331,11 @@ func (c *wsClient) handleWrite(ctx context.Context, msg inMsg) {
return
}
c.log.Info("write", "ds", msg.DS, "name", msg.Name, "value", value)
c.log.Info("write", "ds", msg.DS, "name", msg.Name, "value", value, "user", c.user)
metrics.IncWrites()
// Attribute the write to the connecting end-user so data sources (EPICS) can
// act under that identity rather than the server's.
ctx = datasource.WithUser(ctx, c.user)
if err := ds.Write(ctx, msg.Name, value); err != nil {
c.log.Warn("write: ds.Write failed", "ds", msg.DS, "name", msg.Name, "err", err)
c.sendError(ctx, "WRITE_ERROR", err.Error())