Skip to content

Commit

Permalink
refactor: UI resource logic
Browse files Browse the repository at this point in the history
- Support embed api-ui resources
- The ui-path arg will be applied if provided. Also applied to api-ui resource files
  • Loading branch information
orangedeng committed Jul 23, 2024
1 parent 004e475 commit eacc474
Show file tree
Hide file tree
Showing 18 changed files with 447 additions and 223 deletions.
3 changes: 2 additions & 1 deletion Dockerfile.dapper
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions internal/config/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

var InsecureSkipTLSVerify bool
var SystemDefaultRegistry string
var APIUIVersion = "1.1.11"

var ShellPodImage string

Expand All @@ -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 changes: 11 additions & 0 deletions internal/config/steve.go
Original file line number Diff line number Diff line change
@@ -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
)
10 changes: 9 additions & 1 deletion internal/server/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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 {
Expand All @@ -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
Expand Down
55 changes: 55 additions & 0 deletions internal/ui/apiui.go
Original file line number Diff line number Diff line change
@@ -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 changes: 24 additions & 0 deletions internal/ui/content/content.go
Original file line number Diff line number Diff line change
@@ -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 changes: 97 additions & 0 deletions internal/ui/content/external.go
Original file line number Diff line number Diff line change
@@ -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 changes: 71 additions & 0 deletions internal/ui/content/fs.go
Original file line number Diff line number Diff line change
@@ -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 changes: 43 additions & 0 deletions internal/ui/content/fs_embed.go
Original file line number Diff line number Diff line change
@@ -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...))
}
Loading

0 comments on commit eacc474

Please sign in to comment.