-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathui_handler.go
More file actions
155 lines (142 loc) · 5.27 KB
/
ui_handler.go
File metadata and controls
155 lines (142 loc) · 5.27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
package external
import (
"net/http"
"sort"
"strings"
)
// UIPluginHandler provides HTTP API endpoints for managing UI plugins.
//
// Routes registered by RegisterRoutes:
//
// GET /api/v1/plugins/ui – list loaded UI plugins
// GET /api/v1/plugins/ui/available – list all discovered UI plugins
// GET /api/v1/plugins/ui/{name}/manifest – get a plugin's UI manifest
// POST /api/v1/plugins/ui/{name}/load – load a UI plugin
// POST /api/v1/plugins/ui/{name}/unload – unload a UI plugin
// POST /api/v1/plugins/ui/{name}/reload – hot-reload a UI plugin
// GET /api/v1/plugins/ui/{name}/assets/{path…} – serve static assets
type UIPluginHandler struct {
manager *UIPluginManager
}
// NewUIPluginHandler creates a new handler backed by the given UIPluginManager.
func NewUIPluginHandler(manager *UIPluginManager) *UIPluginHandler {
return &UIPluginHandler{manager: manager}
}
// RegisterRoutes registers all UI plugin HTTP routes on mux.
func (h *UIPluginHandler) RegisterRoutes(mux *http.ServeMux) {
mux.HandleFunc("GET /api/v1/plugins/ui", h.handleListLoaded)
mux.HandleFunc("GET /api/v1/plugins/ui/available", h.handleListAvailable)
mux.HandleFunc("GET /api/v1/plugins/ui/{name}/manifest", h.handleGetManifest)
mux.HandleFunc("POST /api/v1/plugins/ui/{name}/load", h.handleLoad)
mux.HandleFunc("POST /api/v1/plugins/ui/{name}/unload", h.handleUnload)
mux.HandleFunc("POST /api/v1/plugins/ui/{name}/reload", h.handleReload)
mux.HandleFunc("GET /api/v1/plugins/ui/{name}/assets/", h.handleServeAssets)
}
// handleListLoaded returns info about all currently loaded UI plugins.
func (h *UIPluginHandler) handleListLoaded(w http.ResponseWriter, _ *http.Request) {
infos := h.manager.AllUIPluginInfos()
if infos == nil {
infos = []UIPluginInfo{}
}
sort.Slice(infos, func(i, j int) bool { return infos[i].Name < infos[j].Name })
writeOK(w, infos)
}
// handleListAvailable lists all discovered UI plugin directories (whether
// loaded or not).
func (h *UIPluginHandler) handleListAvailable(w http.ResponseWriter, _ *http.Request) {
names, err := h.manager.DiscoverPlugins()
if err != nil {
writeError(w, http.StatusInternalServerError, err.Error())
return
}
if names == nil {
names = []string{}
}
sort.Strings(names)
type entry struct {
Name string `json:"name"`
Loaded bool `json:"loaded"`
}
plugins := make([]entry, 0, len(names))
for _, name := range names {
plugins = append(plugins, entry{
Name: name,
Loaded: h.manager.IsLoaded(name),
})
}
writeOK(w, plugins)
}
// handleGetManifest returns the UI manifest for a loaded plugin.
func (h *UIPluginHandler) handleGetManifest(w http.ResponseWriter, r *http.Request) {
name := r.PathValue("name")
entry, ok := h.manager.GetPlugin(name)
if !ok {
writeError(w, http.StatusNotFound, "UI plugin not found or not loaded")
return
}
writeOK(w, entry.Manifest)
}
// handleLoad loads a UI plugin by reading its ui.json from disk.
func (h *UIPluginHandler) handleLoad(w http.ResponseWriter, r *http.Request) {
name := r.PathValue("name")
if name == "" {
writeError(w, http.StatusBadRequest, "plugin name is required")
return
}
if err := h.manager.LoadPlugin(name); err != nil {
writeError(w, http.StatusInternalServerError, err.Error())
return
}
writeOK(w, map[string]string{"name": name, "action": "loaded"})
}
// handleUnload removes a UI plugin from the manager.
func (h *UIPluginHandler) handleUnload(w http.ResponseWriter, r *http.Request) {
name := r.PathValue("name")
if name == "" {
writeError(w, http.StatusBadRequest, "plugin name is required")
return
}
if err := h.manager.UnloadPlugin(name); err != nil {
writeError(w, http.StatusInternalServerError, err.Error())
return
}
writeOK(w, map[string]string{"name": name, "action": "unloaded"})
}
// handleReload hot-reloads a UI plugin by re-reading its manifest and assets
// from disk. This is the primary mechanism for hot-deploy: copy updated files
// to the plugin directory and call this endpoint.
func (h *UIPluginHandler) handleReload(w http.ResponseWriter, r *http.Request) {
name := r.PathValue("name")
if name == "" {
writeError(w, http.StatusBadRequest, "plugin name is required")
return
}
if err := h.manager.ReloadPlugin(name); err != nil {
writeError(w, http.StatusInternalServerError, err.Error())
return
}
writeOK(w, map[string]string{"name": name, "action": "reloaded"})
}
// handleServeAssets serves static asset files for a loaded UI plugin.
// The URL prefix /api/v1/plugins/ui/{name}/assets/ is stripped before
// delegating to an http.FileServer rooted at the plugin's assets directory.
func (h *UIPluginHandler) handleServeAssets(w http.ResponseWriter, r *http.Request) {
name := r.PathValue("name")
assetHandler := h.manager.ServeAssets(name)
if assetHandler == nil {
writeError(w, http.StatusNotFound, "UI plugin not found or not loaded")
return
}
// Strip the route prefix so http.FileServer resolves paths relative to
// the assets root.
prefix := "/api/v1/plugins/ui/" + name + "/assets"
stripped := strings.TrimPrefix(r.URL.Path, prefix)
if stripped == r.URL.Path {
// Path did not begin with the expected prefix – shouldn't happen.
http.NotFound(w, r)
return
}
r2 := r.Clone(r.Context())
r2.URL.Path = stripped
assetHandler.ServeHTTP(w, r2)
}