Compare commits

...

9 Commits

Author SHA1 Message Date
congs
4cc3f6bab4 fix(stack): EE-4213 Allow latest image to be pulled for stacks: backport backend logic (#7670)
Some checks failed
/ triage (push) Has been cancelled
Lint / Run linters (push) Has been cancelled
Test / test-client (push) Has been cancelled
2022-09-15 16:57:36 +12:00
congs
6330cc885c fix(stack): EE-4213 Allow latest image to be pulled for stacks (#7654) 2022-09-14 10:17:27 +12:00
itsconquest
cd3381ee59 fix(theme): tabs and codeeditor darkmode correction [EE-4188] (#7642)
* fix(theme): tabs and codeeditor darkmode correction [EE-4188]

* correct codemirror background
2022-09-12 17:07:07 +12:00
itsconquest
7b7234c79a fix(theme): env sidebar darkmode color [EE-4188] (#7637)
* fix(theme): env sidebar darkmode color [EE-4188]

* style usericon

* further dark mode changes
2022-09-09 12:47:10 +12:00
Prabhat Khera
a965f5d538 bump version to 2.15.1 (#7634) 2022-09-08 16:55:10 +12:00
itsconquest
b31176da7a fix(auth): prevent trim on password [EE-4197] (#7632) 2022-09-08 13:50:17 +12:00
LP B
aacf4eb880 fix(theme): update dark mode colors [EE-4188] (#7628)
* fix(theme): update dark mode colors [EE-4188]

* fix sidebar hover/selected

Co-authored-by: itsconquest <william.conquest@portainer.io>
2022-09-08 13:49:14 +12:00
LP B
b19e9906fc fix(images/build): enforce file content length only when using the editor (#7629) 2022-09-08 02:32:41 +02:00
Dmitry Salakhov
576456efb2 fix: don't url-escape socket paths (#7622) 2022-09-08 11:45:01 +12:00
35 changed files with 235 additions and 166 deletions

View File

@@ -919,7 +919,7 @@
],
"version": {
"DB_UPDATING": "false",
"DB_VERSION": "60",
"DB_VERSION": "61",
"INSTANCE_ID": "null"
}
}

View File

@@ -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), "")

View File

@@ -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)

View File

@@ -82,7 +82,7 @@ type Handler struct {
}
// @title PortainerCE API
// @version 2.15.0
// @version 2.15.1
// @description.markdown api-description.md
// @termsOfService

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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;

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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"
>

View File

@@ -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 {

View File

@@ -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() {

View File

@@ -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;
}

View File

@@ -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(

View File

@@ -80,6 +80,7 @@
autocomplete="off"
data-cy="auth-passwordInput"
placeholder="Enter your password"
ng-trim="false"
/>
<button
data-cy="auth-passwordInputToggle"

View File

@@ -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;

View File

@@ -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;

View File

@@ -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" />

View 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);
}

View File

@@ -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>

View File

@@ -3,3 +3,7 @@
max-height: 55px;
max-width: min(100%, 230px);
}
.collapseBtn:hover {
background-color: var(--bg-btn-default-hover-color);
}

View File

@@ -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 }
)}

View File

@@ -32,6 +32,7 @@
z-index: 999;
transition: all 0.4s ease 0s;
background-color: var(--bg-sidebar-color);
}
.root ul {

View File

@@ -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 */}

View File

@@ -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,

View File

@@ -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"