1
0
mirror of https://github.com/rancher/steve.git synced 2025-04-28 11:14:43 +00:00
steve/pkg/ui/handler.go
2025-04-14 12:37:26 -07:00

174 lines
4.0 KiB
Go

package ui
import (
"io"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"sync"
"github.com/rancher/apiserver/pkg/middleware"
"github.com/sirupsen/logrus"
)
const (
defaultPath = "./ui"
)
type StringSetting func() string
type BoolSetting func() bool
type Handler struct {
pathSetting func() string
indexSetting func() string
releaseSetting func() bool
offlineSetting func() string
middleware func(http.Handler) http.Handler
indexMiddleware func(http.Handler) http.Handler
downloadOnce sync.Once
downloadSuccess bool
}
type Options struct {
// The location on disk of the UI files
Path StringSetting
// The HTTP URL of the index file to download
Index StringSetting
// Whether or not to run the UI offline, should return true/false/dynamic
Offline StringSetting
// Whether or not is it release, if true UI will run offline if set to dynamic
ReleaseSetting BoolSetting
}
func NewUIHandler(opts *Options) *Handler {
if opts == nil {
opts = &Options{}
}
h := &Handler{
indexSetting: opts.Index,
offlineSetting: opts.Offline,
pathSetting: opts.Path,
releaseSetting: opts.ReleaseSetting,
middleware: middleware.Chain{
middleware.Gzip,
middleware.FrameOptions,
middleware.CacheMiddleware("json", "js", "css"),
}.Handler,
indexMiddleware: middleware.Chain{
middleware.Gzip,
middleware.NoCache,
middleware.FrameOptions,
middleware.ContentType,
}.Handler,
}
if h.indexSetting == nil {
h.indexSetting = func() string {
return "https://releases.rancher.com/dashboard/latest/index.html"
}
}
if h.offlineSetting == nil {
h.offlineSetting = func() string {
return "dynamic"
}
}
if h.pathSetting == nil {
h.pathSetting = func() string {
return defaultPath
}
}
if h.releaseSetting == nil {
h.releaseSetting = func() bool {
return false
}
}
return h
}
func (u *Handler) canDownload(url string) bool {
u.downloadOnce.Do(func() {
if err := serveIndex(ioutil.Discard, url); err == nil {
u.downloadSuccess = true
} else {
logrus.Errorf("Failed to download %s, falling back to packaged UI", url)
}
})
return u.downloadSuccess
}
func (u *Handler) path() (path string, isURL bool) {
switch u.offlineSetting() {
case "dynamic":
if u.releaseSetting() {
return u.pathSetting(), false
}
if u.canDownload(u.indexSetting()) {
return u.indexSetting(), true
}
return u.pathSetting(), false
case "true":
return u.pathSetting(), false
default:
return u.indexSetting(), true
}
}
func (u *Handler) ServeAsset() http.Handler {
return u.middleware(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
http.FileServer(http.Dir(u.pathSetting())).ServeHTTP(rw, req)
}))
}
func (u *Handler) ServeFaviconDashboard() http.Handler {
return u.middleware(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
http.FileServer(http.Dir(filepath.Join(u.pathSetting(), "dashboard"))).ServeHTTP(rw, req)
}))
}
func (u *Handler) IndexFileOnNotFound() http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
// we ignore directories here because we want those to come from the CDN when running in that mode
if stat, err := os.Stat(filepath.Join(u.pathSetting(), req.URL.Path)); err == nil && !stat.IsDir() {
u.ServeAsset().ServeHTTP(rw, req)
} else {
u.IndexFile().ServeHTTP(rw, req)
}
})
}
func (u *Handler) IndexFile() http.Handler {
return u.indexMiddleware(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
if path, isURL := u.path(); isURL {
err := serveIndex(rw, path)
if err != nil {
logrus.Errorf("failed to download %s: %v", path, err)
}
} else {
http.ServeFile(rw, req, filepath.Join(path, "index.html"))
}
}))
}
func serveIndex(resp io.Writer, url string) error {
client := &http.Client{
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
},
}
r, err := client.Get(url)
if err != nil {
return err
}
defer r.Body.Close()
_, err = io.Copy(resp, r.Body)
return err
}