Wworking on improving the tool

This commit is contained in:
Martino Ferrari
2026-05-21 07:41:56 +02:00
parent 6ff8fb5c25
commit 71430bc3b0
30 changed files with 1820 additions and 331 deletions
+71 -10
View File
@@ -7,6 +7,7 @@ import (
"io"
"log/slog"
"net/http"
"net/url"
"strings"
"github.com/uopi/uopi/internal/broker"
@@ -21,12 +22,20 @@ type Handler struct {
synthetic *synthetic.Synthetic // nil if not enabled
store *storage.Store
channelFinderURL string // empty if not configured
archiverURL 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, channelFinderURL string, log *slog.Logger) *Handler {
return &Handler{broker: b, synthetic: synth, store: store, channelFinderURL: channelFinderURL, log: log}
func New(b *broker.Broker, synth *synthetic.Synthetic, store *storage.Store, channelFinderURL, archiverURL string, log *slog.Logger) *Handler {
return &Handler{
broker: b,
synthetic: synth,
store: store,
channelFinderURL: channelFinderURL,
archiverURL: archiverURL,
log: log,
}
}
// Register mounts all API routes onto mux under the given prefix.
@@ -36,6 +45,7 @@ func (h *Handler) Register(mux *http.ServeMux, prefix string) {
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+"/archiver/search", h.archiverSearch)
mux.HandleFunc("GET "+prefix+"/interfaces", h.listInterfaces)
mux.HandleFunc("POST "+prefix+"/interfaces", h.createInterface)
mux.HandleFunc("GET "+prefix+"/interfaces/{id}", h.getInterface)
@@ -72,14 +82,16 @@ func (h *Handler) listDataSources(w http.ResponseWriter, r *http.Request) {
// ── /signals ──────────────────────────────────────────────────────────────────
type signalInfo struct {
Name string `json:"name"`
Type string `json:"type"`
Unit string `json:"unit,omitempty"`
Description string `json:"description,omitempty"`
DisplayLow float64 `json:"displayLow"`
DisplayHigh float64 `json:"displayHigh"`
Writable bool `json:"writable"`
EnumStrings []string `json:"enumStrings,omitempty"`
Name string `json:"name"`
Type string `json:"type"`
Unit string `json:"unit,omitempty"`
Description string `json:"description,omitempty"`
DisplayLow float64 `json:"displayLow"`
DisplayHigh float64 `json:"displayHigh"`
Writable bool `json:"writable"`
EnumStrings []string `json:"enumStrings,omitempty"`
Properties map[string]string `json:"properties,omitempty"`
Tags []string `json:"tags,omitempty"`
}
func (h *Handler) listSignals(w http.ResponseWriter, r *http.Request) {
@@ -160,6 +172,12 @@ func (h *Handler) channelFinder(w http.ResponseWriter, r *http.Request) {
cfURL := strings.TrimRight(h.channelFinderURL, "/") + "/resources/channels"
if q != "" {
cfURL += "?~name=*" + q + "*"
} else if r.URL.RawQuery != "" && r.URL.RawQuery != "q=" {
cfURL += "?" + r.URL.RawQuery
} else {
// Empty query check: return empty results to signal CFS is configured.
jsonOK(w, []struct{}{})
return
}
resp, err := http.Get(cfURL) //nolint:noctx
@@ -187,6 +205,47 @@ func (h *Handler) channelFinder(w http.ResponseWriter, r *http.Request) {
jsonOK(w, names)
}
// ── /archiver/search ──────────────────────────────────────────────────────────
// archiverSearch proxies a search query to the EPICS Archive Appliance
// management API when configured. Returns 501 otherwise.
func (h *Handler) archiverSearch(w http.ResponseWriter, r *http.Request) {
if h.archiverURL == "" {
jsonError(w, http.StatusNotImplemented, "Archiver not configured (set archive_url in config)")
return
}
pattern := r.URL.Query().Get("q")
if pattern == "" {
pattern = "*"
}
// AA expects glob patterns. If no glob characters, wrap in stars.
if !strings.ContainsAny(pattern, "*?[]") {
pattern = "*" + pattern + "*"
}
searchURL := strings.TrimRight(h.archiverURL, "/") + "/mgmt/bpl/getAllPVs?pv=" + url.QueryEscape(pattern)
resp, err := http.Get(searchURL) //nolint:noctx
if err != nil {
jsonError(w, http.StatusBadGateway, "Archiver request failed: "+err.Error())
return
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
jsonError(w, http.StatusBadGateway, "Archiver returned status "+resp.Status)
return
}
var pvs []string
if err := json.NewDecoder(resp.Body).Decode(&pvs); err != nil {
jsonError(w, http.StatusBadGateway, "Archiver response parse error: "+err.Error())
return
}
jsonOK(w, pvs)
}
// ── /groups ───────────────────────────────────────────────────────────────────
// getGroups returns the workspace group tree as a JSON array.
@@ -416,6 +475,8 @@ func metaToSignalInfo(m datasource.Metadata) signalInfo {
DisplayHigh: m.DisplayHigh,
Writable: m.Writable,
EnumStrings: m.EnumStrings,
Properties: m.Properties,
Tags: m.Tags,
}
}