Skip to content

Commit

Permalink
CONSOLE-2425: Support localization of dynamic plugins
Browse files Browse the repository at this point in the history
  • Loading branch information
jhadvig committed Jul 16, 2021
1 parent b5fb173 commit 8c40162
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 26 deletions.
2 changes: 1 addition & 1 deletion frontend/public/i18n.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ i18n
// for all options read: https://www.i18next.com/overview/configuration-options
.init({
backend: {
loadPath: 'static/locales/{{lng}}/{{ns}}.json',
loadPath: '/locales/resource.json?lng={{lng}}&ns={{ns}}',
},
lng: localStorage.getItem('bridge/language'),
fallbackLng: 'en',
Expand Down
52 changes: 47 additions & 5 deletions pkg/plugins/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,32 +17,74 @@ import (
type PluginsHandler struct {
Client *http.Client
PluginsEndpointMap map[string]string
PublicDir string
}

func NewPluginsHandler(client *http.Client, token string, pluginsEndpointMap map[string]string) *PluginsHandler {
func NewPluginsHandler(client *http.Client, pluginsEndpointMap map[string]string, publicDir string) *PluginsHandler {
return &PluginsHandler{
Client: client,
PluginsEndpointMap: pluginsEndpointMap,
PublicDir: publicDir,
}
}

func (p *PluginsHandler) HandlePlugins(w http.ResponseWriter, r *http.Request) {
func (p *PluginsHandler) HandleLocales(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" {
w.Header().Set("Allow", "GET")
serverutils.SendResponse(w, http.StatusMethodNotAllowed, serverutils.ApiError{Err: "Method unsupported, the only supported methods is GET"})
return
}

query := r.URL.Query()
lang := query.Get("lng")
// In case of console app or static plugins, the localization namespace should contain name of the json file that contains the localized strings,
// since various console packages have different names for the localization file.
// eg. namespace query for the local-storage-operator-plugin locales - '&ns=lso-plugin.json'
// In case of the dynamic plugin the localization namespace should contain name of the plugin prefixed with 'plugin__' prefix.
// No file extension is needed since for the dynamic plugins we will fetch file from the plugin pod, based on the namespace.
// eg. 'plugin__helm' will fetch `/locales/{lang}/plugin__helm.json`
namespace := query.Get("ns")
if !strings.HasPrefix(namespace, "plugin__") {
http.ServeFile(w, r, path.Join(p.PublicDir, "locales", lang, namespace))
return
}
// In case of dynamic-plugin we need to trim the "plugin__" prefix, since we are using the ConsolePlugin CR's name
// as key when looking for the plugin's Service endpoint.
pluginName := strings.TrimPrefix(namespace, "plugin__")

pluginServiceRequestURL, err := p.getServiceRequestURL(pluginName)
if err != nil {
errMsg := err.Error()
klog.Error(errMsg)
serverutils.SendResponse(w, http.StatusBadGateway, serverutils.ApiError{Err: errMsg})
return
}
pluginServiceRequestURL.Path = path.Join(pluginServiceRequestURL.Path, "locales", lang, fmt.Sprintf("%s.json", namespace))

p.proxyPluginRequest(pluginServiceRequestURL, pluginName, w, r)
}

func (p *PluginsHandler) HandlePluginAssets(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" {
w.Header().Set("Allow", "GET")
serverutils.SendResponse(w, http.StatusMethodNotAllowed, serverutils.ApiError{Err: "Method unsupported, the only supported methods is GET"})
return
}
pluginName, pluginAssetPath := parsePluginNameAndAssetPath(r.URL.Path)
serviceRequestURL, err := p.getServiceRequestURL(pluginName)
pluginServiceRequestURL, err := p.getServiceRequestURL(pluginName)
if err != nil {
errMsg := err.Error()
klog.Error(errMsg)
serverutils.SendResponse(w, http.StatusBadGateway, serverutils.ApiError{Err: errMsg})
return
}
serviceRequestURL.Path = path.Join(serviceRequestURL.Path, pluginAssetPath)
pluginServiceRequestURL.Path = path.Join(pluginServiceRequestURL.Path, pluginAssetPath)

p.proxyPluginRequest(pluginServiceRequestURL, pluginName, w, r)
}

resp, err := p.Client.Get(serviceRequestURL.String())
func (p *PluginsHandler) proxyPluginRequest(requestURL *url.URL, pluginName string, w http.ResponseWriter, r *http.Request) {
resp, err := p.Client.Get(requestURL.String())
if err != nil {
errMsg := fmt.Sprintf("GET request for %q plugin failed: %v", pluginName, err)
klog.Error(errMsg)
Expand Down
42 changes: 22 additions & 20 deletions pkg/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ const (
gitopsEndpoint = "/api/gitops/"
devfileEndpoint = "/api/devfile/"
devfileSamplesEndpoint = "/api/devfile/samples/"
pluginsEndpoint = "/api/plugins/"
pluginAssetsEndpoint = "/api/plugins/"
localesEndpoint = "/locales/resource.json"

sha256Prefix = "sha256~"
)
Expand Down Expand Up @@ -422,26 +423,27 @@ func (s *Server) HTTPHandler() http.Handler {

helmHandlers := helmhandlerspkg.New(s.K8sProxyConfig.Endpoint.String(), s.K8sClient.Transport, s)

// No need to create plugins handler if no plugin is enabled.
if len(s.EnabledConsolePlugins) > 0 {
pluginsHandler := plugins.NewPluginsHandler(
&http.Client{
// 120 seconds matches the webpack require timeout.
// Plugins are loaded asynchronously, so this doesn't block page load.
Timeout: 120 * time.Second,
Transport: &http.Transport{TLSClientConfig: s.PluginsProxyTLSConfig},
},
s.ServiceAccountToken,
s.EnabledConsolePlugins,
)
pluginsHandler := plugins.NewPluginsHandler(
&http.Client{
// 120 seconds matches the webpack require timeout.
// Plugins are loaded asynchronously, so this doesn't block page load.
Timeout: 120 * time.Second,
Transport: &http.Transport{TLSClientConfig: s.PluginsProxyTLSConfig},
},
s.EnabledConsolePlugins,
s.PublicDir,
)

handle(pluginsEndpoint, http.StripPrefix(
proxy.SingleJoiningSlash(s.BaseURL.Path, pluginsEndpoint),
authHandler(func(w http.ResponseWriter, r *http.Request) {
pluginsHandler.HandlePlugins(w, r)
}),
))
}
handle(pluginAssetsEndpoint, http.StripPrefix(
proxy.SingleJoiningSlash(s.BaseURL.Path, pluginAssetsEndpoint),
authHandler(func(w http.ResponseWriter, r *http.Request) {
pluginsHandler.HandlePluginAssets(w, r)
}),
))

handleFunc(localesEndpoint, func(w http.ResponseWriter, r *http.Request) {
pluginsHandler.HandleLocales(w, r)
})

// Helm Endpoints
handle("/api/helm/template", authHandlerWithUser(helmHandlers.HandleHelmRenderManifests))
Expand Down

0 comments on commit 8c40162

Please sign in to comment.