This commit is contained in:
Martino Ferrari
2026-04-26 11:01:55 +02:00
parent 91b42027c9
commit e83e183673
17 changed files with 1009 additions and 182 deletions
+88 -6
View File
@@ -7,6 +7,7 @@ import (
"io"
"log/slog"
"net/http"
"strings"
"github.com/uopi/uopi/internal/broker"
"github.com/uopi/uopi/internal/datasource"
@@ -16,15 +17,16 @@ import (
// Handler holds the dependencies shared across all API endpoints.
type Handler struct {
broker *broker.Broker
synthetic *synthetic.Synthetic // nil if not enabled
store *storage.Store
log *slog.Logger
broker *broker.Broker
synthetic *synthetic.Synthetic // nil if not enabled
store *storage.Store
channelFinderURL string // empty if not configured
log *slog.Logger
}
// New creates an API Handler. synth may be nil if the synthetic DS is disabled.
func New(b *broker.Broker, synth *synthetic.Synthetic, store *storage.Store, log *slog.Logger) *Handler {
return &Handler{broker: b, synthetic: synth, store: store, log: log}
func New(b *broker.Broker, synth *synthetic.Synthetic, store *storage.Store, channelFinderURL string, log *slog.Logger) *Handler {
return &Handler{broker: b, synthetic: synth, store: store, channelFinderURL: channelFinderURL, log: log}
}
// Register mounts all API routes onto mux under the given prefix.
@@ -32,6 +34,8 @@ func New(b *broker.Broker, synth *synthetic.Synthetic, store *storage.Store, log
func (h *Handler) Register(mux *http.ServeMux, prefix string) {
mux.HandleFunc("GET "+prefix+"/datasources", h.listDataSources)
mux.HandleFunc("GET "+prefix+"/signals", h.listSignals)
mux.HandleFunc("GET "+prefix+"/signals/search", h.searchSignals)
mux.HandleFunc("GET "+prefix+"/channel-finder", h.channelFinder)
mux.HandleFunc("GET "+prefix+"/interfaces", h.listInterfaces)
mux.HandleFunc("POST "+prefix+"/interfaces", h.createInterface)
mux.HandleFunc("GET "+prefix+"/interfaces/{id}", h.getInterface)
@@ -100,6 +104,84 @@ func (h *Handler) listSignals(w http.ResponseWriter, r *http.Request) {
jsonOK(w, out)
}
// ── /signals/search ───────────────────────────────────────────────────────────
func (h *Handler) searchSignals(w http.ResponseWriter, r *http.Request) {
q := strings.ToLower(r.URL.Query().Get("q"))
dsName := r.URL.Query().Get("ds")
var sources []datasource.DataSource
if dsName != "" {
ds, ok := h.broker.Source(dsName)
if !ok {
jsonError(w, http.StatusNotFound, "unknown data source: "+dsName)
return
}
sources = []datasource.DataSource{ds}
} else {
sources = h.broker.DataSources()
}
var out []signalInfo
for _, ds := range sources {
metas, err := ds.ListSignals(r.Context())
if err != nil {
continue
}
for _, m := range metas {
if q == "" || strings.Contains(strings.ToLower(m.Name), q) || strings.Contains(strings.ToLower(m.Description), q) {
si := metaToSignalInfo(m)
out = append(out, si)
}
}
}
if out == nil {
out = []signalInfo{}
}
jsonOK(w, out)
}
// ── /channel-finder ───────────────────────────────────────────────────────────
// channelFinder proxies a query to the EPICS Channel Finder service when one
// is configured, returning a flat list of PV names. Returns 501 otherwise.
func (h *Handler) channelFinder(w http.ResponseWriter, r *http.Request) {
if h.channelFinderURL == "" {
jsonError(w, http.StatusNotImplemented, "Channel Finder not configured (set channel_finder_url in config)")
return
}
q := r.URL.Query().Get("q")
cfURL := strings.TrimRight(h.channelFinderURL, "/") + "/resources/channels"
if q != "" {
cfURL += "?~name=*" + q + "*"
}
resp, err := http.Get(cfURL) //nolint:noctx
if err != nil {
jsonError(w, http.StatusBadGateway, "Channel Finder request failed: "+err.Error())
return
}
defer resp.Body.Close()
// Channel Finder returns JSON array of channel objects; extract names.
var channels []struct {
Name string `json:"name"`
}
if err := json.NewDecoder(resp.Body).Decode(&channels); err != nil {
jsonError(w, http.StatusBadGateway, "Channel Finder response parse error: "+err.Error())
return
}
names := make([]string, 0, len(channels))
for _, ch := range channels {
if ch.Name != "" {
names = append(names, ch.Name)
}
}
jsonOK(w, names)
}
// ── /interfaces ───────────────────────────────────────────────────────────────
func (h *Handler) listInterfaces(w http.ResponseWriter, r *http.Request) {