feat(strategy): add option to show instances details

Closes #77
This commit is contained in:
Alexis Couvreur
2022-11-10 15:07:33 +00:00
parent 77b2611328
commit 546b378416
15 changed files with 107 additions and 2 deletions

View File

@@ -11,11 +11,12 @@ testData:
names: whoami,nginx # Comma separated names of containers/services/deployments etc. names: whoami,nginx # Comma separated names of containers/services/deployments etc.
sessionDuration: 1m # The session duration after which containers/services/deployments instances are shutdown sessionDuration: 1m # The session duration after which containers/services/deployments instances are shutdown
# You can only use one strategy at a time # You can only use one strategy at a time
# To do so, only declare `dynamic` or `blockin` # To do so, only declare `dynamic` or `blocking`
# Dynamic strategy, provides the waiting webui # Dynamic strategy, provides the waiting webui
dynamic: dynamic:
displayName: My Title # (Optional) Defaults to the middleware name displayName: My Title # (Optional) Defaults to the middleware name
showDetails: true # (Optional) Set to true or false to show details specifcally for this middleware, unset to use Sablier server defaults
theme: hacker-terminal # (Optional) The theme to use theme: hacker-terminal # (Optional) The theme to use
refreshFrequency: 5s # (Optional) The loading page refresh frequency refreshFrequency: 5s # (Optional) The loading page refresh frequency

View File

@@ -25,6 +25,7 @@ type RenderOptionsInstanceState struct {
type RenderOptions struct { type RenderOptions struct {
DisplayName string DisplayName string
ShowDetails bool
InstanceStates []RenderOptionsInstanceState InstanceStates []RenderOptionsInstanceState
SessionDuration time.Duration SessionDuration time.Duration
RefreshFrequency time.Duration RefreshFrequency time.Duration
@@ -61,9 +62,15 @@ func Render(options RenderOptions, writer io.Writer) error {
return err return err
} }
instanceStates := []RenderOptionsInstanceState{}
if options.ShowDetails {
instanceStates = options.InstanceStates
}
return tpl.Execute(writer, TemplateValues{ return tpl.Execute(writer, TemplateValues{
DisplayName: options.DisplayName, DisplayName: options.DisplayName,
InstanceStates: options.InstanceStates, InstanceStates: instanceStates,
SessionDuration: humanizeDuration(options.SessionDuration), SessionDuration: humanizeDuration(options.SessionDuration),
RefreshFrequency: fmt.Sprintf("%d", int64(options.RefreshFrequency.Seconds())), RefreshFrequency: fmt.Sprintf("%d", int64(options.RefreshFrequency.Seconds())),
Version: options.Version, Version: options.Version,

View File

@@ -242,6 +242,38 @@ func TestRenderContent(t *testing.T) {
}, },
wantContent: "<meta http-equiv=\"refresh\" content=\"10\" />", wantContent: "<meta http-equiv=\"refresh\" content=\"10\" />",
}, },
{
name: "details is rendered",
args: args{
options: RenderOptions{
DisplayName: "Test",
ShowDetails: true,
InstanceStates: instanceStates,
Theme: "ghost",
SessionDuration: 10 * time.Minute,
RefreshFrequency: 10 * time.Second,
CustomThemes: nil,
Version: "v0.0.0",
},
},
wantContent: "started (4/4)",
},
{
name: "details is not rendered",
args: args{
options: RenderOptions{
DisplayName: "Test",
ShowDetails: false,
InstanceStates: instanceStates,
Theme: "ghost",
SessionDuration: 10 * time.Minute,
RefreshFrequency: 10 * time.Second,
CustomThemes: nil,
Version: "v0.0.0",
},
},
wantContent: "<table></table>",
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {

View File

@@ -6,6 +6,7 @@ import (
type DynamicRequest struct { type DynamicRequest struct {
Names []string `form:"names" binding:"required"` Names []string `form:"names" binding:"required"`
ShowDetails bool `form:"show_details"`
DisplayName string `form:"display_name"` DisplayName string `form:"display_name"`
Theme string `form:"theme"` Theme string `form:"theme"`
SessionDuration time.Duration `form:"session_duration" binding:"required"` SessionDuration time.Duration `form:"session_duration" binding:"required"`

View File

@@ -49,6 +49,7 @@ func NewServeStrategy(sessionsManager sessions.Manager, conf config.Strategy) *S
func (s *ServeStrategy) ServeDynamic(c *gin.Context) { func (s *ServeStrategy) ServeDynamic(c *gin.Context) {
request := models.DynamicRequest{ request := models.DynamicRequest{
Theme: s.StrategyConfig.Dynamic.DefaultTheme, Theme: s.StrategyConfig.Dynamic.DefaultTheme,
ShowDetails: s.StrategyConfig.Dynamic.ShowDetailsByDefault,
RefreshFrequency: s.StrategyConfig.Dynamic.DefaultRefreshFrequency, RefreshFrequency: s.StrategyConfig.Dynamic.DefaultRefreshFrequency,
} }
@@ -67,6 +68,7 @@ func (s *ServeStrategy) ServeDynamic(c *gin.Context) {
renderOptions := pages.RenderOptions{ renderOptions := pages.RenderOptions{
DisplayName: request.DisplayName, DisplayName: request.DisplayName,
ShowDetails: request.ShowDetails,
SessionDuration: request.SessionDuration, SessionDuration: request.SessionDuration,
Theme: request.Theme, Theme: request.Theme,
CustomThemes: s.customThemesFS, CustomThemes: s.customThemesFS,

View File

@@ -66,6 +66,8 @@ It provides an integrations with multiple reverse proxies and different loading
viper.BindPFlag("strategy.dynamic.custom-themes-path", startCmd.Flags().Lookup("strategy.dynamic.custom-themes-path")) viper.BindPFlag("strategy.dynamic.custom-themes-path", startCmd.Flags().Lookup("strategy.dynamic.custom-themes-path"))
startCmd.Flags().StringVar(&conf.Strategy.Dynamic.DefaultTheme, "strategy.dynamic.default-theme", "hacker-terminal", "Default theme used for dynamic strategy") startCmd.Flags().StringVar(&conf.Strategy.Dynamic.DefaultTheme, "strategy.dynamic.default-theme", "hacker-terminal", "Default theme used for dynamic strategy")
viper.BindPFlag("strategy.dynamic.default-theme", startCmd.Flags().Lookup("strategy.dynamic.default-theme")) viper.BindPFlag("strategy.dynamic.default-theme", startCmd.Flags().Lookup("strategy.dynamic.default-theme"))
startCmd.Flags().BoolVar(&conf.Strategy.Dynamic.ShowDetailsByDefault, "strategy.dynamic.show-details-by-default", true, "Show the loading instances details by default")
viper.BindPFlag("strategy.dynamic.show-details-by-default", startCmd.Flags().Lookup("strategy.dynamic.show-details-by-default"))
startCmd.Flags().DurationVar(&conf.Strategy.Dynamic.DefaultRefreshFrequency, "strategy.dynamic.default-refresh-frequency", 5*time.Second, "Default refresh frequency in the HTML page for dynamic strategy") startCmd.Flags().DurationVar(&conf.Strategy.Dynamic.DefaultRefreshFrequency, "strategy.dynamic.default-refresh-frequency", 5*time.Second, "Default refresh frequency in the HTML page for dynamic strategy")
viper.BindPFlag("strategy.dynamic.default-refresh-frequency", startCmd.Flags().Lookup("strategy.dynamic.default-refresh-frequency")) viper.BindPFlag("strategy.dynamic.default-refresh-frequency", startCmd.Flags().Lookup("strategy.dynamic.default-refresh-frequency"))
startCmd.Flags().DurationVar(&conf.Strategy.Blocking.DefaultTimeout, "strategy.blocking.default-timeout", 1*time.Minute, "Default timeout used for blocking strategy") startCmd.Flags().DurationVar(&conf.Strategy.Blocking.DefaultTimeout, "strategy.blocking.default-timeout", 1*time.Minute, "Default timeout used for blocking strategy")

View File

@@ -106,6 +106,8 @@ func TestPrecedence(t *testing.T) {
"--sessions.expiration-interval", "3h", "--sessions.expiration-interval", "3h",
"--logging.level", "info", "--logging.level", "info",
"--strategy.dynamic.custom-themes-path", "/tmp/cli/themes", "--strategy.dynamic.custom-themes-path", "/tmp/cli/themes",
// Must use `=` see https://github.com/spf13/cobra/issues/613
"--strategy.dynamic.show-details-by-default=false",
"--strategy.dynamic.default-theme", "cli", "--strategy.dynamic.default-theme", "cli",
"--strategy.dynamic.default-refresh-frequency", "3h", "--strategy.dynamic.default-refresh-frequency", "3h",
"--strategy.blocking.default-timeout", "3h", "--strategy.blocking.default-timeout", "3h",

View File

@@ -6,6 +6,7 @@ SESSIONS_DEFAULT_DURATION=2h
SESSIONS_EXPIRATION_INTERVAL=2h SESSIONS_EXPIRATION_INTERVAL=2h
LOGGING_LEVEL=debug LOGGING_LEVEL=debug
STRATEGY_DYNAMIC_CUSTOM_THEMES_PATH=/tmp/envvar/themes STRATEGY_DYNAMIC_CUSTOM_THEMES_PATH=/tmp/envvar/themes
STRATEGY_SHOW_DETAILS_BY_DEFAULT=false
STRATEGY_DYNAMIC_DEFAULT_THEME=envvar STRATEGY_DYNAMIC_DEFAULT_THEME=envvar
STRATEGY_DYNAMIC_DEFAULT_REFRESH_FREQUENCY=2h STRATEGY_DYNAMIC_DEFAULT_REFRESH_FREQUENCY=2h
STRATEGY_BLOCKING_DEFAULT_TIMEOUT=2h STRATEGY_BLOCKING_DEFAULT_TIMEOUT=2h

View File

@@ -13,6 +13,7 @@ logging:
strategy: strategy:
dynamic: dynamic:
custom-themes-path: /tmp/configfile/themes custom-themes-path: /tmp/configfile/themes
show-details-by-default: false
default-theme: configfile default-theme: configfile
default-refresh-frequency: 1h default-refresh-frequency: 1h
blocking: blocking:

View File

@@ -19,6 +19,7 @@
"Strategy": { "Strategy": {
"Dynamic": { "Dynamic": {
"CustomThemesPath": "/tmp/cli/themes", "CustomThemesPath": "/tmp/cli/themes",
"ShowDetailsByDefault": false,
"DefaultTheme": "cli", "DefaultTheme": "cli",
"DefaultRefreshFrequency": 10800000000000 "DefaultRefreshFrequency": 10800000000000
}, },

View File

@@ -19,6 +19,7 @@
"Strategy": { "Strategy": {
"Dynamic": { "Dynamic": {
"CustomThemesPath": "/tmp/envvar/themes", "CustomThemesPath": "/tmp/envvar/themes",
"ShowDetailsByDefault": false,
"DefaultTheme": "envvar", "DefaultTheme": "envvar",
"DefaultRefreshFrequency": 7200000000000 "DefaultRefreshFrequency": 7200000000000
}, },

View File

@@ -19,6 +19,7 @@
"Strategy": { "Strategy": {
"Dynamic": { "Dynamic": {
"CustomThemesPath": "/tmp/configfile/themes", "CustomThemesPath": "/tmp/configfile/themes",
"ShowDetailsByDefault": false,
"DefaultTheme": "configfile", "DefaultTheme": "configfile",
"DefaultRefreshFrequency": 3600000000000 "DefaultRefreshFrequency": 3600000000000
}, },

View File

@@ -4,6 +4,7 @@ import "time"
type DynamicStrategy struct { type DynamicStrategy struct {
CustomThemesPath string `mapstructure:"CUSTOM_THEMES_PATH" yaml:"customThemesPath"` CustomThemesPath string `mapstructure:"CUSTOM_THEMES_PATH" yaml:"customThemesPath"`
ShowDetailsByDefault bool `mapstructure:"SHOW_DETAILS_BY_DEFAULT" yaml:"showDetailsByDefault"`
DefaultTheme string `mapstructure:"DEFAULT_THEME" yaml:"defaultTheme" default:"hacker-terminal"` DefaultTheme string `mapstructure:"DEFAULT_THEME" yaml:"defaultTheme" default:"hacker-terminal"`
DefaultRefreshFrequency time.Duration `mapstructure:"DEFAULT_REFRESH_FREQUENCY" yaml:"defaultRefreshFrequency" default:"5s"` DefaultRefreshFrequency time.Duration `mapstructure:"DEFAULT_REFRESH_FREQUENCY" yaml:"defaultRefreshFrequency" default:"5s"`
} }
@@ -27,6 +28,7 @@ func NewStrategyConfig() Strategy {
func newDynamicStrategy() DynamicStrategy { func newDynamicStrategy() DynamicStrategy {
return DynamicStrategy{ return DynamicStrategy{
DefaultTheme: "hacker-terminal", DefaultTheme: "hacker-terminal",
ShowDetailsByDefault: true,
DefaultRefreshFrequency: 5 * time.Second, DefaultRefreshFrequency: 5 * time.Second,
} }
} }

View File

@@ -3,12 +3,14 @@ package traefik
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"strconv"
"strings" "strings"
"time" "time"
) )
type DynamicConfiguration struct { type DynamicConfiguration struct {
DisplayName string `yaml:"displayname"` DisplayName string `yaml:"displayname"`
ShowDetails *bool `yaml:"showDetails"`
Theme string `yaml:"theme"` Theme string `yaml:"theme"`
RefreshFrequency string `yaml:"refreshFrequency"` RefreshFrequency string `yaml:"refreshFrequency"`
} }
@@ -110,6 +112,10 @@ func (c *Config) buildDynamicRequest(middlewareName string) (*http.Request, erro
q.Add("refresh_frequency", c.Dynamic.RefreshFrequency) q.Add("refresh_frequency", c.Dynamic.RefreshFrequency)
} }
if c.Dynamic.ShowDetails != nil {
q.Add("show_details", strconv.FormatBool(*c.Dynamic.ShowDetails))
}
request.URL.RawQuery = q.Encode() request.URL.RawQuery = q.Encode()
return request, nil return request, nil

View File

@@ -9,6 +9,9 @@ import (
"github.com/acouvreur/sablier/plugins/traefik" "github.com/acouvreur/sablier/plugins/traefik"
) )
var fals bool = false
var tru bool = true
func TestConfig_BuildRequest(t *testing.T) { func TestConfig_BuildRequest(t *testing.T) {
type fields struct { type fields struct {
SablierURL string SablierURL string
@@ -98,6 +101,48 @@ func TestConfig_BuildRequest(t *testing.T) {
want: nil, want: nil,
wantErr: true, wantErr: true,
}, },
{
name: "dynamic session with show details to true",
fields: fields{
SablierURL: "http://sablier:10000",
Names: "nginx , apache",
SessionDuration: "1m",
Dynamic: &traefik.DynamicConfiguration{
ShowDetails: &tru,
RefreshFrequency: "1m",
},
},
want: createRequest("GET", "http://sablier:10000/api/strategies/dynamic?display_name=sablier-middleware&names=nginx&names=apache&refresh_frequency=1m&session_duration=1m&show_details=true", nil),
wantErr: false,
},
{
name: "dynamic session with show details to false",
fields: fields{
SablierURL: "http://sablier:10000",
Names: "nginx , apache",
SessionDuration: "1m",
Dynamic: &traefik.DynamicConfiguration{
ShowDetails: &fals,
RefreshFrequency: "1m",
},
},
want: createRequest("GET", "http://sablier:10000/api/strategies/dynamic?display_name=sablier-middleware&names=nginx&names=apache&refresh_frequency=1m&session_duration=1m&show_details=false", nil),
wantErr: false,
},
{
name: "dynamic session without show details set",
fields: fields{
SablierURL: "http://sablier:10000",
Names: "nginx , apache",
SessionDuration: "1m",
Dynamic: &traefik.DynamicConfiguration{
ShowDetails: nil,
RefreshFrequency: "1m",
},
},
want: createRequest("GET", "http://sablier:10000/api/strategies/dynamic?display_name=sablier-middleware&names=nginx&names=apache&refresh_frequency=1m&session_duration=1m", nil),
wantErr: false,
},
{ {
name: "blocking session with default values", name: "blocking session with default values",
fields: fields{ fields: fields{