phase 10
This commit is contained in:
+88
-6
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user