mirror of
https://github.com/niusmallnan/steve.git
synced 2025-06-28 15:36:54 +00:00
Add UI back to steve
This commit is contained in:
parent
00da04b7d5
commit
e8086b4525
@ -6,6 +6,7 @@ import (
|
||||
steveauth "github.com/rancher/steve/pkg/auth"
|
||||
authcli "github.com/rancher/steve/pkg/auth/cli"
|
||||
"github.com/rancher/steve/pkg/server"
|
||||
"github.com/rancher/steve/pkg/ui"
|
||||
"github.com/rancher/wrangler/pkg/kubeconfig"
|
||||
"github.com/rancher/wrangler/pkg/ratelimit"
|
||||
"github.com/urfave/cli"
|
||||
@ -13,9 +14,10 @@ import (
|
||||
|
||||
type Config struct {
|
||||
KubeConfig string
|
||||
Context string
|
||||
HTTPSListenPort int
|
||||
HTTPListenPort int
|
||||
Authentication bool
|
||||
UIPath string
|
||||
|
||||
WebhookConfig authcli.WebhookConfig
|
||||
}
|
||||
@ -33,13 +35,13 @@ func (c *Config) ToServer(ctx context.Context) (*server.Server, error) {
|
||||
auth steveauth.Middleware
|
||||
)
|
||||
|
||||
restConfig, err := kubeconfig.GetNonInteractiveClientConfig(c.KubeConfig).ClientConfig()
|
||||
restConfig, err := kubeconfig.GetNonInteractiveClientConfigWithContext(c.KubeConfig, c.Context).ClientConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
restConfig.RateLimiter = ratelimit.None
|
||||
|
||||
if c.Authentication {
|
||||
if c.WebhookConfig.WebhookAuthentication {
|
||||
auth, err = c.WebhookConfig.WebhookMiddleware()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -48,6 +50,7 @@ func (c *Config) ToServer(ctx context.Context) (*server.Server, error) {
|
||||
|
||||
return server.New(ctx, restConfig, &server.Options{
|
||||
AuthMiddleware: auth,
|
||||
Next: ui.New(c.UIPath),
|
||||
})
|
||||
}
|
||||
|
||||
@ -58,6 +61,15 @@ func Flags(config *Config) []cli.Flag {
|
||||
EnvVar: "KUBECONFIG",
|
||||
Destination: &config.KubeConfig,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "context",
|
||||
EnvVar: "CONTEXT",
|
||||
Destination: &config.Context,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "ui-path",
|
||||
Destination: &config.UIPath,
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "https-listen-port",
|
||||
Value: 9443,
|
||||
@ -68,10 +80,6 @@ func Flags(config *Config) []cli.Flag {
|
||||
Value: 9080,
|
||||
Destination: &config.HTTPListenPort,
|
||||
},
|
||||
cli.BoolTFlag{
|
||||
Name: "authentication",
|
||||
Destination: &config.Authentication,
|
||||
},
|
||||
}
|
||||
|
||||
return append(flags, authcli.Flags(&config.WebhookConfig)...)
|
||||
|
177
pkg/ui/handler.go
Normal file
177
pkg/ui/handler.go
Normal file
@ -0,0 +1,177 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/rancher/apiserver/pkg/middleware"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var (
|
||||
insecureClient = &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
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.DenyFrameOptions,
|
||||
middleware.CacheMiddleware("json", "js", "css"),
|
||||
}.Handler,
|
||||
indexMiddleware: middleware.Chain{
|
||||
middleware.Gzip,
|
||||
middleware.NoCache,
|
||||
middleware.DenyFrameOptions,
|
||||
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 {
|
||||
_ = serveIndex(rw, path)
|
||||
} else {
|
||||
http.ServeFile(rw, req, filepath.Join(path, "index.html"))
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
func serveIndex(resp io.Writer, url string) error {
|
||||
r, err := insecureClient.Get(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer r.Body.Close()
|
||||
|
||||
_, err = io.Copy(resp, r.Body)
|
||||
return err
|
||||
}
|
38
pkg/ui/routes.go
Normal file
38
pkg/ui/routes.go
Normal file
@ -0,0 +1,38 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
func New(path string) http.Handler {
|
||||
vue := NewUIHandler(&Options{
|
||||
Path: func() string {
|
||||
if path == "" {
|
||||
return defaultPath
|
||||
}
|
||||
return path
|
||||
},
|
||||
})
|
||||
|
||||
router := mux.NewRouter()
|
||||
router.UseEncodedPath()
|
||||
|
||||
router.Handle("/", http.RedirectHandler("/dashboard/", http.StatusFound))
|
||||
router.Handle("/dashboard", http.RedirectHandler("/dashboard/", http.StatusFound))
|
||||
router.Handle("/dashboard/", vue.IndexFile())
|
||||
router.Handle("/favicon.png", vue.ServeFaviconDashboard())
|
||||
router.Handle("/favicon.ico", vue.ServeFaviconDashboard())
|
||||
router.PathPrefix("/dashboard/").Handler(vue.IndexFileOnNotFound())
|
||||
router.PathPrefix("/k8s/clusters/local").HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
url := strings.TrimPrefix(req.URL.Path, "/k8s/clusters/local")
|
||||
if url == "" {
|
||||
url = "/"
|
||||
}
|
||||
http.Redirect(rw, req, url, http.StatusFound)
|
||||
})
|
||||
|
||||
return router
|
||||
}
|
Loading…
Reference in New Issue
Block a user