mirror of
https://github.com/grafana/grafana.git
synced 2025-07-28 19:12:19 +08:00

* do it all * feat(plugins): move loadingStrategy to ds pluginMeta and add to plugin settings endpoint * support child plugins and update tests * use relative path for nested plugins * feat(plugins): support nested plugins in the plugin loader cache by extracting pluginId from path * feat(grafana-data): add plugin loading strategy to plugin meta and export * feat(plugins): pass down loadingStrategy to fe plugin loader * refactor(plugins): make PluginLoadingStrategy an enum * feat(plugins): add the loading strategy to the fe plugin loader cache * feat(plugins): load fe plugin js assets as script tags based on be loadingStrategy * add more tests * feat(plugins): add loading strategy to plugin preloader * feat(plugins): make loadingStrategy a maybe and provide fetch fallback * test(alerting): update config.apps mocks to include loadingStrategy * fix format --------- Co-authored-by: Jack Westbrook <jack.westbrook@gmail.com>
156 lines
4.6 KiB
Go
156 lines
4.6 KiB
Go
package assetpath
|
|
|
|
import (
|
|
"fmt"
|
|
"net/url"
|
|
"path"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/grafana/grafana/pkg/plugins"
|
|
"github.com/grafana/grafana/pkg/plugins/config"
|
|
"github.com/grafana/grafana/pkg/plugins/pluginscdn"
|
|
)
|
|
|
|
// Service provides methods for constructing asset paths for plugins.
|
|
// It supports core plugins, external plugins stored on the local filesystem, and external plugins stored
|
|
// on the plugins CDN, and it will switch to the correct implementation depending on the plugin and the config.
|
|
type Service struct {
|
|
cdn *pluginscdn.Service
|
|
cfg *config.PluginManagementCfg
|
|
}
|
|
|
|
func ProvideService(cfg *config.PluginManagementCfg, cdn *pluginscdn.Service) *Service {
|
|
return &Service{cfg: cfg, cdn: cdn}
|
|
}
|
|
|
|
type PluginInfo struct {
|
|
pluginJSON plugins.JSONData
|
|
class plugins.Class
|
|
fs plugins.FS
|
|
parent *PluginInfo
|
|
}
|
|
|
|
func NewPluginInfo(pluginJSON plugins.JSONData, class plugins.Class, fs plugins.FS, parent *PluginInfo) PluginInfo {
|
|
return PluginInfo{
|
|
pluginJSON: pluginJSON,
|
|
class: class,
|
|
fs: fs,
|
|
parent: parent,
|
|
}
|
|
}
|
|
|
|
func DefaultService(cfg *config.PluginManagementCfg) *Service {
|
|
return &Service{cfg: cfg, cdn: pluginscdn.ProvideService(cfg)}
|
|
}
|
|
|
|
// Base returns the base path for the specified plugin.
|
|
func (s *Service) Base(n PluginInfo) (string, error) {
|
|
if n.class == plugins.ClassCore {
|
|
baseDir := getBaseDir(n.fs.Base())
|
|
return path.Join("public/app/plugins", string(n.pluginJSON.Type), baseDir), nil
|
|
}
|
|
if n.class == plugins.ClassCDN {
|
|
return n.fs.Base(), nil
|
|
}
|
|
if s.cdn.PluginSupported(n.pluginJSON.ID) {
|
|
return s.cdn.AssetURL(n.pluginJSON.ID, n.pluginJSON.Info.Version, "")
|
|
}
|
|
if n.parent != nil {
|
|
relPath, err := n.parent.fs.Rel(n.fs.Base())
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if s.cdn.PluginSupported(n.parent.pluginJSON.ID) {
|
|
return s.cdn.AssetURL(n.parent.pluginJSON.ID, n.parent.pluginJSON.Info.Version, relPath)
|
|
}
|
|
return path.Join("public/plugins", n.parent.pluginJSON.ID, relPath), nil
|
|
}
|
|
|
|
return path.Join("public/plugins", n.pluginJSON.ID), nil
|
|
}
|
|
|
|
// Module returns the module.js path for the specified plugin.
|
|
func (s *Service) Module(n PluginInfo) (string, error) {
|
|
if n.class == plugins.ClassCore {
|
|
if filepath.Base(n.fs.Base()) == "dist" {
|
|
// The core plugin has been built externally, use the module from the dist folder
|
|
} else {
|
|
baseDir := getBaseDir(n.fs.Base())
|
|
return path.Join("core:plugin", baseDir), nil
|
|
}
|
|
}
|
|
if n.class == plugins.ClassCDN {
|
|
return pluginscdn.JoinPath(n.fs.Base(), "module.js")
|
|
}
|
|
|
|
if s.cdn.PluginSupported(n.pluginJSON.ID) {
|
|
return s.cdn.AssetURL(n.pluginJSON.ID, n.pluginJSON.Info.Version, "module.js")
|
|
}
|
|
if n.parent != nil {
|
|
relPath, err := n.parent.fs.Rel(n.fs.Base())
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if s.cdn.PluginSupported(n.parent.pluginJSON.ID) {
|
|
return s.cdn.AssetURL(n.parent.pluginJSON.ID, n.parent.pluginJSON.Info.Version, path.Join(relPath, "module.js"))
|
|
}
|
|
return path.Join("public/plugins", n.parent.pluginJSON.ID, relPath, "module.js"), nil
|
|
}
|
|
|
|
return path.Join("public/plugins", n.pluginJSON.ID, "module.js"), nil
|
|
}
|
|
|
|
// RelativeURL returns the relative URL for an arbitrary plugin asset.
|
|
func (s *Service) RelativeURL(n PluginInfo, pathStr string) (string, error) {
|
|
if n.class == plugins.ClassCDN {
|
|
return pluginscdn.JoinPath(n.fs.Base(), pathStr)
|
|
}
|
|
|
|
if s.cdn.PluginSupported(n.pluginJSON.ID) {
|
|
return s.cdn.NewCDNURLConstructor(n.pluginJSON.ID, n.pluginJSON.Info.Version).StringPath(pathStr)
|
|
}
|
|
if n.parent != nil {
|
|
if s.cdn.PluginSupported(n.parent.pluginJSON.ID) {
|
|
relPath, err := n.parent.fs.Rel(n.fs.Base())
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return s.cdn.AssetURL(n.parent.pluginJSON.ID, n.parent.pluginJSON.Info.Version, path.Join(relPath, pathStr))
|
|
}
|
|
}
|
|
// Local
|
|
u, err := url.Parse(pathStr)
|
|
if err != nil {
|
|
return "", fmt.Errorf("url parse: %w", err)
|
|
}
|
|
if u.IsAbs() {
|
|
return pathStr, nil
|
|
}
|
|
|
|
baseURL, err := s.Base(n)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
// has already been prefixed with base path
|
|
if strings.HasPrefix(pathStr, baseURL) {
|
|
return pathStr, nil
|
|
}
|
|
return path.Join(baseURL, pathStr), nil
|
|
}
|
|
|
|
// DefaultLogoPath returns the default logo path for the specified plugin type.
|
|
func (s *Service) DefaultLogoPath(pluginType plugins.Type) string {
|
|
return path.Join("public/img", fmt.Sprintf("icn-%s.svg", string(pluginType)))
|
|
}
|
|
|
|
func getBaseDir(pluginDir string) string {
|
|
baseDir := filepath.Base(pluginDir)
|
|
// Decoupled core plugins will be suffixed with "dist" if they have been built
|
|
if baseDir == "dist" {
|
|
return filepath.Base(strings.TrimSuffix(pluginDir, baseDir))
|
|
}
|
|
return baseDir
|
|
}
|