mirror of
https://github.com/cnrancher/kube-explorer.git
synced 2025-05-11 09:28:30 +00:00
refactor: UI resource logic
- Support embed api-ui resources - The ui-path arg will be applied if provided. Also applied to api-ui resource files
This commit is contained in:
parent
004e4751c8
commit
eacc47482e
@ -20,7 +20,8 @@ RUN if [ "${ARCH}" == "amd64" ]; then \
|
||||
fi
|
||||
COPY --from=tools /app/release-notary /usr/local/bin/
|
||||
ENV CATTLE_DASHBOARD_UI_VERSION="v2.8.0-kube-explorer-ui-rc3"
|
||||
|
||||
ENV CATTLE_API_UI_VERSION="1.1.11"
|
||||
|
||||
ENV DAPPER_ENV REPO TAG DRONE_TAG CROSS GOPROXY SKIP_COMPRESS GITHUB_REPOSITORY GITHUB_TOKEN
|
||||
ENV DAPPER_SOURCE /go/src/github.com/cnrancher/kube-explorer
|
||||
ENV DAPPER_OUTPUT ./bin ./dist
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
|
||||
var InsecureSkipTLSVerify bool
|
||||
var SystemDefaultRegistry string
|
||||
var APIUIVersion = "1.1.11"
|
||||
|
||||
var ShellPodImage string
|
||||
|
||||
@ -24,5 +25,11 @@ func Flags() []cli.Flag {
|
||||
Destination: &ShellPodImage,
|
||||
Value: "rancher/shell:v0.2.1-rc.7",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "apiui-version",
|
||||
Hidden: true,
|
||||
Destination: &APIUIVersion,
|
||||
Value: APIUIVersion,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
11
internal/config/steve.go
Normal file
11
internal/config/steve.go
Normal file
@ -0,0 +1,11 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/rancher/steve/pkg/debug"
|
||||
stevecli "github.com/rancher/steve/pkg/server/cli"
|
||||
)
|
||||
|
||||
var (
|
||||
Steve stevecli.Config
|
||||
Debug debug.Config
|
||||
)
|
@ -17,6 +17,7 @@ import (
|
||||
"github.com/cnrancher/kube-explorer/internal/config"
|
||||
"github.com/cnrancher/kube-explorer/internal/resources/cluster"
|
||||
"github.com/cnrancher/kube-explorer/internal/ui"
|
||||
"github.com/cnrancher/kube-explorer/internal/version"
|
||||
)
|
||||
|
||||
func ToServer(ctx context.Context, c *cli.Config, sqlCache bool) (*server.Server, error) {
|
||||
@ -48,10 +49,15 @@ func ToServer(ctx context.Context, c *cli.Config, sqlCache bool) (*server.Server
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ui, apiui := ui.New(&ui.Options{
|
||||
ReleaseSetting: version.IsRelease,
|
||||
Path: func() string { return c.UIPath },
|
||||
})
|
||||
|
||||
steveServer, err := server.New(ctx, restConfig, &server.Options{
|
||||
AuthMiddleware: auth,
|
||||
Controllers: controllers,
|
||||
Next: ui.New(c.UIPath),
|
||||
Next: ui,
|
||||
SQLCache: sqlCache,
|
||||
// router needs to hack here
|
||||
Router: func(h router.Handlers) http.Handler {
|
||||
@ -62,6 +68,8 @@ func ToServer(ctx context.Context, c *cli.Config, sqlCache bool) (*server.Server
|
||||
return nil, err
|
||||
}
|
||||
|
||||
steveServer.APIServer.CustomAPIUIResponseWriter(apiui.CSS(), apiui.JS(), func() string { return config.APIUIVersion })
|
||||
|
||||
// registrer local cluster
|
||||
if err := cluster.Register(ctx, steveServer, c.Context); err != nil {
|
||||
return steveServer, err
|
||||
|
55
internal/ui/apiui.go
Normal file
55
internal/ui/apiui.go
Normal file
@ -0,0 +1,55 @@
|
||||
package ui
|
||||
|
||||
import "github.com/rancher/apiserver/pkg/writer"
|
||||
|
||||
type APIUI struct {
|
||||
offline StringSetting
|
||||
release BoolSetting
|
||||
embed bool
|
||||
}
|
||||
|
||||
func apiUI(opt *Options) APIUI {
|
||||
var rtn = APIUI{
|
||||
offline: opt.Offline,
|
||||
release: opt.ReleaseSetting,
|
||||
embed: true,
|
||||
}
|
||||
if rtn.offline == nil {
|
||||
rtn.offline = StaticSetting("dynamic")
|
||||
}
|
||||
if rtn.release == nil {
|
||||
rtn.release = StaticSetting(false)
|
||||
}
|
||||
for _, file := range []string{
|
||||
"ui/api-ui/ui.min.css",
|
||||
"ui/api-ui/ui.min.js",
|
||||
} {
|
||||
if _, err := staticContent.Open(file); err != nil {
|
||||
rtn.embed = false
|
||||
break
|
||||
}
|
||||
}
|
||||
return rtn
|
||||
}
|
||||
|
||||
func (a APIUI) content(name string) writer.StringGetter {
|
||||
return func() (rtn string) {
|
||||
switch a.offline() {
|
||||
case "dynamic":
|
||||
if !a.release() && !a.embed {
|
||||
return ""
|
||||
}
|
||||
case "false":
|
||||
return ""
|
||||
}
|
||||
return name
|
||||
}
|
||||
}
|
||||
|
||||
func (a APIUI) CSS() writer.StringGetter {
|
||||
return a.content("/api-ui/ui.min.css")
|
||||
}
|
||||
|
||||
func (a APIUI) JS() writer.StringGetter {
|
||||
return a.content("/api-ui/ui.min.js")
|
||||
}
|
24
internal/ui/content/content.go
Normal file
24
internal/ui/content/content.go
Normal file
@ -0,0 +1,24 @@
|
||||
package content
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type fsFunc func(name string) (fs.File, error)
|
||||
|
||||
func (f fsFunc) Open(name string) (fs.File, error) {
|
||||
return f(name)
|
||||
}
|
||||
|
||||
type fsContent interface {
|
||||
ToFileServer(basePaths ...string) http.Handler
|
||||
Open(name string) (fs.File, error)
|
||||
}
|
||||
|
||||
type Handler interface {
|
||||
ServeAssets(middleware func(http.Handler) http.Handler, hext http.Handler) http.Handler
|
||||
ServeFaviconDashboard() http.Handler
|
||||
GetIndex() ([]byte, error)
|
||||
Refresh()
|
||||
}
|
97
internal/ui/content/external.go
Normal file
97
internal/ui/content/external.go
Normal file
@ -0,0 +1,97 @@
|
||||
package content
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultIndex = "https://releases.rancher.com/dashboard/latest/index.html"
|
||||
)
|
||||
|
||||
func NewExternal(getIndex func() string) Handler {
|
||||
return &externalIndexHandler{
|
||||
getIndexFunc: getIndex,
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
insecureClient = &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
_ Handler = &externalIndexHandler{}
|
||||
)
|
||||
|
||||
type externalIndexHandler struct {
|
||||
sync.RWMutex
|
||||
getIndexFunc func() string
|
||||
current string
|
||||
downloadSuccess *bool
|
||||
}
|
||||
|
||||
func (u *externalIndexHandler) ServeAssets(_ func(http.Handler) http.Handler, next http.Handler) http.Handler {
|
||||
return next
|
||||
}
|
||||
|
||||
func (u *externalIndexHandler) ServeFaviconDashboard() http.Handler {
|
||||
return http.NotFoundHandler()
|
||||
}
|
||||
|
||||
func (u *externalIndexHandler) GetIndex() ([]byte, error) {
|
||||
if u.canDownload() {
|
||||
var buffer bytes.Buffer
|
||||
if err := serveIndex(&buffer, u.current); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buffer.Bytes(), nil
|
||||
}
|
||||
return nil, errors.New("external index is not available")
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (u *externalIndexHandler) canDownload() bool {
|
||||
u.RLock()
|
||||
rtn := u.downloadSuccess
|
||||
u.RUnlock()
|
||||
if rtn != nil {
|
||||
return *rtn
|
||||
}
|
||||
|
||||
return u.refresh()
|
||||
}
|
||||
|
||||
func (u *externalIndexHandler) refresh() bool {
|
||||
u.Lock()
|
||||
defer u.RUnlock()
|
||||
|
||||
u.current = u.getIndexFunc()
|
||||
if u.current == "" {
|
||||
u.current = defaultIndex
|
||||
}
|
||||
t := serveIndex(io.Discard, u.current) == nil
|
||||
u.downloadSuccess = &t
|
||||
return t
|
||||
}
|
||||
|
||||
func (u *externalIndexHandler) Refresh() {
|
||||
_ = u.refresh()
|
||||
}
|
71
internal/ui/content/fs.go
Normal file
71
internal/ui/content/fs.go
Normal file
@ -0,0 +1,71 @@
|
||||
package content
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var _ Handler = &handler{}
|
||||
|
||||
func newFS(content fsContent) Handler {
|
||||
return &handler{
|
||||
content: content,
|
||||
cacheFS: &sync.Map{},
|
||||
}
|
||||
}
|
||||
|
||||
type handler struct {
|
||||
content fsContent
|
||||
cacheFS *sync.Map
|
||||
}
|
||||
|
||||
func (h *handler) pathExist(path string) bool {
|
||||
_, err := h.content.Open(path)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (h *handler) serveContent(basePaths ...string) http.Handler {
|
||||
key := filepath.Join(basePaths...)
|
||||
if rtn, ok := h.cacheFS.Load(key); ok {
|
||||
return rtn.(http.Handler)
|
||||
}
|
||||
|
||||
rtn := h.content.ToFileServer(basePaths...)
|
||||
h.cacheFS.Store(key, rtn)
|
||||
return rtn
|
||||
}
|
||||
|
||||
func (h *handler) Refresh() {
|
||||
h.cacheFS.Range(func(key, _ any) bool {
|
||||
h.cacheFS.Delete(key)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func (h *handler) ServeAssets(middleware func(http.Handler) http.Handler, next http.Handler) http.Handler {
|
||||
assets := middleware(h.serveContent())
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if h.pathExist(r.URL.Path) {
|
||||
assets.ServeHTTP(w, r)
|
||||
} else {
|
||||
next.ServeHTTP(w, r)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (h *handler) ServeFaviconDashboard() http.Handler {
|
||||
return h.serveContent("dashboard")
|
||||
|
||||
}
|
||||
|
||||
func (h *handler) GetIndex() ([]byte, error) {
|
||||
path := filepath.Join("dashboard", "index.html")
|
||||
f, err := h.content.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
return io.ReadAll(f)
|
||||
}
|
43
internal/ui/content/fs_embed.go
Normal file
43
internal/ui/content/fs_embed.go
Normal file
@ -0,0 +1,43 @@
|
||||
package content
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func NewEmbedded(staticContent embed.FS, prefix string) Handler {
|
||||
return newFS(&embedFS{
|
||||
pathPrefix: prefix,
|
||||
staticContent: staticContent,
|
||||
})
|
||||
}
|
||||
|
||||
var _ fsContent = &embedFS{}
|
||||
|
||||
type embedFS struct {
|
||||
pathPrefix string
|
||||
staticContent embed.FS
|
||||
}
|
||||
|
||||
// Open implements fsContent.
|
||||
func (e *embedFS) Open(name string) (fs.File, error) {
|
||||
return e.staticContent.Open(joinEmbedFilepath(e.pathPrefix, name))
|
||||
}
|
||||
|
||||
// ToFileServer implements fsContent.
|
||||
func (e *embedFS) ToFileServer(basePaths ...string) http.Handler {
|
||||
handler := fsFunc(func(name string) (fs.File, error) {
|
||||
assetPath := joinEmbedFilepath(joinEmbedFilepath(basePaths...), name)
|
||||
return e.Open(assetPath)
|
||||
})
|
||||
|
||||
return http.FileServer(http.FS(handler))
|
||||
}
|
||||
|
||||
func (e *embedFS) Refresh() error { return nil }
|
||||
|
||||
func joinEmbedFilepath(paths ...string) string {
|
||||
return filepath.ToSlash(filepath.Join(paths...))
|
||||
}
|
41
internal/ui/content/fs_filepath.go
Normal file
41
internal/ui/content/fs_filepath.go
Normal file
@ -0,0 +1,41 @@
|
||||
package content
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func NewFilepath(getPath func() string) Handler {
|
||||
return newFS(&filepathFS{
|
||||
getPath: getPath,
|
||||
})
|
||||
}
|
||||
|
||||
var _ fsContent = &filepathFS{}
|
||||
|
||||
type filepathFS struct {
|
||||
getPath func() string
|
||||
}
|
||||
|
||||
func (f *filepathFS) ToFileServer(basePaths ...string) http.Handler {
|
||||
root := f.getPath()
|
||||
if root == "" {
|
||||
return http.NotFoundHandler()
|
||||
}
|
||||
path := filepath.Join(append([]string{string(root)}, basePaths...)...)
|
||||
return http.FileServer(http.Dir(path))
|
||||
}
|
||||
|
||||
func (f *filepathFS) Open(name string) (fs.File, error) {
|
||||
root := f.getPath()
|
||||
if root == "" {
|
||||
return nil, errors.New("filepath fs is not ready")
|
||||
}
|
||||
return http.Dir(root).Open(name)
|
||||
}
|
||||
|
||||
func (f *filepathFS) Refresh() error {
|
||||
return nil
|
||||
}
|
7
internal/ui/dev.go
Normal file
7
internal/ui/dev.go
Normal file
@ -0,0 +1,7 @@
|
||||
//go:build !embed
|
||||
|
||||
package ui
|
||||
|
||||
import "embed"
|
||||
|
||||
var staticContent embed.FS
|
@ -4,88 +4,9 @@ package ui
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// content holds our static web server content.
|
||||
//
|
||||
//go:embed all:ui/*
|
||||
var staticContent embed.FS
|
||||
|
||||
type fsFunc func(name string) (fs.File, error)
|
||||
|
||||
func (f fsFunc) Open(name string) (fs.File, error) {
|
||||
return f(name)
|
||||
}
|
||||
|
||||
func pathExist(path string) bool {
|
||||
_, err := staticContent.Open(path)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func openFile(path string) (fs.File, error) {
|
||||
file, err := staticContent.Open(path)
|
||||
if err != nil {
|
||||
logrus.Errorf("openEmbedFile %s err: %v", path, err)
|
||||
}
|
||||
return file, err
|
||||
}
|
||||
|
||||
func serveEmbed(basePaths ...string) http.Handler {
|
||||
handler := fsFunc(func(name string) (fs.File, error) {
|
||||
logrus.Debugf("serveEmbed name: %s", name)
|
||||
assetPath := joinEmbedFilepath(append(basePaths, name)...)
|
||||
logrus.Debugf("serveEmbed final path: %s", assetPath)
|
||||
return openFile(assetPath)
|
||||
})
|
||||
|
||||
return http.FileServer(http.FS(handler))
|
||||
}
|
||||
|
||||
func serveEmbedIndex(basePath string) http.Handler {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
path := joinEmbedFilepath(basePath, "dashboard", "index.html")
|
||||
logrus.Debugf("serveEmbedIndex : %s", path)
|
||||
f, _ := staticContent.Open(path)
|
||||
io.Copy(rw, f)
|
||||
f.Close()
|
||||
})
|
||||
}
|
||||
|
||||
func (u *Handler) ServeAsset() http.Handler {
|
||||
return u.middleware(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
serveEmbed(u.pathSetting()).ServeHTTP(rw, req)
|
||||
}))
|
||||
}
|
||||
|
||||
func (u *Handler) ServeFaviconDashboard() http.Handler {
|
||||
return u.middleware(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
serveEmbed(u.pathSetting(), "dashboard").ServeHTTP(rw, req)
|
||||
}))
|
||||
}
|
||||
|
||||
func (u *Handler) IndexFileOnNotFound() http.Handler {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
path := joinEmbedFilepath(u.pathSetting(), req.URL.Path)
|
||||
if pathExist(path) {
|
||||
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) {
|
||||
serveEmbedIndex(u.pathSetting()).ServeHTTP(rw, req)
|
||||
}))
|
||||
}
|
||||
|
||||
func joinEmbedFilepath(paths ...string) string {
|
||||
return filepath.ToSlash(filepath.Join(paths...))
|
||||
}
|
||||
|
@ -1,42 +0,0 @@
|
||||
//go:build !embed
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
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"))
|
||||
}
|
||||
}))
|
||||
}
|
@ -1,43 +1,30 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"io"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/cnrancher/kube-explorer/internal/ui/content"
|
||||
"github.com/rancher/apiserver/pkg/middleware"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultPath = "./ui"
|
||||
)
|
||||
|
||||
var (
|
||||
insecureClient = &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
type StringSetting func() string
|
||||
type BoolSetting func() bool
|
||||
|
||||
func StaticSetting[T any](input T) func() T {
|
||||
return func() T {
|
||||
return input
|
||||
}
|
||||
}
|
||||
|
||||
type Handler struct {
|
||||
contentHandlers map[string]content.Handler
|
||||
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 {
|
||||
@ -45,7 +32,7 @@ type Options struct {
|
||||
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
|
||||
// Whether or not to run the UI offline, should return true/false/dynamic/embed
|
||||
Offline StringSetting
|
||||
// Whether or not is it release, if true UI will run offline if set to dynamic
|
||||
ReleaseSetting BoolSetting
|
||||
@ -57,10 +44,11 @@ func NewUIHandler(opts *Options) *Handler {
|
||||
}
|
||||
|
||||
h := &Handler{
|
||||
indexSetting: opts.Index,
|
||||
offlineSetting: opts.Offline,
|
||||
pathSetting: opts.Path,
|
||||
releaseSetting: opts.ReleaseSetting,
|
||||
contentHandlers: make(map[string]content.Handler),
|
||||
indexSetting: opts.Index,
|
||||
offlineSetting: opts.Offline,
|
||||
pathSetting: opts.Path,
|
||||
releaseSetting: opts.ReleaseSetting,
|
||||
middleware: middleware.Chain{
|
||||
middleware.Gzip,
|
||||
middleware.FrameOptions,
|
||||
@ -75,67 +63,76 @@ func NewUIHandler(opts *Options) *Handler {
|
||||
}
|
||||
|
||||
if h.indexSetting == nil {
|
||||
h.indexSetting = func() string {
|
||||
return "https://releases.rancher.com/dashboard/latest/index.html"
|
||||
}
|
||||
h.indexSetting = StaticSetting("")
|
||||
}
|
||||
|
||||
if h.offlineSetting == nil {
|
||||
h.offlineSetting = func() string {
|
||||
return "dynamic"
|
||||
}
|
||||
h.offlineSetting = StaticSetting("dynamic")
|
||||
}
|
||||
|
||||
if h.pathSetting == nil {
|
||||
h.pathSetting = func() string {
|
||||
return defaultPath
|
||||
}
|
||||
h.pathSetting = StaticSetting("")
|
||||
}
|
||||
|
||||
if h.releaseSetting == nil {
|
||||
h.releaseSetting = func() bool {
|
||||
return false
|
||||
}
|
||||
h.releaseSetting = StaticSetting(false)
|
||||
}
|
||||
|
||||
h.contentHandlers["embed"] = content.NewEmbedded(staticContent, "ui")
|
||||
h.contentHandlers["false"] = content.NewExternal(h.indexSetting)
|
||||
h.contentHandlers["true"] = content.NewFilepath(h.pathSetting)
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
func (u *Handler) path() (path string, isURL bool) {
|
||||
switch u.offlineSetting() {
|
||||
case "dynamic":
|
||||
if u.releaseSetting() {
|
||||
return u.pathSetting(), false
|
||||
func (h *Handler) content() content.Handler {
|
||||
offline := h.offlineSetting()
|
||||
if handler, ok := h.contentHandlers[offline]; ok {
|
||||
return handler
|
||||
}
|
||||
embedHandler := h.contentHandlers["embed"]
|
||||
filepathHandler := h.contentHandlers["true"]
|
||||
externalHandler := h.contentHandlers["false"]
|
||||
// default to dynamic
|
||||
switch {
|
||||
case h.pathSetting() != "":
|
||||
if _, err := filepathHandler.GetIndex(); err == nil {
|
||||
return filepathHandler
|
||||
}
|
||||
if u.canDownload(u.indexSetting()) {
|
||||
return u.indexSetting(), true
|
||||
}
|
||||
return u.pathSetting(), false
|
||||
case "true":
|
||||
return u.pathSetting(), false
|
||||
fallthrough
|
||||
case h.releaseSetting():
|
||||
// release must use embed first
|
||||
return embedHandler
|
||||
default:
|
||||
return u.indexSetting(), true
|
||||
}
|
||||
}
|
||||
|
||||
func (u *Handler) canDownload(url string) bool {
|
||||
u.downloadOnce.Do(func() {
|
||||
if err := serveIndex(io.Discard, url); err == nil {
|
||||
u.downloadSuccess = true
|
||||
} else {
|
||||
logrus.Errorf("Failed to download %s, falling back to packaged UI", url)
|
||||
// try embed
|
||||
if _, err := embedHandler.GetIndex(); err == nil {
|
||||
return embedHandler
|
||||
}
|
||||
})
|
||||
return u.downloadSuccess
|
||||
}
|
||||
|
||||
func serveIndex(resp io.Writer, url string) error {
|
||||
r, err := insecureClient.Get(url)
|
||||
if err != nil {
|
||||
return err
|
||||
return externalHandler
|
||||
}
|
||||
defer r.Body.Close()
|
||||
|
||||
_, err = io.Copy(resp, r.Body)
|
||||
return err
|
||||
}
|
||||
|
||||
func (h *Handler) ServeAssets(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
h.content().ServeAssets(h.middleware, next).ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func (h *Handler) ServeFaviconDashboard() http.Handler {
|
||||
return h.middleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
h.content().ServeFaviconDashboard().ServeHTTP(w, r)
|
||||
}))
|
||||
}
|
||||
|
||||
func (h *Handler) IndexFile() http.Handler {
|
||||
return h.indexMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
rtn, err := h.content().GetIndex()
|
||||
if err != nil {
|
||||
logrus.Warnf("failed to serve index with error %v", err)
|
||||
http.NotFoundHandler().ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write(rtn)
|
||||
}))
|
||||
}
|
||||
|
@ -4,29 +4,11 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/cnrancher/kube-explorer/internal/version"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
func New(path string) http.Handler {
|
||||
vue := NewUIHandler(&Options{
|
||||
Path: func() string {
|
||||
if path == "" {
|
||||
return defaultPath
|
||||
}
|
||||
return path
|
||||
},
|
||||
Offline: func() string {
|
||||
if path != "" {
|
||||
return "true"
|
||||
}
|
||||
return "dynamic"
|
||||
},
|
||||
ReleaseSetting: func() bool {
|
||||
return version.IsRelease()
|
||||
},
|
||||
})
|
||||
|
||||
func New(opt *Options) (http.Handler, APIUI) {
|
||||
vue := NewUIHandler(opt)
|
||||
router := mux.NewRouter()
|
||||
router.UseEncodedPath()
|
||||
|
||||
@ -35,7 +17,8 @@ func New(path string) http.Handler {
|
||||
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("/dashboard/").Handler(vue.ServeAssets(vue.IndexFile()))
|
||||
router.PathPrefix("/api-ui/").Handler(vue.ServeAssets(http.NotFoundHandler()))
|
||||
router.PathPrefix("/k8s/clusters/local").HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
url := strings.TrimPrefix(req.URL.Path, "/k8s/clusters/local")
|
||||
if url == "" {
|
||||
@ -44,5 +27,5 @@ func New(path string) http.Handler {
|
||||
http.Redirect(rw, req, url, http.StatusFound)
|
||||
})
|
||||
|
||||
return router
|
||||
return router, apiUI(opt)
|
||||
}
|
||||
|
15
main.go
15
main.go
@ -14,19 +14,14 @@ import (
|
||||
"github.com/cnrancher/kube-explorer/internal/server"
|
||||
)
|
||||
|
||||
var (
|
||||
config stevecli.Config
|
||||
debugconfig debug.Config
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := cli.NewApp()
|
||||
app.Name = "kube-explorer"
|
||||
app.Version = version.FriendlyVersion()
|
||||
app.Usage = ""
|
||||
app.Flags = joinFlags(
|
||||
stevecli.Flags(&config),
|
||||
debug.Flags(&debugconfig),
|
||||
stevecli.Flags(&keconfig.Steve),
|
||||
debug.Flags(&keconfig.Debug),
|
||||
keconfig.Flags(),
|
||||
)
|
||||
app.Action = run
|
||||
@ -38,12 +33,12 @@ func main() {
|
||||
|
||||
func run(_ *cli.Context) error {
|
||||
ctx := signals.SetupSignalContext()
|
||||
debugconfig.MustSetupDebug()
|
||||
s, err := server.ToServer(ctx, &config, false)
|
||||
keconfig.Debug.MustSetupDebug()
|
||||
s, err := server.ToServer(ctx, &keconfig.Steve, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.ListenAndServe(ctx, config.HTTPSListenPort, config.HTTPListenPort, nil)
|
||||
return s.ListenAndServe(ctx, keconfig.Steve.HTTPSListenPort, keconfig.Steve.HTTPListenPort, nil)
|
||||
}
|
||||
|
||||
func joinFlags(flags ...[]cli.Flag) []cli.Flag {
|
||||
|
@ -11,7 +11,8 @@ OS_ARCH_ARG_DARWIN="amd64 arm64"
|
||||
OS_ARCH_ARG_WINDOWS="amd64"
|
||||
|
||||
LD_INJECT_VALUES="-X github.com/cnrancher/kube-explorer/internal/version.Version=$VERSION
|
||||
-X github.com/cnrancher/kube-explorer/internal/version.GitCommit=$COMMIT"
|
||||
-X github.com/cnrancher/kube-explorer/internal/version.GitCommit=$COMMIT
|
||||
-X github.com/cnrancher/kube-explorer/internal/config.APIUIVersion=$CATTLE_API_UI_VERSION"
|
||||
|
||||
[ "$(uname)" != "Darwin" ] && LINKFLAGS="-extldflags -static -s"
|
||||
|
||||
|
@ -10,9 +10,13 @@ else
|
||||
TAR_CMD="tar"
|
||||
fi
|
||||
|
||||
rm -rf internal/ui/ui/*
|
||||
|
||||
mkdir -p internal/ui/ui/dashboard
|
||||
cd internal/ui/ui/dashboard || exit 1;
|
||||
curl -sL https://pandaria-dashboard-ui.s3.ap-southeast-2.amazonaws.com/release-2.8-cn/kube-explorer-ui/${CATTLE_DASHBOARD_UI_VERSION}.tar.gz | $TAR_CMD xvzf - --strip-components=2
|
||||
cp index.html ../index.html
|
||||
|
||||
|
||||
mkdir ../api-ui
|
||||
cd ../api-ui || exit 1;
|
||||
curl -sL https://releases.rancher.com/api-ui/${CATTLE_API_UI_VERSION}.tar.gz | $TAR_CMD xvzf - --strip-components=1
|
||||
|
Loading…
Reference in New Issue
Block a user