Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4cc3f6bab4 | ||
|
|
6330cc885c | ||
|
|
cd3381ee59 | ||
|
|
7b7234c79a | ||
|
|
a965f5d538 | ||
|
|
b31176da7a | ||
|
|
aacf4eb880 | ||
|
|
b19e9906fc | ||
|
|
576456efb2 |
@@ -919,7 +919,7 @@
|
||||
],
|
||||
"version": {
|
||||
"DB_UPDATING": "false",
|
||||
"DB_VERSION": "60",
|
||||
"DB_VERSION": "61",
|
||||
"INSTANCE_ID": "null"
|
||||
}
|
||||
}
|
||||
@@ -85,6 +85,27 @@ func (manager *ComposeStackManager) Down(ctx context.Context, stack *portainer.S
|
||||
return errors.Wrap(err, "failed to remove a stack")
|
||||
}
|
||||
|
||||
// Pull an image associated with a service defined in a docker-compose.yml or docker-stack.yml file,
|
||||
// but does not start containers based on those images.
|
||||
func (manager *ComposeStackManager) Pull(ctx context.Context, stack *portainer.Stack, endpoint *portainer.Endpoint) error {
|
||||
url, proxy, err := manager.fetchEndpointProxy(endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if proxy != nil {
|
||||
defer proxy.Close()
|
||||
}
|
||||
|
||||
envFile, err := createEnvFile(stack)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create env file")
|
||||
}
|
||||
|
||||
filePaths := stackutils.GetStackFilePaths(stack)
|
||||
err = manager.deployer.Pull(ctx, stack.ProjectPath, url, stack.Name, filePaths, envFile)
|
||||
return errors.Wrap(err, "failed to pull images of the stack")
|
||||
}
|
||||
|
||||
// NormalizeStackName returns a new stack name with unsupported characters replaced
|
||||
func (manager *ComposeStackManager) NormalizeStackName(name string) string {
|
||||
return stackNameNormalizeRegex.ReplaceAllString(strings.ToLower(name), "")
|
||||
|
||||
@@ -89,7 +89,7 @@ func (manager *SwarmStackManager) Logout(endpoint *portainer.Endpoint) error {
|
||||
}
|
||||
|
||||
// Deploy executes the docker stack deploy command.
|
||||
func (manager *SwarmStackManager) Deploy(stack *portainer.Stack, prune bool, endpoint *portainer.Endpoint) error {
|
||||
func (manager *SwarmStackManager) Deploy(stack *portainer.Stack, prune bool, pullImage bool, endpoint *portainer.Endpoint) error {
|
||||
filePaths := stackutils.GetStackFilePaths(stack)
|
||||
command, args, err := manager.prepareDockerCommandAndArgs(manager.binaryPath, manager.configPath, endpoint)
|
||||
if err != nil {
|
||||
@@ -101,6 +101,9 @@ func (manager *SwarmStackManager) Deploy(stack *portainer.Stack, prune bool, end
|
||||
} else {
|
||||
args = append(args, "stack", "deploy", "--with-registry-auth")
|
||||
}
|
||||
if !pullImage {
|
||||
args = append(args, "--resolve-image=never")
|
||||
}
|
||||
|
||||
args = configureFilePaths(args, filePaths)
|
||||
args = append(args, stack.Name)
|
||||
|
||||
@@ -82,7 +82,7 @@ type Handler struct {
|
||||
}
|
||||
|
||||
// @title PortainerCE API
|
||||
// @version 2.15.0
|
||||
// @version 2.15.1
|
||||
// @description.markdown api-description.md
|
||||
// @termsOfService
|
||||
|
||||
|
||||
@@ -124,7 +124,7 @@ func (handler *Handler) createComposeStackFromFileContent(w http.ResponseWriter,
|
||||
doCleanUp := true
|
||||
defer handler.cleanUp(stack, &doCleanUp)
|
||||
|
||||
config, configErr := handler.createComposeDeployConfig(r, stack, endpoint)
|
||||
config, configErr := handler.createComposeDeployConfig(r, stack, endpoint, false)
|
||||
if configErr != nil {
|
||||
return configErr
|
||||
}
|
||||
@@ -275,7 +275,7 @@ func (handler *Handler) createComposeStackFromGitRepository(w http.ResponseWrite
|
||||
}
|
||||
stack.GitConfig.ConfigHash = commitID
|
||||
|
||||
config, configErr := handler.createComposeDeployConfig(r, stack, endpoint)
|
||||
config, configErr := handler.createComposeDeployConfig(r, stack, endpoint, false)
|
||||
if configErr != nil {
|
||||
return configErr
|
||||
}
|
||||
@@ -386,7 +386,7 @@ func (handler *Handler) createComposeStackFromFileUpload(w http.ResponseWriter,
|
||||
doCleanUp := true
|
||||
defer handler.cleanUp(stack, &doCleanUp)
|
||||
|
||||
config, configErr := handler.createComposeDeployConfig(r, stack, endpoint)
|
||||
config, configErr := handler.createComposeDeployConfig(r, stack, endpoint, false)
|
||||
if configErr != nil {
|
||||
return configErr
|
||||
}
|
||||
@@ -408,14 +408,15 @@ func (handler *Handler) createComposeStackFromFileUpload(w http.ResponseWriter,
|
||||
}
|
||||
|
||||
type composeStackDeploymentConfig struct {
|
||||
stack *portainer.Stack
|
||||
endpoint *portainer.Endpoint
|
||||
registries []portainer.Registry
|
||||
isAdmin bool
|
||||
user *portainer.User
|
||||
stack *portainer.Stack
|
||||
endpoint *portainer.Endpoint
|
||||
registries []portainer.Registry
|
||||
isAdmin bool
|
||||
user *portainer.User
|
||||
forcePullImage bool
|
||||
}
|
||||
|
||||
func (handler *Handler) createComposeDeployConfig(r *http.Request, stack *portainer.Stack, endpoint *portainer.Endpoint) (*composeStackDeploymentConfig, *httperror.HandlerError) {
|
||||
func (handler *Handler) createComposeDeployConfig(r *http.Request, stack *portainer.Stack, endpoint *portainer.Endpoint, forcePullImage bool) (*composeStackDeploymentConfig, *httperror.HandlerError) {
|
||||
securityContext, err := security.RetrieveRestrictedRequestContext(r)
|
||||
if err != nil {
|
||||
return nil, &httperror.HandlerError{StatusCode: http.StatusInternalServerError, Message: "Unable to retrieve info from request context", Err: err}
|
||||
@@ -433,11 +434,12 @@ func (handler *Handler) createComposeDeployConfig(r *http.Request, stack *portai
|
||||
filteredRegistries := security.FilterRegistries(registries, user, securityContext.UserMemberships, endpoint.ID)
|
||||
|
||||
config := &composeStackDeploymentConfig{
|
||||
stack: stack,
|
||||
endpoint: endpoint,
|
||||
registries: filteredRegistries,
|
||||
isAdmin: securityContext.IsAdmin,
|
||||
user: user,
|
||||
stack: stack,
|
||||
endpoint: endpoint,
|
||||
registries: filteredRegistries,
|
||||
isAdmin: securityContext.IsAdmin,
|
||||
user: user,
|
||||
forcePullImage: forcePullImage,
|
||||
}
|
||||
|
||||
return config, nil
|
||||
@@ -477,5 +479,5 @@ func (handler *Handler) deployComposeStack(config *composeStackDeploymentConfig,
|
||||
}
|
||||
}
|
||||
|
||||
return handler.StackDeployer.DeployComposeStack(config.stack, config.endpoint, config.registries, forceCreate)
|
||||
return handler.StackDeployer.DeployComposeStack(config.stack, config.endpoint, config.registries, config.forcePullImage, forceCreate)
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ func (handler *Handler) createSwarmStackFromFileContent(w http.ResponseWriter, r
|
||||
doCleanUp := true
|
||||
defer handler.cleanUp(stack, &doCleanUp)
|
||||
|
||||
config, configErr := handler.createSwarmDeployConfig(r, stack, endpoint, false)
|
||||
config, configErr := handler.createSwarmDeployConfig(r, stack, endpoint, false, true)
|
||||
if configErr != nil {
|
||||
return configErr
|
||||
}
|
||||
@@ -226,7 +226,7 @@ func (handler *Handler) createSwarmStackFromGitRepository(w http.ResponseWriter,
|
||||
}
|
||||
stack.GitConfig.ConfigHash = commitID
|
||||
|
||||
config, configErr := handler.createSwarmDeployConfig(r, stack, endpoint, false)
|
||||
config, configErr := handler.createSwarmDeployConfig(r, stack, endpoint, false, true)
|
||||
if configErr != nil {
|
||||
return configErr
|
||||
}
|
||||
@@ -332,7 +332,7 @@ func (handler *Handler) createSwarmStackFromFileUpload(w http.ResponseWriter, r
|
||||
doCleanUp := true
|
||||
defer handler.cleanUp(stack, &doCleanUp)
|
||||
|
||||
config, configErr := handler.createSwarmDeployConfig(r, stack, endpoint, false)
|
||||
config, configErr := handler.createSwarmDeployConfig(r, stack, endpoint, false, true)
|
||||
if configErr != nil {
|
||||
return configErr
|
||||
}
|
||||
@@ -360,9 +360,10 @@ type swarmStackDeploymentConfig struct {
|
||||
prune bool
|
||||
isAdmin bool
|
||||
user *portainer.User
|
||||
pullImage bool
|
||||
}
|
||||
|
||||
func (handler *Handler) createSwarmDeployConfig(r *http.Request, stack *portainer.Stack, endpoint *portainer.Endpoint, prune bool) (*swarmStackDeploymentConfig, *httperror.HandlerError) {
|
||||
func (handler *Handler) createSwarmDeployConfig(r *http.Request, stack *portainer.Stack, endpoint *portainer.Endpoint, prune bool, pullImage bool) (*swarmStackDeploymentConfig, *httperror.HandlerError) {
|
||||
securityContext, err := security.RetrieveRestrictedRequestContext(r)
|
||||
if err != nil {
|
||||
return nil, &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve info from request context", err}
|
||||
@@ -386,6 +387,7 @@ func (handler *Handler) createSwarmDeployConfig(r *http.Request, stack *portaine
|
||||
prune: prune,
|
||||
isAdmin: securityContext.IsAdmin,
|
||||
user: user,
|
||||
pullImage: pullImage,
|
||||
}
|
||||
|
||||
return config, nil
|
||||
@@ -413,5 +415,5 @@ func (handler *Handler) deploySwarmStack(config *swarmStackDeploymentConfig) err
|
||||
}
|
||||
}
|
||||
|
||||
return handler.StackDeployer.DeploySwarmStack(config.stack, config.endpoint, config.registries, config.prune)
|
||||
return handler.StackDeployer.DeploySwarmStack(config.stack, config.endpoint, config.registries, config.prune, config.pullImage)
|
||||
}
|
||||
|
||||
@@ -189,7 +189,7 @@ func (handler *Handler) migrateStack(r *http.Request, stack *portainer.Stack, ne
|
||||
}
|
||||
|
||||
func (handler *Handler) migrateComposeStack(r *http.Request, stack *portainer.Stack, next *portainer.Endpoint) *httperror.HandlerError {
|
||||
config, configErr := handler.createComposeDeployConfig(r, stack, next)
|
||||
config, configErr := handler.createComposeDeployConfig(r, stack, next, false)
|
||||
if configErr != nil {
|
||||
return configErr
|
||||
}
|
||||
@@ -203,7 +203,7 @@ func (handler *Handler) migrateComposeStack(r *http.Request, stack *portainer.St
|
||||
}
|
||||
|
||||
func (handler *Handler) migrateSwarmStack(r *http.Request, stack *portainer.Stack, next *portainer.Endpoint) *httperror.HandlerError {
|
||||
config, configErr := handler.createSwarmDeployConfig(r, stack, next, true)
|
||||
config, configErr := handler.createSwarmDeployConfig(r, stack, next, true, true)
|
||||
if configErr != nil {
|
||||
return configErr
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@ func (handler *Handler) startStack(stack *portainer.Stack, endpoint *portainer.E
|
||||
case portainer.DockerComposeStack:
|
||||
return handler.ComposeStackManager.Up(context.TODO(), stack, endpoint, false)
|
||||
case portainer.DockerSwarmStack:
|
||||
return handler.SwarmStackManager.Deploy(stack, true, endpoint)
|
||||
return handler.SwarmStackManager.Deploy(stack, true, true, endpoint)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ type updateComposeStackPayload struct {
|
||||
StackFileContent string `example:"version: 3\n services:\n web:\n image:nginx"`
|
||||
// A list of environment(endpoint) variables used during stack deployment
|
||||
Env []portainer.Pair
|
||||
// Force a pulling to current image with the original tag though the image is already the latest
|
||||
PullImage bool `example:"false"`
|
||||
}
|
||||
|
||||
func (payload *updateComposeStackPayload) Validate(r *http.Request) error {
|
||||
@@ -38,6 +40,8 @@ type updateSwarmStackPayload struct {
|
||||
Env []portainer.Pair
|
||||
// Prune services that are no longer referenced (only available for Swarm stacks)
|
||||
Prune bool `example:"true"`
|
||||
// Force a pulling to current image with the original tag though the image is already the latest
|
||||
PullImage bool `example:"false"`
|
||||
}
|
||||
|
||||
func (payload *updateSwarmStackPayload) Validate(r *http.Request) error {
|
||||
@@ -194,7 +198,7 @@ func (handler *Handler) updateComposeStack(r *http.Request, stack *portainer.Sta
|
||||
return &httperror.HandlerError{StatusCode: http.StatusInternalServerError, Message: "Unable to persist updated Compose file on disk", Err: err}
|
||||
}
|
||||
|
||||
config, configErr := handler.createComposeDeployConfig(r, stack, endpoint)
|
||||
config, configErr := handler.createComposeDeployConfig(r, stack, endpoint, payload.PullImage)
|
||||
if configErr != nil {
|
||||
return configErr
|
||||
}
|
||||
@@ -231,7 +235,7 @@ func (handler *Handler) updateSwarmStack(r *http.Request, stack *portainer.Stack
|
||||
return &httperror.HandlerError{StatusCode: http.StatusInternalServerError, Message: "Unable to persist updated Compose file on disk", Err: err}
|
||||
}
|
||||
|
||||
config, configErr := handler.createSwarmDeployConfig(r, stack, endpoint, payload.Prune)
|
||||
config, configErr := handler.createSwarmDeployConfig(r, stack, endpoint, payload.Prune, payload.PullImage)
|
||||
if configErr != nil {
|
||||
return configErr
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@ type stackGitRedployPayload struct {
|
||||
RepositoryPassword string
|
||||
Env []portainer.Pair
|
||||
Prune bool
|
||||
// Force a pulling to current image with the original tag though the image is already the latest
|
||||
PullImage bool `example:"false"`
|
||||
}
|
||||
|
||||
func (payload *stackGitRedployPayload) Validate(r *http.Request) error {
|
||||
@@ -167,7 +169,7 @@ func (handler *Handler) stackGitRedeploy(w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
}()
|
||||
|
||||
httpErr := handler.deployStack(r, stack, endpoint)
|
||||
httpErr := handler.deployStack(r, stack, payload.PullImage, endpoint)
|
||||
if httpErr != nil {
|
||||
return httpErr
|
||||
}
|
||||
@@ -199,14 +201,14 @@ func (handler *Handler) stackGitRedeploy(w http.ResponseWriter, r *http.Request)
|
||||
return response.JSON(w, stack)
|
||||
}
|
||||
|
||||
func (handler *Handler) deployStack(r *http.Request, stack *portainer.Stack, endpoint *portainer.Endpoint) *httperror.HandlerError {
|
||||
func (handler *Handler) deployStack(r *http.Request, stack *portainer.Stack, pullImage bool, endpoint *portainer.Endpoint) *httperror.HandlerError {
|
||||
switch stack.Type {
|
||||
case portainer.DockerSwarmStack:
|
||||
prune := false
|
||||
if stack.Option != nil {
|
||||
prune = stack.Option.Prune
|
||||
}
|
||||
config, httpErr := handler.createSwarmDeployConfig(r, stack, endpoint, prune)
|
||||
config, httpErr := handler.createSwarmDeployConfig(r, stack, endpoint, prune, pullImage)
|
||||
if httpErr != nil {
|
||||
return httpErr
|
||||
}
|
||||
@@ -216,7 +218,7 @@ func (handler *Handler) deployStack(r *http.Request, stack *portainer.Stack, end
|
||||
}
|
||||
|
||||
case portainer.DockerComposeStack:
|
||||
config, httpErr := handler.createComposeDeployConfig(r, stack, endpoint)
|
||||
config, httpErr := handler.createComposeDeployConfig(r, stack, endpoint, pullImage)
|
||||
if httpErr != nil {
|
||||
return httpErr
|
||||
}
|
||||
|
||||
@@ -11,7 +11,12 @@ import (
|
||||
// to prevent an error when url has port but no protocol prefix
|
||||
// we add `//` prefix if needed
|
||||
func ParseURL(endpointURL string) (*url.URL, error) {
|
||||
if !strings.HasPrefix(endpointURL, "http") && !strings.HasPrefix(endpointURL, "tcp") && !strings.HasPrefix(endpointURL, "//") {
|
||||
|
||||
if !strings.HasPrefix(endpointURL, "http") &&
|
||||
!strings.HasPrefix(endpointURL, "tcp") &&
|
||||
!strings.HasPrefix(endpointURL, "//") &&
|
||||
!strings.HasPrefix(endpointURL, `unix:`) &&
|
||||
!strings.HasPrefix(endpointURL, `npipe:`) {
|
||||
endpointURL = fmt.Sprintf("//%s", endpointURL)
|
||||
}
|
||||
|
||||
|
||||
@@ -1243,6 +1243,7 @@ type (
|
||||
NormalizeStackName(name string) string
|
||||
Up(ctx context.Context, stack *Stack, endpoint *Endpoint, forceRereate bool) error
|
||||
Down(ctx context.Context, stack *Stack, endpoint *Endpoint) error
|
||||
Pull(ctx context.Context, stack *Stack, endpoint *Endpoint) error
|
||||
}
|
||||
|
||||
// CryptoService represents a service for encrypting/hashing data
|
||||
@@ -1392,7 +1393,7 @@ type (
|
||||
SwarmStackManager interface {
|
||||
Login(registries []Registry, endpoint *Endpoint) error
|
||||
Logout(endpoint *Endpoint) error
|
||||
Deploy(stack *Stack, prune bool, endpoint *Endpoint) error
|
||||
Deploy(stack *Stack, prune bool, pullImage bool, endpoint *Endpoint) error
|
||||
Remove(stack *Stack, endpoint *Endpoint) error
|
||||
NormalizeStackName(name string) string
|
||||
}
|
||||
@@ -1400,9 +1401,9 @@ type (
|
||||
|
||||
const (
|
||||
// APIVersion is the version number of the Portainer API
|
||||
APIVersion = "2.15.0"
|
||||
APIVersion = "2.15.1"
|
||||
// DBVersion is the version number of the Portainer database
|
||||
DBVersion = 60
|
||||
DBVersion = 61
|
||||
// ComposeSyntaxMaxVersion is a maximum supported version of the docker compose syntax
|
||||
ComposeSyntaxMaxVersion = "3.9"
|
||||
// AssetsServerURL represents the URL of the Portainer asset server
|
||||
|
||||
@@ -89,12 +89,12 @@ func RedeployWhenChanged(stackID portainer.StackID, deployer StackDeployer, data
|
||||
|
||||
switch stack.Type {
|
||||
case portainer.DockerComposeStack:
|
||||
err := deployer.DeployComposeStack(stack, endpoint, registries, false)
|
||||
err := deployer.DeployComposeStack(stack, endpoint, registries, true, false)
|
||||
if err != nil {
|
||||
return errors.WithMessagef(err, "failed to deploy a docker compose stack %v", stackID)
|
||||
}
|
||||
case portainer.DockerSwarmStack:
|
||||
err := deployer.DeploySwarmStack(stack, endpoint, registries, true)
|
||||
err := deployer.DeploySwarmStack(stack, endpoint, registries, true, true)
|
||||
if err != nil {
|
||||
return errors.WithMessagef(err, "failed to deploy a docker compose stack %v", stackID)
|
||||
}
|
||||
|
||||
@@ -28,11 +28,11 @@ func (g *gitService) LatestCommitID(repositoryURL, referenceName, username, pass
|
||||
|
||||
type noopDeployer struct{}
|
||||
|
||||
func (s *noopDeployer) DeploySwarmStack(stack *portainer.Stack, endpoint *portainer.Endpoint, registries []portainer.Registry, prune bool) error {
|
||||
func (s *noopDeployer) DeploySwarmStack(stack *portainer.Stack, endpoint *portainer.Endpoint, registries []portainer.Registry, prune bool, pullImage bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *noopDeployer) DeployComposeStack(stack *portainer.Stack, endpoint *portainer.Endpoint, registries []portainer.Registry, forceRereate bool) error {
|
||||
func (s *noopDeployer) DeployComposeStack(stack *portainer.Stack, endpoint *portainer.Endpoint, registries []portainer.Registry, forcePullImage bool, forceRereate bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -13,8 +13,8 @@ import (
|
||||
)
|
||||
|
||||
type StackDeployer interface {
|
||||
DeploySwarmStack(stack *portainer.Stack, endpoint *portainer.Endpoint, registries []portainer.Registry, prune bool) error
|
||||
DeployComposeStack(stack *portainer.Stack, endpoint *portainer.Endpoint, registries []portainer.Registry, forceRereate bool) error
|
||||
DeploySwarmStack(stack *portainer.Stack, endpoint *portainer.Endpoint, registries []portainer.Registry, prune bool, pullImage bool) error
|
||||
DeployComposeStack(stack *portainer.Stack, endpoint *portainer.Endpoint, registries []portainer.Registry, forcePullImage bool, forceRereate bool) error
|
||||
DeployKubernetesStack(stack *portainer.Stack, endpoint *portainer.Endpoint, user *portainer.User) error
|
||||
}
|
||||
|
||||
@@ -35,23 +35,31 @@ func NewStackDeployer(swarmStackManager portainer.SwarmStackManager, composeStac
|
||||
}
|
||||
}
|
||||
|
||||
func (d *stackDeployer) DeploySwarmStack(stack *portainer.Stack, endpoint *portainer.Endpoint, registries []portainer.Registry, prune bool) error {
|
||||
func (d *stackDeployer) DeploySwarmStack(stack *portainer.Stack, endpoint *portainer.Endpoint, registries []portainer.Registry, prune bool, pullImage bool) error {
|
||||
d.lock.Lock()
|
||||
defer d.lock.Unlock()
|
||||
|
||||
d.swarmStackManager.Login(registries, endpoint)
|
||||
defer d.swarmStackManager.Logout(endpoint)
|
||||
|
||||
return d.swarmStackManager.Deploy(stack, prune, endpoint)
|
||||
return d.swarmStackManager.Deploy(stack, prune, pullImage, endpoint)
|
||||
}
|
||||
|
||||
func (d *stackDeployer) DeployComposeStack(stack *portainer.Stack, endpoint *portainer.Endpoint, registries []portainer.Registry, forceRereate bool) error {
|
||||
func (d *stackDeployer) DeployComposeStack(stack *portainer.Stack, endpoint *portainer.Endpoint, registries []portainer.Registry, forcePullImage bool, forceRereate bool) error {
|
||||
d.lock.Lock()
|
||||
defer d.lock.Unlock()
|
||||
|
||||
d.swarmStackManager.Login(registries, endpoint)
|
||||
defer d.swarmStackManager.Logout(endpoint)
|
||||
|
||||
// --force-recreate doesn't pull updated images
|
||||
if forcePullImage {
|
||||
err := d.composeStackManager.Pull(context.TODO(), stack, endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err := d.composeStackManager.Up(context.TODO(), stack, endpoint, forceRereate)
|
||||
if err != nil {
|
||||
d.composeStackManager.Down(context.TODO(), stack, endpoint)
|
||||
|
||||
@@ -88,8 +88,8 @@ fieldset[disabled] .btn {
|
||||
@apply hover:bg-gray-3 hover:border-gray-5 hover:text-gray-10;
|
||||
|
||||
/* dark mode */
|
||||
@apply th-dark:bg-gray-warm-10 th-dark:border-gray-warm-7 th-dark:text-gray-warm-4;
|
||||
@apply th-dark:hover:bg-gray-warm-9 th-dark:hover:border-gray-6 th-dark:hover:text-gray-warm-4;
|
||||
@apply th-dark:bg-gray-iron-10 th-dark:border-gray-warm-7 th-dark:text-gray-warm-4;
|
||||
@apply th-dark:hover:bg-gray-iron-9 th-dark:hover:border-gray-6 th-dark:hover:text-gray-warm-4;
|
||||
|
||||
@apply th-highcontrast:bg-black th-highcontrast:border-gray-2 th-highcontrast:text-white;
|
||||
@apply th-highcontrast:hover:bg-gray-9 th-highcontrast:hover:border-gray-6 th-highcontrast:hover:text-gray-warm-4;
|
||||
|
||||
@@ -93,6 +93,8 @@
|
||||
--bg-main-color: var(--white-color);
|
||||
--bg-body-color: var(--grey-9);
|
||||
--bg-checkbox-border-color: var(--grey-49);
|
||||
--bg-sidebar-color: var(--ui-blue-10);
|
||||
--bg-sidebar-nav-color: var(--ui-blue-11);
|
||||
--bg-widget-color: var(--white-color);
|
||||
--bg-widget-header-color: var(--grey-10);
|
||||
--bg-widget-table-color: var(--ui-gray-3);
|
||||
@@ -100,15 +102,16 @@
|
||||
--bg-hover-table-color: var(--grey-14);
|
||||
--bg-switch-box-color: var(--ui-gray-5);
|
||||
--bg-input-group-addon-color: var(--ui-gray-3);
|
||||
--bg-btn-default-color: var(--white-color);
|
||||
--bg-btn-default-color: var(--ui-blue-10);
|
||||
--bg-blocklist-hover-color: var(--ui-blue-2);
|
||||
--bg-boxselector-color: var(--ui-gray-2);
|
||||
--bg-table-color: var(--white-color);
|
||||
--bg-md-checkbox-color: var(--grey-12);
|
||||
--bg-form-control-disabled-color: var(--grey-11);
|
||||
--bg-modal-content-color: var(--white-color);
|
||||
--bg-navtabs-color: var(--white-color);
|
||||
--bg-nav-container-color: var(--ui-gray-2);
|
||||
--bg-navtabs-hover-color: var(--grey-16);
|
||||
--bg-nav-tab-active-color: var(--ui-gray-4);
|
||||
--bg-table-selected-color: var(--grey-14);
|
||||
--bg-codemirror-color: var(--white-color);
|
||||
--bg-codemirror-gutters-color: var(--grey-17);
|
||||
@@ -141,7 +144,7 @@
|
||||
--bg-daterangepicker-in-range: var(--grey-58);
|
||||
--bg-daterangepicker-active: var(--blue-14);
|
||||
--bg-input-autofill-color: var(--white-color);
|
||||
--bg-btn-default-hover-color: var(--grey-59);
|
||||
--bg-btn-default-hover-color: var(--ui-blue-9);
|
||||
--bg-btn-focus: var(--grey-59);
|
||||
--bg-boxselector-disabled-color: var(--white-color);
|
||||
--bg-small-select-color: var(--white-color);
|
||||
@@ -155,9 +158,7 @@
|
||||
--bg-webeditor-color: var(--ui-gray-3);
|
||||
--bg-button-group-color: var(--ui-white);
|
||||
--bg-pagination-disabled-color: var(--ui-white);
|
||||
--bg-nav-container-color: var(--ui-gray-2);
|
||||
--bg-code-script-color: var(--ui-white);
|
||||
--bg-nav-tabs-active-color: var(--ui-gray-4);
|
||||
--bg-stepper-color: var(--ui-white);
|
||||
--bg-stepper-active-color: var(--ui-blue-1);
|
||||
--bg-code-color: var(--ui-white);
|
||||
@@ -174,7 +175,9 @@
|
||||
--text-dashboard-item-color: var(--grey-32);
|
||||
--text-danger-color: var(--red-1);
|
||||
--text-code-color: var(--ui-gray-9);
|
||||
--text-navtabs-color: var(--grey-25);
|
||||
--text-navtabs-color: var(--grey-7);
|
||||
--text-navtabs-hover-color: var(--grey-6);
|
||||
--text-nav-tab-active-color: var(--grey-25);
|
||||
--text-cm-default-color: var(--blue-1);
|
||||
--text-cm-meta-color: var(--black-color);
|
||||
--text-cm-string-color: var(--red-3);
|
||||
@@ -211,7 +214,7 @@
|
||||
|
||||
--border-color: var(--grey-42);
|
||||
--border-widget-color: var(--grey-43);
|
||||
--border-sidebar-color: var(--white-color);
|
||||
--border-sidebar-color: var(--ui-blue-9);
|
||||
--border-form-control-color: var(--grey-44);
|
||||
--border-table-color: var(--grey-19);
|
||||
--border-table-top-color: var(--grey-19);
|
||||
@@ -262,12 +265,13 @@
|
||||
--bg-multiselect-helpercontainer: var(--white-color);
|
||||
--text-input-textarea: var(--white-color);
|
||||
|
||||
--user-menu-icon-color: var(--ui-gray-4);
|
||||
--sort-icon-muted: var(--ui-gray-5);
|
||||
--sort-icon-hover: var(--ui-gray-6);
|
||||
--sort-icon: var(--ui-gray-9);
|
||||
--border-checkbox: var(--ui-gray-5);
|
||||
--bg-checkbox: var(--white-color);
|
||||
--border-searchbar: var(--ui-gray-5);
|
||||
--border-searchbar: var(--grey-44);
|
||||
--bg-button-group: var(--white-color);
|
||||
--border-button-group: var(--ui-gray-5);
|
||||
--text-button-group: var(--ui-gray-9);
|
||||
@@ -282,15 +286,17 @@
|
||||
--bg-blocklist-item-selected-color: var(--grey-3);
|
||||
--bg-card-color: var(--grey-1);
|
||||
--bg-checkbox-border-color: var(--grey-8);
|
||||
--bg-code-color: var(--ui-gray-warm-11);
|
||||
--bg-codemirror-color: var(--ui-gray-warm-11);
|
||||
--bg-codemirror-gutters-color: var(--ui-gray-warm-8);
|
||||
--bg-codemirror-selected-color: var(--ui-gray-warm-7);
|
||||
--bg-code-color: var(--grey-2);
|
||||
--bg-codemirror-color: var(--grey-2);
|
||||
--bg-codemirror-gutters-color: var(--grey-3);
|
||||
--bg-codemirror-selected-color: var(--grey-3);
|
||||
--bg-dropdown-menu-color: var(--ui-gray-7);
|
||||
--bg-main-color: var(--grey-2);
|
||||
--bg-widget-color: var(--ui-gray-warm-10);
|
||||
--bg-widget-header-color: var(--grey-1);
|
||||
--bg-widget-table-color: var(--ui-gray-warm-9);
|
||||
--bg-sidebar-color: var(--grey-1);
|
||||
--bg-sidebar-nav-color: var(--grey-2);
|
||||
--bg-widget-color: var(--grey-1);
|
||||
--bg-widget-header-color: var(--grey-3);
|
||||
--bg-widget-table-color: var(--grey-3);
|
||||
--bg-header-color: var(--grey-2);
|
||||
--bg-hover-table-color: var(--grey-3);
|
||||
--bg-switch-box-color: var(--grey-53);
|
||||
@@ -298,8 +304,9 @@
|
||||
--bg-md-checkbox-color: var(--grey-31);
|
||||
--bg-form-control-disabled-color: var(--grey-3);
|
||||
--bg-modal-content-color: var(--grey-1);
|
||||
--bg-navtabs-color: var(--ui-gray-warm-11);
|
||||
--bg-nav-container-color: var(--ui-gray-iron-10);
|
||||
--bg-navtabs-hover-color: var(--grey-3);
|
||||
--bg-nav-tab-active-color: var(--grey-2);
|
||||
--bg-table-selected-color: var(--grey-3);
|
||||
--bg-log-viewer-color: var(--grey-2);
|
||||
--bg-log-line-selected-color: var(--grey-3);
|
||||
@@ -330,7 +337,7 @@
|
||||
--bg-daterangepicker-in-range: var(--ui-gray-warm-11);
|
||||
--bg-daterangepicker-active: var(--blue-14);
|
||||
--bg-input-autofill-color: var(--grey-2);
|
||||
--bg-btn-default-hover-color: var(--grey-3);
|
||||
--bg-btn-default-hover-color: var(--grey-4);
|
||||
--bg-btn-focus: var(--grey-3);
|
||||
--bg-boxselector-disabled-color: var(--grey-54);
|
||||
--bg-small-select-color: var(--grey-2);
|
||||
@@ -341,12 +348,10 @@
|
||||
--bg-searchbar: var(--ui-grey-warm-11);
|
||||
--bg-inputbox: var(--grey-2);
|
||||
--bg-dropdown-hover: var(--grey-3);
|
||||
--bg-webeditor-color: var(--ui-gray-warm-9);
|
||||
--bg-webeditor-color: var(--ui-gray-iron-10);
|
||||
--bg-button-group-color: var(--ui-black);
|
||||
--bg-pagination-disabled-color: var(--grey-1);
|
||||
--bg-nav-container-color: var(--ui-gray-iron-10);
|
||||
--bg-code-script-color: var(--ui-gray-warm-11);
|
||||
--bg-nav-tabs-active-color: var(--ui-gray-warm-9);
|
||||
--bg-stepper-color: var(--ui-gray-iron-10);
|
||||
--bg-stepper-active-color: var(--ui-blue-8);
|
||||
|
||||
@@ -362,7 +367,9 @@
|
||||
--text-dashboard-item-color: var(--blue-2);
|
||||
--text-danger-color: var(--red-4);
|
||||
--text-code-color: var(--white-color);
|
||||
--text-navtabs-color: var(--white-color);
|
||||
--text-navtabs-color: var(--grey-8);
|
||||
--text-navtabs-hover-color: var(--grey-9);
|
||||
--text-nav-tab-active-color: var(--white-color);
|
||||
--text-cm-default-color: var(--blue-10);
|
||||
--text-cm-meta-color: var(--white-color);
|
||||
--text-cm-string-color: var(--red-5);
|
||||
@@ -400,7 +407,7 @@
|
||||
|
||||
--border-color: var(--grey-3);
|
||||
--border-widget-color: var(--grey-1);
|
||||
--border-sidebar-color: var(--blue-9);
|
||||
--border-sidebar-color: var(--ui-gray-8);
|
||||
--border-form-control-color: var(--grey-54);
|
||||
--border-table-color: var(--grey-3);
|
||||
--border-table-top-color: var(--grey-3);
|
||||
@@ -428,7 +435,7 @@
|
||||
--border-sortbutton: var(--grey-3);
|
||||
--border-bootbox: var(--ui-gray-9);
|
||||
--border-blocklist: var(--ui-gray-9);
|
||||
--border-widget: var(--ui-gray-9);
|
||||
--border-widget: var(--grey-3);
|
||||
--border-pagination-color: var(--grey-1);
|
||||
--border-nav-container-color: var(--ui-gray-neutral-8);
|
||||
--border-stepper-color: var(--ui-gray-warm-9);
|
||||
@@ -451,12 +458,13 @@
|
||||
--bg-multiselect-helpercontainer: var(--grey-1);
|
||||
--text-input-textarea: var(--grey-1);
|
||||
|
||||
--user-menu-icon-color: var(--grey-3);
|
||||
--sort-icon-muted: var(--ui-gray-7);
|
||||
--sort-icon-hover: var(--ui-gray-6);
|
||||
--sort-icon: var(--ui-gray-3);
|
||||
--border-checkbox: var(--ui-gray-5);
|
||||
--bg-checkbox: var(--white-color);
|
||||
--border-searchbar: var(--ui-gray-warm-9);
|
||||
--border-searchbar: var(--grey-54);
|
||||
--bg-button-group: var(--white-color);
|
||||
--border-button-group: var(--ui-gray-5);
|
||||
--text-button-group: var(--ui-gray-9);
|
||||
@@ -468,6 +476,8 @@
|
||||
--bg-main-color: var(--black-color);
|
||||
--bg-body-color: var(--black-color);
|
||||
--bg-checkbox-border-color: var(--grey-8);
|
||||
--bg-sidebar-color: var(--black-color);
|
||||
--bg-sidebar-nav-color: var(--black-color);
|
||||
--bg-widget-color: var(--black-color);
|
||||
--bg-widget-header-color: var(--black-color);
|
||||
--bg-widget-table-color: var(--black-color);
|
||||
@@ -509,14 +519,13 @@
|
||||
--bg-tooltip-color: var(--black-color);
|
||||
--bg-table-selected-color: var(--grey-3);
|
||||
--bg-pre-color: var(--grey-2);
|
||||
--bg-nav-container-color: var(--ui-black);
|
||||
--bg-navtabs-hover-color: var(--grey-3);
|
||||
--bg-nav-tab-active-color: var(--ui-black);
|
||||
--bg-btn-default-color: var(--black-color);
|
||||
--bg-navtabs-color: var(--black-color);
|
||||
--bg-input-autofill-color: var(--black-color);
|
||||
--bg-code-color: var(--ui-black);
|
||||
--bg-navtabs-hover-color: var(--grey-3);
|
||||
--bg-btn-default-hover-color: var(--grey-3);
|
||||
--bg-btn-default-color: var(--black-color);
|
||||
--bg-btn-default-hover-color: var(--grey-4);
|
||||
--bg-btn-focus: var(--black-color);
|
||||
--bg-boxselector-color: var(--black-color);
|
||||
--bg-boxselector-disabled-color: var(--black-color);
|
||||
@@ -530,9 +539,7 @@
|
||||
--bg-webeditor-color: var(--ui-gray-warm-9);
|
||||
--bg-pagination-disabled-color: var(--ui-black);
|
||||
--bg-pagination-hover-color: var(--ui-black);
|
||||
--bg-nav-container-color: var(--ui-black);
|
||||
--bg-code-script-color: var(--ui-black);
|
||||
--bg-nav-tabs-active-color: var(--ui-black);
|
||||
--bg-stepper-active-color: var(--ui-blue-8);
|
||||
--bg-stepper-color: var(--ui-black);
|
||||
|
||||
@@ -567,8 +574,9 @@
|
||||
--text-json-tree-branch-preview-color: var(--white-color);
|
||||
--text-pre-color: var(--white-color);
|
||||
--text-navtabs-color: var(--white-color);
|
||||
--text-navtabs-hover-color: var(--white-color);
|
||||
--text-nav-tab-active-color: var(--white-color);
|
||||
--text-input-autofill-color: var(--white-color);
|
||||
--text-navtabs-color: var(--white-color);
|
||||
--text-button-hover-color: var(--white-color);
|
||||
--text-small-select-color: var(--white-color);
|
||||
--text-pagination-span-color: var(--ui-white);
|
||||
@@ -579,7 +587,7 @@
|
||||
|
||||
--border-color: var(--grey-55);
|
||||
--border-widget-color: var(--white-color);
|
||||
--border-sidebar-color: var(--blue-9);
|
||||
--border-sidebar-color: var(--white-color);
|
||||
--border-form-control-color: var(--grey-54);
|
||||
--border-table-color: var(--grey-55);
|
||||
--border-table-top-color: var(--grey-55);
|
||||
@@ -627,6 +635,7 @@
|
||||
--text-cm-string-color: var(--red-7);
|
||||
--text-progress-bar-color: var(--black-color);
|
||||
|
||||
--user-menu-icon-color: var(--white-color);
|
||||
--sort-icon-muted: var(--ui-gray-7);
|
||||
--sort-icon-hover: var(--ui-gray-6);
|
||||
--sort-icon: var(--ui-gray-3);
|
||||
|
||||
@@ -104,8 +104,8 @@ code {
|
||||
.nav-tabs > li.active > a,
|
||||
.nav-tabs > li.active > a:hover,
|
||||
.nav-tabs > li.active > a:focus {
|
||||
color: var(--text-navtabs-color);
|
||||
background-color: var(--bg-navtabs-color);
|
||||
color: var(--text-nav-tab-active-color);
|
||||
background-color: var(--bg-nav-tab-active-color);
|
||||
border: 1px solid var(--border-navtabs-color);
|
||||
}
|
||||
|
||||
@@ -117,8 +117,13 @@ code {
|
||||
border-color: var(--border-navtabs-color);
|
||||
}
|
||||
|
||||
.nav > li > a {
|
||||
color: var(--text-navtabs-color);
|
||||
}
|
||||
|
||||
.nav > li > a:hover,
|
||||
.nav > li > a:focus {
|
||||
color: var(--text-navtabs-hover-color);
|
||||
background-color: var(--bg-navtabs-hover-color);
|
||||
}
|
||||
|
||||
@@ -436,6 +441,5 @@ input:-webkit-autofill {
|
||||
}
|
||||
|
||||
.nav-tabs > li {
|
||||
background-color: var(--bg-nav-tabs-active-color);
|
||||
border-top-right-radius: 8px;
|
||||
}
|
||||
|
||||
@@ -211,10 +211,10 @@
|
||||
type="button"
|
||||
class="btn btn-primary btn-sm"
|
||||
ng-disabled="state.actionInProgress
|
||||
|| (state.BuildType === 'editor' && formValues.DockerFileContent === '')
|
||||
|| (state.BuildType === 'upload' && (!formValues.UploadFile || !formValues.Path))
|
||||
|| (state.BuildType === 'url' && (!formValues.URL || !formValues.Path))
|
||||
|| (formValues.ImageNames.length === 0 || !validImageNames())
|
||||
|| (formValues.DockerFileContent === '')"
|
||||
|| (formValues.ImageNames.length === 0 || !validImageNames())"
|
||||
ng-click="buildImage()"
|
||||
button-spinner="state.actionInProgress"
|
||||
>
|
||||
|
||||
@@ -72,8 +72,6 @@
|
||||
|
||||
.datatable .searchBar input[type='text'] {
|
||||
border: 0px !important;
|
||||
@apply placeholder:text-gray-7;
|
||||
@apply th-dark:placeholder:text-gray-warm-7;
|
||||
}
|
||||
|
||||
.datatable .searchIcon {
|
||||
|
||||
@@ -28,6 +28,7 @@ class StackRedeployGitFormController {
|
||||
RepositoryUsername: '',
|
||||
RepositoryPassword: '',
|
||||
Env: [],
|
||||
PullImage: false,
|
||||
Option: {
|
||||
Prune: false,
|
||||
},
|
||||
@@ -102,33 +103,35 @@ class StackRedeployGitFormController {
|
||||
}
|
||||
|
||||
async submit() {
|
||||
const tplCrop =
|
||||
'<div>Any changes to this stack or application made locally in Portainer will be overridden, which may cause service interruption. Do you wish to continue?</div>' +
|
||||
'<div"><div style="position: absolute; right: 5px; top: 84px; z-index: 999">' +
|
||||
'<be-feature-indicator feature="stackPullImageFeature"></be-feature-indicator></div></div>';
|
||||
const template = angular.element(tplCrop);
|
||||
const html = this.$compile(template)(this.$scope);
|
||||
this.ModalService.confirmStackUpdate(html, true, false, 'btn-warning', async (result) => {
|
||||
if (!result) {
|
||||
return;
|
||||
const isSwarmStack = this.stack.Type === 1;
|
||||
const that = this;
|
||||
this.ModalService.confirmStackUpdate(
|
||||
'Any changes to this stack or application made locally in Portainer will be overridden, which may cause service interruption. Do you wish to continue?',
|
||||
isSwarmStack,
|
||||
'btn-warning',
|
||||
async function (result) {
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
that.state.redeployInProgress = true;
|
||||
await that.StackService.updateGit(
|
||||
that.stack.Id,
|
||||
that.stack.EndpointId,
|
||||
that.FormHelper.removeInvalidEnvVars(that.formValues.Env),
|
||||
that.formValues.Option.Prune,
|
||||
that.formValues,
|
||||
!!result[0]
|
||||
);
|
||||
that.Notifications.success('Success', 'Pulled and redeployed stack successfully');
|
||||
that.$state.reload();
|
||||
} catch (err) {
|
||||
that.Notifications.error('Failure', err, 'Failed redeploying stack');
|
||||
} finally {
|
||||
that.state.redeployInProgress = false;
|
||||
}
|
||||
}
|
||||
try {
|
||||
this.state.redeployInProgress = true;
|
||||
await this.StackService.updateGit(
|
||||
this.stack.Id,
|
||||
this.stack.EndpointId,
|
||||
this.FormHelper.removeInvalidEnvVars(this.formValues.Env),
|
||||
this.formValues.Option.Prune,
|
||||
this.formValues
|
||||
);
|
||||
this.Notifications.success('Success', 'Pulled and redeployed stack successfully');
|
||||
this.$state.reload();
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Failed redeploying stack');
|
||||
} finally {
|
||||
this.state.redeployInProgress = false;
|
||||
}
|
||||
});
|
||||
);
|
||||
}
|
||||
|
||||
async saveGitSettings() {
|
||||
|
||||
@@ -266,8 +266,17 @@ angular.module('portainer.app').factory('StackService', [
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.updateStack = function (stack, stackFile, env, prune) {
|
||||
return Stack.update({ endpointId: stack.EndpointId }, { id: stack.Id, StackFileContent: stackFile, Env: env, Prune: prune }).$promise;
|
||||
service.updateStack = function (stack, stackFile, env, prune, pullImage) {
|
||||
return Stack.update(
|
||||
{ endpointId: stack.EndpointId },
|
||||
{
|
||||
id: stack.Id,
|
||||
StackFileContent: stackFile,
|
||||
Env: env,
|
||||
Prune: prune,
|
||||
PullImage: pullImage,
|
||||
}
|
||||
).$promise;
|
||||
};
|
||||
|
||||
service.updateKubeStack = function (stack, stackFile, gitConfig) {
|
||||
@@ -436,7 +445,7 @@ angular.module('portainer.app').factory('StackService', [
|
||||
return Stack.stop({ id }).$promise;
|
||||
}
|
||||
|
||||
function updateGit(id, endpointId, env, prune, gitConfig) {
|
||||
function updateGit(id, endpointId, env, prune, gitConfig, pullImage) {
|
||||
return Stack.updateGit(
|
||||
{ endpointId, id },
|
||||
{
|
||||
@@ -446,6 +455,7 @@ angular.module('portainer.app').factory('StackService', [
|
||||
RepositoryAuthentication: gitConfig.RepositoryAuthentication,
|
||||
RepositoryUsername: gitConfig.RepositoryUsername,
|
||||
RepositoryPassword: gitConfig.RepositoryPassword,
|
||||
PullImage: pullImage,
|
||||
}
|
||||
).$promise;
|
||||
}
|
||||
|
||||
@@ -162,45 +162,31 @@ export function confirmServiceForceUpdate(
|
||||
|
||||
export function confirmStackUpdate(
|
||||
message: string,
|
||||
defaultDisabled: boolean,
|
||||
defaultToggle: boolean,
|
||||
confirmButtonClassName: string | undefined,
|
||||
confirmButtonClass: string | undefined,
|
||||
callback: PromptCallback
|
||||
) {
|
||||
const sanitizedMessage =
|
||||
typeof message === 'string' ? sanitize(message) : message;
|
||||
const sanitizedMessage = sanitize(message);
|
||||
|
||||
const box = prompt({
|
||||
title: buildTitle('Are you sure?'),
|
||||
inputType: 'checkbox',
|
||||
inputOptions: [
|
||||
{
|
||||
text: 'Pull latest image version<i></i>',
|
||||
text: 'Re-pull image and redeploy<i></i>',
|
||||
value: '1',
|
||||
},
|
||||
],
|
||||
buttons: {
|
||||
confirm: {
|
||||
label: 'Update',
|
||||
className: confirmButtonClassName || 'btn-primary',
|
||||
className: 'btn-primary',
|
||||
},
|
||||
},
|
||||
callback,
|
||||
});
|
||||
box.find('.bootbox-body').prepend(sanitizedMessage);
|
||||
const checkbox = box.find('.bootbox-input-checkbox');
|
||||
checkbox.prop('checked', defaultToggle);
|
||||
checkbox.prop('disabled', defaultDisabled);
|
||||
const checkboxDiv = box.find('.checkbox');
|
||||
checkboxDiv.removeClass('checkbox');
|
||||
checkboxDiv.prop(
|
||||
'style',
|
||||
'position: relative; display: block; margin-top: 10px; margin-bottom: 10px;'
|
||||
);
|
||||
const checkboxLabel = box.find('.form-check-label');
|
||||
checkboxLabel.addClass('switch box-selector-item limited business mt-4');
|
||||
checkboxLabel.prop('style', 'width: 100%');
|
||||
const switchEle = checkboxLabel.find('i');
|
||||
switchEle.prop('style', 'margin-left:20px');
|
||||
|
||||
customizeCheckboxPrompt(box, sanitizedMessage, defaultToggle);
|
||||
}
|
||||
|
||||
function customizeCheckboxPrompt(
|
||||
|
||||
@@ -80,6 +80,7 @@
|
||||
autocomplete="off"
|
||||
data-cy="auth-passwordInput"
|
||||
placeholder="Enter your password"
|
||||
ng-trim="false"
|
||||
/>
|
||||
<button
|
||||
data-cy="auth-passwordInputToggle"
|
||||
|
||||
@@ -244,13 +244,8 @@ angular.module('portainer.app').controller('StackController', [
|
||||
|
||||
$scope.deployStack = function () {
|
||||
const stack = $scope.stack;
|
||||
const tplCrop =
|
||||
'<div>Do you want to force an update of the stack?</div>' +
|
||||
'<div style="position: absolute; right: 5px; top: 50px; z-index: 999"><be-feature-indicator feature="stackPullImageFeature"></be-feature-indicator></div>';
|
||||
const template = angular.element(tplCrop);
|
||||
const html = $compile(template)($scope);
|
||||
// 'Do you want to force an update of the stack?'
|
||||
ModalService.confirmStackUpdate(html, true, false, null, function (result) {
|
||||
const isSwarmStack = stack.Type === 1;
|
||||
ModalService.confirmStackUpdate('Do you want to force an update of the stack?', isSwarmStack, null, function (result) {
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
@@ -267,7 +262,7 @@ angular.module('portainer.app').controller('StackController', [
|
||||
}
|
||||
|
||||
$scope.state.actionInProgress = true;
|
||||
StackService.updateStack(stack, stackFile, env, prune)
|
||||
StackService.updateStack(stack, stackFile, env, prune, !!result[0])
|
||||
.then(function success() {
|
||||
Notifications.success('Success', 'Stack successfully deployed');
|
||||
$scope.state.isEditorDirty = false;
|
||||
|
||||
@@ -7,6 +7,10 @@
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.menu-icon {
|
||||
background: var(--user-menu-icon-color);
|
||||
}
|
||||
|
||||
.menu-list {
|
||||
background: var(--bg-dropdown-menu-color);
|
||||
border-radius: 8px;
|
||||
|
||||
@@ -28,9 +28,10 @@ export function UserMenu() {
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
styles.menuIcon,
|
||||
'icon-badge text-lg !p-2 mr-1',
|
||||
'bg-gray-4 text-gray-8',
|
||||
'th-dark:bg-gray-warm-10 th-dark:text-gray-warm-7'
|
||||
'text-gray-8',
|
||||
'th-dark:text-gray-warm-7'
|
||||
)}
|
||||
>
|
||||
<User className="feather" />
|
||||
|
||||
12
app/react/sidebar/EnvironmentSidebar.module.css
Normal file
12
app/react/sidebar/EnvironmentSidebar.module.css
Normal file
@@ -0,0 +1,12 @@
|
||||
.root {
|
||||
background-color: var(--bg-sidebar-nav-color);
|
||||
border-color: var(--border-sidebar-color);
|
||||
}
|
||||
|
||||
.closeBtn {
|
||||
background-color: var(--bg-btn-default-color);
|
||||
}
|
||||
|
||||
.closeBtn:hover {
|
||||
background-color: var(--bg-btn-default-hover-color);
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import { useLocalStorage } from '@/portainer/hooks/useLocalStorage';
|
||||
|
||||
import { getPlatformIcon } from '../portainer/environments/utils/get-platform-icon';
|
||||
|
||||
import styles from './EnvironmentSidebar.module.css';
|
||||
import { AzureSidebar } from './AzureSidebar';
|
||||
import { DockerSidebar } from './DockerSidebar';
|
||||
import { KubernetesSidebar } from './KubernetesSidebar';
|
||||
@@ -32,13 +33,7 @@ export function EnvironmentSidebar() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
'rounded border border-dotted py-2',
|
||||
'bg-blue-11 be:bg-gray-10 th-dark:bg-gray-warm-11',
|
||||
'border-blue-9 be:border-gray-8 th-dark:border-gray-warm-9'
|
||||
)}
|
||||
>
|
||||
<div className={clsx(styles.root, 'rounded border border-dotted py-2')}>
|
||||
{environment ? (
|
||||
<Content environment={environment} onClear={clearEnvironment} />
|
||||
) : (
|
||||
@@ -145,7 +140,10 @@ function Title({ environment, onClear }: TitleProps) {
|
||||
title="Clear environment"
|
||||
type="button"
|
||||
onClick={onClear}
|
||||
className="flex items-center justify-center be:bg-gray-9 bg-blue-10 th-dark:bg-gray-warm-10 hover:bg-blue-9 be:hover:bg-gray-7 th-dark:hover:bg-gray-8 transition-colors duration-200 rounded border-0 text-sm h-5 w-5 p-1 ml-auto mr-2 text-gray-5 be:text-gray-6 hover:text-white be:hover:text-white"
|
||||
className={clsx(
|
||||
styles.closeBtn,
|
||||
'flex items-center justify-center transition-colors duration-200 rounded border-0 text-sm h-5 w-5 p-1 ml-auto mr-2 text-gray-5 be:text-gray-6 hover:text-white be:hover:text-white'
|
||||
)}
|
||||
>
|
||||
<X />
|
||||
</button>
|
||||
|
||||
@@ -3,3 +3,7 @@
|
||||
max-height: 55px;
|
||||
max-width: min(100%, 230px);
|
||||
}
|
||||
|
||||
.collapseBtn:hover {
|
||||
background-color: var(--bg-btn-default-hover-color);
|
||||
}
|
||||
|
||||
@@ -61,11 +61,12 @@ export function Header({ logo: customLogo }: Props) {
|
||||
type="button"
|
||||
onClick={() => toggle()}
|
||||
className={clsx(
|
||||
styles.collapseBtn,
|
||||
'w-6 h-6 flex justify-center items-center border-0 rounded',
|
||||
'transition-all duration-200',
|
||||
'text-sm text-gray-4 be:text-gray-5 hover:text-white be:hover:text-white',
|
||||
'bg-blue-11 hover:bg-blue-10 be:bg-gray-10 be:hover:bg-gray-8',
|
||||
'th-dark:bg-gray-warm-11 hover:th-dark:bg-gray-warm-9',
|
||||
'bg-blue-11 be:bg-gray-10',
|
||||
'th-dark:bg-gray-warm-11',
|
||||
'absolute',
|
||||
{ '-right-[10px]': !isOpen, 'right-6': isOpen }
|
||||
)}
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
|
||||
z-index: 999;
|
||||
transition: all 0.4s ease 0s;
|
||||
background-color: var(--bg-sidebar-color);
|
||||
}
|
||||
|
||||
.root ul {
|
||||
|
||||
@@ -29,13 +29,7 @@ export function Sidebar() {
|
||||
return (
|
||||
/* in the future (when we remove r2a) this should wrap the whole app - to change root styles */
|
||||
<SidebarProvider>
|
||||
<nav
|
||||
className={clsx(
|
||||
styles.root,
|
||||
'p-5 flex flex-col be:bg-gray-11 bg-blue-10 th-dark:bg-gray-warm-10'
|
||||
)}
|
||||
aria-label="Main"
|
||||
>
|
||||
<nav className={clsx(styles.root, 'p-5 flex flex-col')} aria-label="Main">
|
||||
<Header logo={LogoURL} />
|
||||
|
||||
{/* negative margin + padding -> scrollbar won't hide the content */}
|
||||
|
||||
@@ -34,7 +34,7 @@ export function Head({
|
||||
const { isOpen } = useSidebarState();
|
||||
const anchorProps = useSrefActive(
|
||||
to,
|
||||
'bg-blue-8 be:bg-gray-8 th-dark:bg-gray-9',
|
||||
'bg-blue-8 be:bg-gray-8 th-dark:bg-gray-true-8',
|
||||
params,
|
||||
options,
|
||||
ignorePaths
|
||||
@@ -48,7 +48,7 @@ export function Head({
|
||||
anchorProps.className,
|
||||
'text-inherit no-underline hover:no-underline hover:text-inherit focus:no-underline focus:text-inherit',
|
||||
'w-full flex-1 rounded-md flex items-center h-8 space-x-4 text-sm',
|
||||
'hover:bg-blue-9 be:hover:bg-gray-9 transition-colors duration-200',
|
||||
'hover:bg-blue-9 th-dark:hover:bg-gray-true-9 be:hover:bg-gray-9 transition-colors duration-200',
|
||||
{
|
||||
'px-3 justify-start w-full': isOpen,
|
||||
'justify-center w-8': !isOpen,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Portainer.io",
|
||||
"name": "portainer",
|
||||
"homepage": "http://portainer.io",
|
||||
"version": "2.15.0",
|
||||
"version": "2.15.1",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:portainer/portainer.git"
|
||||
|
||||
Reference in New Issue
Block a user