feat(10.1-01): serve controller JS/CSS and run registered toolbar actions
- boardwalk exports ContentType and SetSecurityHeaders
- pact.AdminClientAssets files are read and hashed at boot and served by exact
key under {prefix}/assets/{vendor}/{plugin}/ with nosniff, CSP, CORP,
no-cache and an ETag; a miss falls through to the SPA
- list and form schemas carry assets URLs with a ?v= hash
- toolbar.buttons resolves create, delete and registered actions after decode;
toolbarActions is permission-filtered per admin
- POST .../toolbar/{action} behind requireAjax and action permissions
This commit is contained in:
54
modules/cabana/plugin_assets.go
Normal file
54
modules/cabana/plugin_assets.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package cabana
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"git.golem15.com/golem15/summercms/modules/boardwalk"
|
||||
)
|
||||
|
||||
// pluginAsset serves GET {prefix}/assets/{vendor}/{plugin}/{file...}: a
|
||||
// controller's declared JS or CSS file, looked up by exact key in the map
|
||||
// built at boot, so a plugin's embedded tree is never exposed wholesale and
|
||||
// traversal matches no key. A miss falls through to the SPA handler, which
|
||||
// serves the embedded dist assets and answers any other name with its 404.
|
||||
//
|
||||
// Plugin files are not content-hashed, so they are revalidated on every use
|
||||
// (no-cache plus a sha256 ETag); the schema URLs carry a ?v= hash instead of
|
||||
// the long-lived caching the SPA's hashed dist files get.
|
||||
func (s *service) pluginAsset(w http.ResponseWriter, r *http.Request) {
|
||||
var asset *pluginAsset
|
||||
if s != nil && s.reg != nil {
|
||||
asset = s.reg.assets[r.PathValue("vendor")+"/"+r.PathValue("plugin")+"/"+r.PathValue("file")]
|
||||
}
|
||||
if asset == nil {
|
||||
s.serveSPA(w, r)
|
||||
return
|
||||
}
|
||||
h := w.Header()
|
||||
boardwalk.SetSecurityHeaders(h)
|
||||
h.Set("Cross-Origin-Resource-Policy", "same-origin")
|
||||
h.Set("Content-Type", asset.contentType)
|
||||
h.Set("Cache-Control", "no-cache")
|
||||
h.Set("ETag", asset.etag)
|
||||
http.ServeContent(w, r, path.Base(asset.key), time.Time{}, bytes.NewReader(asset.body))
|
||||
}
|
||||
|
||||
// controllerAssets are the same-origin URLs of a controller's plugin files,
|
||||
// each with a ?v= content hash so a rebuilt binary never serves stale JS.
|
||||
func (s *service) controllerAssets(cc *CompiledController) ControllerAssets {
|
||||
out := ControllerAssets{Scripts: []string{}, Styles: []string{}}
|
||||
if cc == nil {
|
||||
return out
|
||||
}
|
||||
base := s.adminPrefix() + "/assets/"
|
||||
for _, file := range cc.scripts {
|
||||
out.Scripts = append(out.Scripts, base+file.key+"?v="+file.version)
|
||||
}
|
||||
for _, file := range cc.styles {
|
||||
out.Styles = append(out.Styles, base+file.key+"?v="+file.version)
|
||||
}
|
||||
return out
|
||||
}
|
||||
Reference in New Issue
Block a user