Repository: ovh/cds
Pull request #6135 (2022-04-05 14:29:29)

chore(deps): bump minimist from 1.2.5 to 1.2.6 in /contrib/grpcplugins/action/examples/nodejs
Bumps [minimist](https://github.com/substack/minimist) from 1.2.5 to 1.2.6.
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/substack/minimist/commit/7efb22a518b53b06f5b02a1038a88bd6290c2846"><code>7efb22a</code></a> 1.2.6</li>
<li><a href="https://github.com/substack/minimist/commit/ef88b9325f77b5ee643ccfc97e2ebda577e4c4e2"><code>ef88b93</code></a> security notice for additional prototype pollution issue</li>
<li><a href="https://github.com/substack/minimist/commit/c2b981977fa834b223b408cfb860f933c9811e4d"><code>c2b9819</code></a> isConstructorOrProto adapted from PR</li>
<li><a href="https://github.com/substack/minimist/commit/bc8ecee43875261f4f17eb20b1243d3ed15e70eb"><code>bc8ecee</code></a> test from prototype pollution PR</li>
<li>See full diff in <a href="https://github.com/substack/minimist/compare/1.2.5...1.2.6">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=minimist&package-manager=npm_and_yarn&previous-version=1.2.5&new-version=1.2.6)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
- `@dependabot use these labels` will set the current labels as the default for future PRs for this repo and language
- `@dependabot use these reviewers` will set the current reviewers as the default for future PRs for this repo and language
- `@dependabot use these assignees` will set the current assignees as the default for future PRs for this repo and language
- `@dependabot use this milestone` will set the current milestone as the default for future PRs for this repo and language

You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/ovh/cds/network/alerts).

</details>


Patch:
diff --git a/contrib/grpcplugins/action/examples/nodejs/package-lock.json b/contrib/grpcplugins/action/examples/nodejs/package-lock.json
index 97380b20b..aeb7cf01d 100644
--- a/contrib/grpcplugins/action/examples/nodejs/package-lock.json
+++ b/contrib/grpcplugins/action/examples/nodejs/package-lock.json
@@ -1809,12 +1809,6 @@
             "brace-expansion": "^1.1.7"
           }
         },
-        "minimist": {
-          "version": "1.2.5",
-          "bundled": true,
-          "dev": true,
-          "optional": true
-        },
         "minipass": {
           "version": "2.9.0",
           "bundled": true,
@@ -2863,9 +2857,9 @@
       }
     },
     "minimist": {
-      "version": "1.2.5",
-      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
-      "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
+      "version": "1.2.6",
+      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
+      "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==",
       "dev": true
     },
     "minipass": {
diff --git a/docs/content/docs/concepts/files/workflow-syntax.md b/docs/content/docs/concepts/files/workflow-syntax.md
index 2ab4d9a10..8c57dde48 100644
--- a/docs/content/docs/concepts/files/workflow-syntax.md
+++ b/docs/content/docs/concepts/files/workflow-syntax.md
@@ -27,6 +27,9 @@ workflow:
 hooks:
   build:
   - type: RepositoryWebHook
+integrations:
+  my-artifactory-integration-name:
+    type: artifact_manager
 notifications:
 - type: email
   pipelines:
@@ -81,6 +84,10 @@ Example with using LUA syntax as advanced condition:
       == "master" and git_repository == "ovh/cds")
 ```
 
+## Integrations
+
+[Artifactory Integration]({{<relref "/docs/integrations/artifact-manager.md">}})
+
 ## Notifications
 
 [Notifications documentation]({{<relref "/docs/concepts/workflow/notifications.md">}})
diff --git a/docs/content/docs/concepts/workflow/hooks/_index.md b/docs/content/docs/concepts/workflow/hooks/_index.md
index bd187a9ae..97ac60887 100644
--- a/docs/content/docs/concepts/workflow/hooks/_index.md
+++ b/docs/content/docs/concepts/workflow/hooks/_index.md
@@ -14,8 +14,8 @@ On the root pipeline only, you can add hooks:
 * [scheduler]({{< relref "/docs/concepts/workflow/hooks/scheduler.md" >}})
 * [git repository webhooks]({{< relref "/docs/concepts/workflow/hooks/git-repo-webhook.md" >}})
 * [git repository poller]({{< relref "/docs/concepts/workflow/hooks/git-repo-poller.md" >}})
-* [kafka hook] ({{< relref "/docs/concepts/workflow/hooks/kafka-hook.md" >}})
-* [RabbitMQ hook] ({{< relref "/docs/concepts/workflow/hooks/rabbitmq-hook.md" >}})
+* [kafka hook]({{< relref "/docs/concepts/workflow/hooks/kafka-hook.md" >}})
+* [RabbitMQ hook]({{< relref "/docs/concepts/workflow/hooks/rabbitmq-hook.md" >}})
 
 There are two hooks on this pipeline, a repository webhook (GitHub here) and a webhook:
 
diff --git a/engine/api/api_routes.go b/engine/api/api_routes.go
index cea0fac14..e5bdc8989 100644
--- a/engine/api/api_routes.go
+++ b/engine/api/api_routes.go
@@ -290,7 +290,7 @@ func (api *API) InitRouter() {
 	r.Handle("/project/{key}/workflows/{permWorkflowName}/triggers/condition", Scope(sdk.AuthConsumerScopeRun), r.GET(api.getWorkflowTriggerConditionHandler))
 	r.Handle("/project/{key}/workflows/{permWorkflowName}/runs/{number}/nodes/{nodeRunID}/release", Scope(sdk.AuthConsumerScopeRunExecution), r.POSTEXECUTE(api.releaseApplicationWorkflowHandler, MaintenanceAware()))
 	r.Handle("/project/{key}/workflows/{permWorkflowNameAdvanced}/runs/{number}/hooks/{hookRunID}/callback", Scope(sdk.AuthConsumerScopeRun), r.POST(api.postWorkflowJobHookCallbackHandler, MaintenanceAware()))
-	r.Handle("/project/{key}/workflows/{permWorkflowName}/runs/{number}/hooks/{hookRunID}/details", Scope(sdk.AuthConsumerScopeRun), r.GET(api.getWorkflowJobHookDetailsHandler))
+	r.Handle("/project/{key}/workflows/{permWorkflowNameAdvanced}/runs/{number}/hooks/{hookRunID}/details", Scope(sdk.AuthConsumerScopeRun), r.GET(api.getWorkflowJobHookDetailsHandler))
 
 	// Environment
 	r.Handle("/project/{permProjectKey}/environment", Scope(sdk.AuthConsumerScopeProject), r.GET(api.getEnvironmentsHandler), r.POST(api.addEnvironmentHandler))
diff --git a/engine/api/application/dao.go b/engine/api/application/dao.go
index 5c7adc32d..e82d5a0a2 100644
--- a/engine/api/application/dao.go
+++ b/engine/api/application/dao.go
@@ -27,7 +27,7 @@ func (e dbApplication) Canonical() gorpmapper.CanonicalForms {
 }
 
 // LoadOptionFunc is a type for all options in LoadOptions
-type LoadOptionFunc *func(gorp.SqlExecutor, *sdk.Application) error
+type LoadOptionFunc *func(context.Context, gorp.SqlExecutor, *sdk.Application) error
 
 // LoadOptions provides all options on project loads functions
 var LoadOptions = struct {
@@ -137,14 +137,14 @@ func getWithClearVCSStrategyPassword(ctx context.Context, db gorp.SqlExecutor, k
 		return nil, err
 	}
 	if !isValid {
-		log.Error(context.Background(), "application.get> application %d data corrupted", dbApp.ID)
+		log.Error(ctx, "application.get> application %d data corrupted", dbApp.ID)
 		return nil, sdk.WithStack(sdk.ErrNotFound)
 	}
 	dbApp.ProjectKey = key
-	return unwrap(db, opts, &dbApp)
+	return unwrap(ctx, db, opts, &dbApp)
 }
 
-func unwrap(db gorp.SqlExecutor, opts []LoadOptionFunc, dbApp *dbApplication) (*sdk.Application, error) {
+func unwrap(ctx context.Context, db gorp.SqlExecutor, opts []LoadOptionFunc, dbApp *dbApplication) (*sdk.Application, error) {
 	app := &dbApp.Application
 	if app.ProjectKey == "" {
 		pkey, errP := db.SelectStr("SELECT projectkey FROM project WHERE id = $1", app.ProjectID)
@@ -155,7 +155,7 @@ func unwrap(db gorp.SqlExecutor, opts []LoadOptionFunc, dbApp *dbApplication) (*
 	}
 
 	for _, f := range opts {
-		if err := (*f)(db, app); err != nil && sdk.Cause(err) != sql.ErrNoRows {
+		if err := (*f)(ctx, db, app); err != nil && sdk.Cause(err) != sql.ErrNoRows {
 			return nil, sdk.WrapError(err, "application.unwrap")
 		}
 	}
@@ -283,7 +283,7 @@ func getAllWithClearVCS(ctx context.Context, db gorp.SqlExecutor, opts []LoadOpt
 			continue
 		}
 		a := &res[i]
-		app, err := unwrap(db, opts, a)
+		app, err := unwrap(ctx, db, opts, a)
 		if err != nil {
 			return nil, sdk.WrapError(err, "application.getAllWithClearVCS")
 		}
@@ -310,7 +310,7 @@ func getAll(ctx context.Context, db gorp.SqlExecutor, opts []LoadOptionFunc, que
 		}
 
 		a := &res[i]
-		app, err := unwrap(db, opts, a)
+		app, err := unwrap(ctx, db, opts, a)
 		if err != nil {
 			return nil, sdk.WrapError(err, "application.getAll")
 		}
diff --git a/engine/api/application/dao_application_integration.go b/engine/api/application/dao_application_integration.go
index cad4b3ca5..e4c2f163d 100644
--- a/engine/api/application/dao_application_integration.go
+++ b/engine/api/application/dao_application_integration.go
@@ -8,6 +8,7 @@ import (
 	"github.com/rockbears/log"
 
 	"github.com/ovh/cds/engine/api/database/gorpmapping"
+	"github.com/ovh/cds/engine/api/integration"
 	"github.com/ovh/cds/engine/gorpmapper"
 	"github.com/ovh/cds/sdk"
 )
@@ -46,7 +47,7 @@ func (e *dbApplicationDeploymentStrategy) IntegrationConfig() sdk.IntegrationCon
 }
 
 // LoadDeploymentStrategies loads the deployment strategies for an application
-func LoadDeploymentStrategies(db gorp.SqlExecutor, appID int64, withClearPassword bool) (map[string]sdk.IntegrationConfig, error) {
+func LoadDeploymentStrategies(ctx context.Context, db gorp.SqlExecutor, appID int64, withClearPassword bool) (map[string]sdk.IntegrationConfig, error) {
 	query := gorpmapping.NewQuery(`
     SELECT *
 	  FROM application_deployment_strategy
@@ -54,7 +55,7 @@ func LoadDeploymentStrategies(db gorp.SqlExecutor, appID int64, withClearPasswor
   `).Args(appID)
 
 	var res []dbApplicationDeploymentStrategy
-	if err := gorpmapping.GetAll(context.Background(), db, query, &res, gorpmapping.GetOptions.WithDecryption); err != nil {
+	if err := gorpmapping.GetAll(ctx, db, query, &res, gorpmapping.GetOptions.WithDecryption); err != nil {
 		return nil, sdk.WrapError(err, "unable to load deployment strategies")
 	}
 
@@ -65,7 +66,7 @@ func LoadDeploymentStrategies(db gorp.SqlExecutor, appID int64, withClearPasswor
 			return nil, err
 		}
 		if !isValid {
-			log.Error(context.Background(), "application.LoadDeploymentStrategies> application_deployment_strategy %d data corrupted", appID)
+			log.Error(ctx, "application.LoadDeploymentStrategies> application_deployment_strategy %d data corrupted", appID)
 			continue
 		}
 
@@ -85,12 +86,16 @@ func LoadDeploymentStrategies(db gorp.SqlExecutor, appID int64, withClearPasswor
 				newCfg[k] = v
 			}
 		}
-		// Sorry about that :(
-		projectIntegrationName, err := db.SelectStr("SELECT name FROM project_integration WHERE id = $1 ", r.ProjectIntegrationID)
+		projectIntegration, err := integration.LoadProjectIntegrationByID(ctx, db, r.ProjectIntegrationID)
 		if err != nil {
 			return nil, sdk.WrapError(err, "unable to find project integration name for ID=%d", r.ProjectIntegrationID)
 		}
-		deps[projectIntegrationName] = newCfg
+		for name, val := range projectIntegration.Model.AdditionalDefaultConfig {
+			if _, ok := newCfg[name]; !ok {
+				newCfg[name] = val
+			}
+		}
+		deps[projectIntegration.Name] = newCfg
 	}
 
 	return deps, nil
diff --git a/engine/api/application/dao_dependencies.go b/engine/api/application/dao_dependencies.go
index b2e436aa4..9a3dab8fe 100644
--- a/engine/api/application/dao_dependencies.go
+++ b/engine/api/application/dao_dependencies.go
@@ -1,6 +1,7 @@
 package application
 
 import (
+	"context"
 	"database/sql"
 
 	"github.com/go-gorp/gorp"
@@ -9,14 +10,14 @@ import (
 )
 
 var (
-	loadDefaultDependencies = func(db gorp.SqlExecutor, app *sdk.Application) error {
-		if err := loadVariables(db, app); err != nil && sdk.Cause(err) != sql.ErrNoRows {
+	loadDefaultDependencies = func(ctx context.Context, db gorp.SqlExecutor, app *sdk.Application) error {
+		if err := loadVariables(ctx, db, app); err != nil && sdk.Cause(err) != sql.ErrNoRows {
 			return sdk.WrapError(err, "application.loadDefaultDependencies %s", app.Name)
 		}
 		return nil
 	}
 
-	loadVariables = func(db gorp.SqlExecutor, app *sdk.Application) error {
+	loadVariables = func(ctx context.Context, db gorp.SqlExecutor, app *sdk.Application) error {
 		variables, err := LoadAllVariables(db, app.ID)
 		if err != nil && sdk.Cause(err) != sql.ErrNoRows {
 			return sdk.WrapError(err, "Unable to load variables for application %d", app.ID)
@@ -25,7 +26,7 @@ var (
 		return nil
 	}
 
-	loadVariablesWithClearPassword = func(db gorp.SqlExecutor, app *sdk.Application) error {
+	loadVariablesWithClearPassword = func(ctx context.Context, db gorp.SqlExecutor, app *sdk.Application) error {
 		variables, err := LoadAllVariablesWithDecrytion(db, app.ID)
 		if err != nil && sdk.Cause(err) != sql.ErrNoRows {
 			return sdk.WrapError(err, "Unable to load variables for application %d", app.ID)
@@ -34,7 +35,7 @@ var (
 		return nil
 	}
 
-	loadKeys = func(db gorp.SqlExecutor, app *sdk.Application) error {
+	loadKeys = func(ctx context.Context, db gorp.SqlExecutor, app *sdk.Application) error {
 		keys, err := LoadAllKeys(db, app.ID)
 		if err != nil {
 			return err
@@ -43,7 +44,7 @@ var (
 		return nil
 	}
 
-	loadClearKeys = func(db gorp.SqlExecutor, app *sdk.Application) error {
+	loadClearKeys = func(ctx context.Context, db gorp.SqlExecutor, app *sdk.Application) error {
 		keys, err := LoadAllKeysWithPrivateContent(db, app.ID)
 		if err != nil {
 			return err
@@ -52,16 +53,16 @@ var (
 		return nil
 	}
 
-	loadDeploymentStrategies = func(db gorp.SqlExecutor, app *sdk.Application) error {
+	loadDeploymentStrategies = func(ctx context.Context, db gorp.SqlExecutor, app *sdk.Application) error {
 		var err error
-		app.DeploymentStrategies, err = LoadDeploymentStrategies(db, app.ID, false)
+		app.DeploymentStrategies, err = LoadDeploymentStrategies(ctx, db, app.ID, false)
 		if err != nil && sdk.Cause(err) != sql.ErrNoRows {
 			return sdk.WrapError(err, "Unable to load deployment strategies for application %d", app.ID)
 		}
 		return nil
 	}
 
-	loadIcon = func(db gorp.SqlExecutor, app *sdk.Application) error {
+	loadIcon = func(ctx context.Context, db gorp.SqlExecutor, app *sdk.Application) error {
 		var err error
 		app.Icon, err = LoadIcon(db, app.ID)
 		if err != nil && sdk.Cause(err) != sql.ErrNoRows {
@@ -70,7 +71,7 @@ var (
 		return nil
 	}
 
-	loadVulnerabilities = func(db gorp.SqlExecutor, app *sdk.Application) error {
+	loadVulnerabilities = func(ctx context.Context, db gorp.SqlExecutor, app *sdk.Application) error {
 		var err error
 		app.Vulnerabilities, err = LoadVulnerabilities(db, app.ID)
 		if err != nil {
@@ -79,9 +80,9 @@ var (
 		return nil
 	}
 
-	loadDeploymentStrategiesWithClearPassword = func(db gorp.SqlExecutor, app *sdk.Application) error {
+	loadDeploymentStrategiesWithClearPassword = func(ctx context.Context, db gorp.SqlExecutor, app *sdk.Application) error {
 		var err error
-		app.DeploymentStrategies, err = LoadDeploymentStrategies(db, app.ID, true)
+		app.DeploymentStrategies, err = LoadDeploymentStrategies(ctx, db, app.ID, true)
 		if err != nil && sdk.Cause(err) != sql.ErrNoRows {
 			return sdk.WrapError(err, "Unable to load deployment strategies for application %d", app.ID)
 		}
diff --git a/engine/api/download.go b/engine/api/download.go
index 641bdd84f..33a5997df 100644
--- a/engine/api/download.go
+++ b/engine/api/download.go
@@ -24,11 +24,15 @@ func (api *API) downloadsHandler() service.Handler {
 func (api *API) downloadHandler() service.Handler {
 	return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
 		vars := mux.Vars(r)
-		name := vars["name"]
-		os := vars["os"]
-		arch := vars["arch"]
+		name := sdk.NoPath(vars["name"])
+		os := sdk.NoPath(vars["os"])
+		arch := sdk.NoPath(vars["arch"])
 		r.ParseForm() // nolint
-		variant := r.Form.Get("variant")
+		variant := sdk.NoPath(r.Form.Get("variant"))
+
+		if !sdk.IsInArray(variant, []string{"", "nokeychain", "keychain"}) {
+			return sdk.NewErrorFrom(sdk.ErrWrongRequest, "invalid variant: %s", variant)
+		}
 
 		if err := download.CheckBinary(ctx, api.getDownloadConf(), name, os, arch, variant); err != nil {
 			return err
diff --git a/engine/api/project/loader.go b/engine/api/project/loader.go
index 134a1f375..f12f79b54 100644
--- a/engine/api/project/loader.go
+++ b/engine/api/project/loader.go
@@ -101,7 +101,7 @@ func loadApplicationWithDeploymentStrategies(ctx context.Context, db gorp.SqlExe
 	}
 	for i := range proj.Applications {
 		a := &proj.Applications[i]
-		if err := (*application.LoadOptions.WithDeploymentStrategies)(db, a); err != nil {
+		if err := (*application.LoadOptions.WithDeploymentStrategies)(ctx, db, a); err != nil {
 			return sdk.WithStack(err)
 		}
 	}
@@ -134,7 +134,7 @@ func loadApplicationVariables(ctx context.Context, db gorp.SqlExecutor, proj *sd
 	}
 
 	for _, a := range proj.Applications {
-		if err := (*application.LoadOptions.WithVariables)(db, &a); err != nil {
+		if err := (*application.LoadOptions.WithVariables)(ctx, db, &a); err != nil {
 			return sdk.WithStack(err)
 		}
 	}
@@ -150,7 +150,7 @@ func loadApplicationKeys(ctx context.Context, db gorp.SqlExecutor, proj *sdk.Pro
 	}
 
 	for _, a := range proj.Applications {
-		if err := (*application.LoadOptions.WithKeys)(db, &a); err != nil {
+		if err := (*application.LoadOptions.WithKeys)(ctx, db, &a); err != nil {
 			return sdk.WithStack(err)
 		}
 	}
diff --git a/engine/api/router_middleware_auth_permission.go b/engine/api/router_middleware_auth_permission.go
index 3791a4760..5ea5c1fbd 100644
--- a/engine/api/router_middleware_auth_permission.go
+++ b/engine/api/router_middleware_auth_permission.go
@@ -367,6 +367,10 @@ func (api *API) checkGroupPermissions(ctx context.Context, w http.ResponseWriter
 			}
 		}
 	} else {
+		// Hatcheries started for "shared.infra" group are granted for group "shared.infra"
+		if isHatcheryShared(ctx) {
+			return nil
+		}
 		if !isGroupMember(ctx, g) && !isMaintainer(ctx) { // Only group member or CDS maintainer can get a group or its dependencies
 			return sdk.WithStack(sdk.ErrForbidden)
 		}
diff --git a/engine/api/user.go b/engine/api/user.go
index 5e9319374..88a218c56 100644
--- a/engine/api/user.go
+++ b/engine/api/user.go
@@ -83,7 +83,6 @@ func (api *API) putUserHandler() service.Handler {
 		}
 
 		newUser := *oldUser
-		newUser.Username = data.Username
 		newUser.Fullname = data.Fullname
 
 		// Only an admin can change the ring of a user
diff --git a/engine/api/workflow_hook.go b/engine/api/workflow_hook.go
index 186d6463a..300621b04 100644
--- a/engine/api/workflow_hook.go
+++ b/engine/api/workflow_hook.go
@@ -325,7 +325,7 @@ func (api *API) getWorkflowJobHookDetailsHandler() service.Handler {
 	return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
 		vars := mux.Vars(r)
 		key := vars["key"]
-		workflowName := vars["permWorkflowName"]
+		workflowName := vars["permWorkflowNameAdvanced"]
 		hookRunID := vars["hookRunID"]
 		number, errnum := requestVarInt(r, "number")
 		if errnum != nil {
diff --git a/engine/api/workflowtemplate/instance.go b/engine/api/workflowtemplate/instance.go
index 9b39bc8d6..22b31e712 100644
--- a/engine/api/workflowtemplate/instance.go
+++ b/engine/api/workflowtemplate/instance.go
@@ -141,7 +141,8 @@ func CheckAndExecuteTemplate(ctx context.Context, db *gorp.DbMap, store cache.St
 
 	// Check if the template can be used by the current consumer
 	var groupPermissionValid bool
-	if consumer.Maintainer() {
+	// Maintainer and the Hooks micro-service are allowed
+	if consumer.Maintainer() || (consumer.Service != nil && consumer.Service.Type == sdk.TypeHooks) {
 		groupPermissionValid = true
 	} else if grp.ID == group.SharedInfraGroup.ID {
 		groupPermissionValid = true
@@ -155,6 +156,7 @@ func CheckAndExecuteTemplate(ctx context.Context, db *gorp.DbMap, store cache.St
 		}
 	}
 	if !groupPermissionValid {
+		log.Error(ctx, "invalid permission for template %s/%s %s and consumer %s (%+v)", groupName, templateSlug, templateVersion, consumer.ID, consumer.GetGroupIDs())
 		return allMsgs, nil, sdk.NewErrorFrom(sdk.ErrWrongRequest, "could not find given workflow template")
 	}
 
diff --git a/engine/hatchery/openstack/types.go b/engine/hatchery/openstack/types.go
index 2d5a2039e..c9c1ebc95 100644
--- a/engine/hatchery/openstack/types.go
+++ b/engine/hatchery/openstack/types.go
@@ -25,7 +25,7 @@ type HatcheryConfiguration struct {
 	User string `mapstructure:"user" toml:"user" default:"" commented:"false" comment:"Openstack User" json:"user"`
 
 	// Address  openstack-auth-endpoint
-	Address string `mapstructure:"address" toml:"address" default:"https://auth.cloud.ovh.net/v2.0" commented:"false" comment:"Opentack Auth Endpoint" json:"address"`
+	Address string `mapstructure:"address" toml:"address" default:"https://auth.cloud.ovh.net/v3.0" commented:"false" comment:"Opentack Auth Endpoint" json:"address"`
 
 	// Password openstack-password
 	Password string `mapstructure:"password" toml:"password" default:"" commented:"false" comment:"Openstack Password" json:"-"`
diff --git a/sdk/common.go b/sdk/common.go
index 588cfa5c4..0b3485c18 100644
--- a/sdk/common.go
+++ b/sdk/common.go
@@ -13,6 +13,7 @@ import (
 	"io"
 	"os"
 	"path"
+	"path/filepath"
 	"reflect"
 	"regexp"
 	"runtime"
@@ -338,3 +339,33 @@ type KeyValues struct {
 	Key    string
 	Values []string
 }
+
+func CleanPath(path string) string {
+	// Deal with empty strings nicely.
+	if path == "" {
+		return ""
+	}
+
+	// Ensure that all paths are cleaned (especially problematic ones like
+	// "/../../../../../" which can cause lots of issues).
+	path = filepath.Clean(path)
+
+	// If the path isn't absolute, we need to do more processing to fix paths
+	// such as "../../../../<etc>/some/path". We also shouldn't convert absolute
+	// paths to relative ones.
+	if !filepath.IsAbs(path) {
+		path = filepath.Clean(string(os.PathSeparator) + path)
+		// This can't fail, as (by definition) all paths are relative to root.
+		path, _ = filepath.Rel(string(os.PathSeparator), path)
+	}
+
+	// Clean the path again for good measure.
+	return filepath.Clean(path)
+}
+
+func NoPath(path string) string {
+	if path == "" {
+		return ""
+	}
+	return filepath.Base(CleanPath(path))
+}
diff --git a/sdk/download.go b/sdk/download.go
index aa976035c..58bf183fc 100644
--- a/sdk/download.go
+++ b/sdk/download.go
@@ -124,12 +124,12 @@ func BinaryFilename(name, os, arch, variant string) string {
 	if os == "windows" {
 		suffix = ".exe"
 	}
-	return fmt.Sprintf("%s%s-%s-%s%s%s", prefix, name, os, getArchName(arch), variant, suffix)
+	return NoPath(fmt.Sprintf("%s%s-%s-%s%s%s", prefix, name, os, getArchName(arch), variant, suffix))
 }
 
 // IsDownloadedBinary returns true if the binary is already downloaded, false otherwise
 func IsDownloadedBinary(directoriesDownload, filename string) bool {
-	if _, err := os.Stat(path.Join(directoriesDownload, filename)); err == nil {
+	if _, err := os.Stat(CleanPath(path.Join(directoriesDownload, filename))); err == nil {
 		return true
 	}
 	return false
diff --git a/sdk/luascript/luascript.go b/sdk/luascript/luascript.go
index 043f0df35..a51d91c4d 100644
--- a/sdk/luascript/luascript.go
+++ b/sdk/luascript/luascript.go
@@ -56,7 +56,7 @@ func NewCheck() (*Check, error) {
 	state.PreloadModule("json", luajson.Loader)
 
 	// Sandboxing lua engine
-	if err := state.DoString("coroutine=nil;debug=nil;io=nil;open=nil;os.rename=nil;os.remove=nil;os.exit=nil;os.clock=nil;os.execute=nil;os.getenv=nil;os.setlocale=nil;os.tmpname=nil"); err != nil {
+	if err := state.DoString("coroutine=nil;debug=nil;io=nil;open=nil;dofile=nil;loadfile=nil;getfenv=nil;print=nil;pcall=nil;os.rename=nil;os.remove=nil;os.exit=nil;os.clock=nil;os.execute=nil;os.getenv=nil;os.setlocale=nil;os.tmpname=nil"); err != nil {
 		return nil, err
 	}
 
diff --git a/ui/angular.json b/ui/angular.json
index c54194fe7..e4896a8f1 100644
--- a/ui/angular.json
+++ b/ui/angular.json
@@ -26,13 +26,14 @@
                 "glob": "*",
                 "input": "node_modules/@ant-design/icons-angular/src/inline-svg/",
                 "output": "/assets/"
-              }, {
-                "glob": "ng-zorro-antd.dark.min.css",
-                "input": "./node_modules/ng-zorro-antd",
-                "output": "/assets/"
               }
             ],
             "styles": [
+              {
+                "input": "src/ngzorro.dark.less",
+                "bundleName": "ngzorro.dark",
+                "inject": false
+              },
               "node_modules/ng-zorro-antd/ng-zorro-antd.min.css",
               "node_modules/dragula/dist/dragula.css",
               "node_modules/codemirror/lib/codemirror.css",
diff --git a/ui/src/app/app.component.html b/ui/src/app/app.component.html
index f7f43e614..ea12a4192 100644
--- a/ui/src/app/app.component.html
+++ b/ui/src/app/app.component.html
@@ -38,12 +38,17 @@
     </ng-container>
 </div>
 <ng-template #templateRootToast let-data="data">
-    <div class="toastTitle">{{data.title}}</div>
+    <div class="toastTitle">
+        <i nz-icon
+           nzType="exclamation-circle"
+        class="ant-notification-notice-icon-error"></i>
+        {{data.title}}
+    </div>
     <span *ngIf="data.from">{{data.from}}<br /></span>
     <div *ngIf="data?.help && data?.help?.length > 0">
         <markdown [data]="data.help"></markdown>
     </div>
-    <span *ngIf="data.status === 500">
+    <span>
         {{'toast_http_error_request_id' | translate}}
         <pre style="font-size: 10px">{{data.request_id}}</pre>
     </span>
diff --git a/ui/src/app/service/theme/theme.store.ts b/ui/src/app/service/theme/theme.store.ts
index 335a40631..55de7dad3 100644
--- a/ui/src/app/service/theme/theme.store.ts
+++ b/ui/src/app/service/theme/theme.store.ts
@@ -25,7 +25,7 @@ export class ThemeStore {
             style.type = 'text/css';
             style.rel = 'stylesheet';
             style.id = 'dark-theme';
-            style.href = 'assets/ng-zorro-antd.dark.min.css';
+            style.href = 'ngzorro.dark.css';
             document.body.appendChild(style);
         } else {
             const dom = document.getElementById('dark-theme');
diff --git a/ui/src/app/shared/action/action.component.spec.ts b/ui/src/app/shared/action/action.component.spec.ts
index 7958082b6..0c5f5c166 100644
--- a/ui/src/app/shared/action/action.component.spec.ts
+++ b/ui/src/app/shared/action/action.component.spec.ts
@@ -1,7 +1,7 @@
 import { APP_BASE_HREF } from '@angular/common';
 import { HttpRequest } from '@angular/common/http';
 import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
-import { fakeAsync, TestBed, tick } from '@angular/core/testing';
+import { fakeAsync, flush, TestBed, tick } from '@angular/core/testing';
 import { RouterTestingModule } from '@angular/router/testing';
 import { TranslateLoader, TranslateModule, TranslateParser, TranslateService } from '@ngx-translate/core';
 import { Action } from 'app/model/action.model';
@@ -24,8 +24,14 @@ import { SharedService } from '../shared.service';
 import { ActionComponent } from './action.component';
 import { ActionEvent } from './action.event.model';
 import { StepEvent } from './step/step.event';
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+import { By } from '@angular/platform-browser';
+import { NzDropDownDirective } from 'ng-zorro-antd/dropdown';
+import { OverlayContainer } from '@angular/cdk/overlay';
+import { Inject } from '@angular/core';
 
 describe('CDS: Action Component', () => {
+
     beforeEach(async () => {
         await TestBed.configureTestingModule({
             declarations: [],
@@ -46,6 +52,7 @@ describe('CDS: Action Component', () => {
                 AuthenticationService
             ],
             imports: [
+                BrowserAnimationsModule,
                 RouterTestingModule.withRoutes([]),
                 SharedModule,
                 TranslateModule.forRoot(),
@@ -68,9 +75,6 @@ describe('CDS: Action Component', () => {
         fixture.componentInstance.edit = true;
         fixture.componentInstance.project = <Project>{ key: 'key' };
 
-        fixture.detectChanges();
-        tick(50);
-
         let r: Requirement = new Requirement('binary');
         r.name = 'npm';
         r.value = 'npm';
@@ -105,9 +109,6 @@ describe('CDS: Action Component', () => {
         fixture.componentInstance.edit = true;
         fixture.componentInstance.project = <Project>{ key: 'key' };
 
-        fixture.detectChanges();
-        tick(50);
-
         let p: Parameter = new Parameter();
         p.name = 'gitUrl';
         p.type = 'string';
@@ -125,43 +126,6 @@ describe('CDS: Action Component', () => {
         expect(fixture.componentInstance.editableAction.parameters.length).toBe(0, 'Action must have 0 parameter');
     }));
 
-    it('should send delete action event', fakeAsync(() => {
-        // Create component
-        let fixture = TestBed.createComponent(ActionComponent);
-        let component = fixture.debugElement.componentInstance;
-        expect(component).toBeTruthy();
-
-        let action: Action = new Action();
-        action.name = 'FooAction';
-        action.requirements = new Array<Requirement>();
-        action.id = 1;
-        fixture.componentInstance.editableAction = action;
-        fixture.componentInstance.project = <Project>{ key: 'key' };
-
-        fixture.componentInstance._cd.detectChanges();
-        tick(50);
-
-        // readonly , no button
-        expect(fixture.debugElement.nativeElement.querySelector('.ui.red.button')).toBeFalsy();
-        expect(fixture.debugElement.nativeElement.querySelector('button[name="updatebtn"]')).toBeFalsy();
-
-        fixture.componentInstance.edit = true;
-
-        fixture.componentInstance._cd.detectChanges();
-        tick(50);
-
-        let compiled = fixture.debugElement.nativeElement;
-
-        spyOn(fixture.componentInstance.actionEvent, 'emit');
-        compiled.querySelector('.ui.red.button').click();
-        fixture.componentInstance._cd.detectChanges();
-        tick(50);
-        compiled.querySelector('.ui.red.button.active').click();
-
-        expect(compiled.querySelector('button[name="updatebtn"]')).toBeTruthy();
-        expect(fixture.componentInstance.actionEvent.emit).toHaveBeenCalledWith(new ActionEvent('delete', action));
-    }));
-
     it('should send insert action event', fakeAsync(() => {
         // Create component
         let fixture = TestBed.createComponent(ActionComponent);
@@ -176,7 +140,7 @@ describe('CDS: Action Component', () => {
         fixture.componentInstance.project = <Project>{ key: 'key' };
 
         fixture.detectChanges();
-        tick(50);
+        tick(100);
 
         let compiled = fixture.debugElement.nativeElement;
 
@@ -190,10 +154,12 @@ describe('CDS: Action Component', () => {
         expect(compiled.querySelector('button[name="deletebtn"]')).toBeFalsy();
         expect(compiled.querySelector('button[name="updatebtn"]')).toBeFalsy();
 
-        let btn = compiled.querySelector('button[name="addbtn"]');
+        let btn = compiled.querySelector('button[name="updateBtn"]');
         btn.click();
 
-        expect(fixture.componentInstance.actionEvent.emit).toHaveBeenCalledWith(new ActionEvent('insert', action));
+        expect(fixture.componentInstance.actionEvent.emit).toHaveBeenCalledWith(new ActionEvent('update', action));
+
+        flush();
     }));
 
     it('should add and then remove a step', fakeAsync(() => {
@@ -210,14 +176,11 @@ describe('CDS: Action Component', () => {
         let component = fixture.debugElement.componentInstance;
         expect(component).toBeTruthy();
 
-        // TODO
-        // http.expectOne(((req: HttpRequest<any>) => {
-        //    return req.url === '/requirement/types';
-        // })).flush(actionMock);
 
         fixture.componentInstance.ngOnInit();
         http.expectOne(((req: HttpRequest<any>) => req.url === '/project/key/action')).flush(actionMock);
 
+
         let action: Action = <Action>{
             name: 'FooAction',
             requirements: new Array<Requirement>()
@@ -225,9 +188,6 @@ describe('CDS: Action Component', () => {
         fixture.componentInstance.editableAction = action;
         fixture.componentInstance.edit = true;
 
-        fixture.detectChanges();
-        tick(50);
-
         let step = <Action>{
             always_executed: false,
             name: 'action1'
diff --git a/ui/src/app/shared/action/action.html b/ui/src/app/shared/action/action.html
index 557557301..7b1546056 100644
--- a/ui/src/app/shared/action/action.html
+++ b/ui/src/app/shared/action/action.html
@@ -110,55 +110,51 @@
     <div class="ui info message" *ngIf="steps.length === 0">
         {{ 'step_nonfinal_no' | translate }}
     </div>
-
-    <div class="ui bottomBtnsWrapper segment"
-        *ngIf="edit && !(pipeline && pipeline.previewMode)">
-        <div class="ui grid">
-            <div [ngClass]="editableAction.showAddStep ? 'sixteen wide column' : ( (editableAction.id || pipeline?.from_repository) ? 'seven wide column' : 'eight wide column')"
-                *ngIf="publicActions.length > 0">
-                <app-action-step-form [actions]="publicActions" (onEvent)="stepManagement($event)">
-                </app-action-step-form>
-            </div>
-
-            <ng-container *ngIf="(editableAction.id || pipeline?.from_repository) && !editableAction.showAddStep">
-                <div [ngClass]="publicActions.length > 0 ? 'seven wide column' : 'fourteen wide column'">
-                    <button class="ui fluid green button" name="updatebtn"
-                            (click)="sendActionEvent('update')"
-                        [disabled]="!editableAction.hasChanged || editableAction.loading"
-                        [class.loading]="editableAction.loading">
-                        <i class="save icon"></i>
-                        <span *ngIf="!editPipelineMode">{{ 'btn_save' | translate }}</span>
-                        <span *ngIf="editPipelineMode">{{ 'btn_apply' | translate }}</span>
-                    </button>
-                </div>
-                <div class="two wide column">
-                    <div class="ui grey pointing dropdown button bottom right icon buttonOptions" suiDropdown
-                        autoClose="outsideClick">
-                        <i class="ellipsis vertical icon"></i>
-                        <div class="menu" suiDropdownMenu>
-                            <div class="item">
-                                <button class="ui fluid blue button"
-                                    (click)="$event.stopPropagation(); initActionFromJob()">
-                                    <i class="share icon"></i>
-                                    {{ 'action_btn_init_from_job' | translate }}
-                                </button>
-                            </div>
-                            <div class="item">
-                                <app-delete-button
-                                    (event)="sendActionEvent('delete')" [loading]="editableAction.loading"
-                                    [class]="'fluid'" title="job_delete"></app-delete-button>
-                            </div>
-                        </div>
-                    </div>
-                </div>
-            </ng-container>
-
-            <div class="eight wide column" *ngIf="!editableAction.id && !pipeline?.from_repository && !editableAction.showAddStep">
-                <button class="ui green button" name="addbtn" (click)="sendActionEvent('insert')"
-                    [disabled]="editableAction.loading" [class.loading]="editableAction.loading">
-                    <i class="save icon"></i>{{ 'btn_create' | translate }}
-                </button>
-            </div>
+</form>
+<nz-layout class="bottomBtnsWrapper"
+     *ngIf="edit && !(pipeline && pipeline.previewMode)">
+    <div nz-row *ngIf="publicActions.length > 0">
+        <div nz-col nzSpan="24">
+            <app-action-step-form [actions]="publicActions" (onEvent)="stepManagement($event)"></app-action-step-form>
         </div>
     </div>
-</form>
+    <div nz-row *ngIf="!editableAction.showAddStep">
+        <div nz-col nzSpan="20">
+            <button name="updateBtn"
+                    nzType="primary"
+                    nz-button
+                    (click)="sendActionEvent('update')"
+                    [disabled]="!editableAction.hasChanged || editableAction.loading"
+                    [nzLoading]="editableAction.loading" nzBlock>
+                <i nz-icon nzType="save" nzTheme="outline"></i>
+                <span *ngIf="!editPipelineMode">Save</span>
+                <span *ngIf="editPipelineMode">Apply</span>
+            </button>
+        </div>
+        <div nz-col nzSpan="4">
+            <button nz-button nz-dropdown class="menuBtn" [nzDropdownMenu]="menu" nzPlacement="topRight" nzTrigger="click" name="menuBtn">
+                <i nz-icon nzType="ellipsis" nzTheme="outline"></i>
+            </button>
+            <nz-dropdown-menu #menu="nzDropdownMenu" >
+                <ul nz-menu>
+                    <li nz-menu-item>
+                        <button nz-button nzBlock nzType="primary"
+                                (click)="$event.stopPropagation(); initActionFromJob()">
+                            <i nz-icon nzType="share-alt" nzTheme="outline"></i>Init a new action from job
+                        </button>
+                    </li>
+                    <li nz-menu-item>
+                        <button nz-popconfirm nzDanger nzBlock
+                                name="deleteBtn"
+                                nzPopconfirmTitle="Are you sure delete this action?"
+                                (nzOnConfirm)="sendActionEvent('delete')"
+                                nzPopconfirmPlacement="topLeft"
+                                nz-button>
+                            <i nz-icon nzType="rest" nzTheme="outline"></i>Delete
+                        </button>
+                    </li>
+                </ul>
+            </nz-dropdown-menu>
+        </div>
+    </div>
+</nz-layout>
diff --git a/ui/src/app/shared/action/action.scss b/ui/src/app/shared/action/action.scss
index a34175abb..798860db5 100644
--- a/ui/src/app/shared/action/action.scss
+++ b/ui/src/app/shared/action/action.scss
@@ -56,20 +56,20 @@
 }
 
 .bottomBtnsWrapper {
-  background-color: #f9f9f9;
   position: fixed;
   bottom: 0;
   right: 0;
   width: 430px;
-  padding-top: 20px;
-  padding-bottom: 20px;
-  padding-left: 21px;
-  padding-right: 21px;
-  .column {
-    padding-top: 5px;
-    padding-bottom: 5px;
-    padding-left: 7px;
-    padding-right: 7px;
+  padding: 10px 10px 10px 10px;
+  .ant-row {
+    padding-bottom: 4px;
+    padding-top: 4px;
+  }
+  .saveBtn {
+    width: 100%;
+  }
+  .menuBtn {
+    width: 100%;
   }
 }
 
diff --git a/ui/src/app/shared/action/step/form/step.form.component.spec.ts b/ui/src/app/shared/action/step/form/step.form.component.spec.ts
index 4df99e090..0d574f9d4 100644
--- a/ui/src/app/shared/action/step/form/step.form.component.spec.ts
+++ b/ui/src/app/shared/action/step/form/step.form.component.spec.ts
@@ -1,7 +1,7 @@
 /* eslint-disable @typescript-eslint/no-unused-vars */
 
 import { APP_BASE_HREF } from '@angular/common';
-import { fakeAsync, TestBed, tick } from '@angular/core/testing';
+import { fakeAsync, flush, TestBed, tick } from '@angular/core/testing';
 import { RouterTestingModule } from '@angular/router/testing';
 import { TranslateLoader, TranslateModule, TranslateParser, TranslateService } from '@ngx-translate/core';
 import { Action } from '../../../../model/action.model';
@@ -10,6 +10,8 @@ import { SharedModule } from '../../../shared.module';
 import { SharedService } from '../../../shared.service';
 import { StepEvent } from '../step.event';
 import { ActionStepFormComponent } from './step.form.component';
+import { BrowserModule } from '@angular/platform-browser';
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
 
 describe('CDS: Step Form Component', () => {
 
@@ -26,6 +28,8 @@ describe('CDS: Step Form Component', () => {
                 { provide: APP_BASE_HREF, useValue: '/' }
             ],
             imports: [
+                BrowserModule,
+                BrowserAnimationsModule,
                 RouterTestingModule.withRoutes([]),
                 TranslateModule.forRoot(),
                 SharedModule
@@ -37,42 +41,41 @@ describe('CDS: Step Form Component', () => {
         // Create component
         let fixture = TestBed.createComponent(ActionStepFormComponent);
         let component = fixture.debugElement.componentInstance;
-        expect(component).toBeTruthy();
+        fixture.componentInstance.actions = [];
 
+        let scriptAction = new Action();
+        scriptAction.name = 'Script';
+        fixture.componentInstance.actions.push(scriptAction);
+        fixture.componentInstance.selected = scriptAction;
 
-        let step = new Action();
-        step.always_executed = true;
-        fixture.componentInstance.selected = step;
-        fixture.componentInstance.actions = new Array<Action>();
-        let a = new Action();
-        a.name = 'Script';
-        fixture.componentInstance.actions.push(a);
+        expect(component).toBeTruthy();
 
         fixture.detectChanges();
-        tick(50);
 
         spyOn(fixture.componentInstance.onEvent, 'emit');
 
 
         let compiled = fixture.debugElement.nativeElement;
-        expect(compiled.querySelector('.ui.fluid.blue.button')).toBeTruthy('Add button must be displayed');
-        compiled.querySelector('.ui.fluid.blue.button').click();
-
-        fixture.detectChanges();
-        tick(50);
+        let button = compiled.querySelector('button[name="addStepMenuBtn"]');
+        console.log(button);
+        expect(button).toBeTruthy('Add step menu btn must be displayed');
+        button.click();
 
         expect(fixture.componentInstance.onEvent.emit).toHaveBeenCalledWith(
             new StepEvent('expend', null)
         );
 
-        expect(compiled.querySelector('.ui.green.button')).toBeTruthy('Add green button must be displayed');
-        compiled.querySelector('.ui.green.button').click();
+        fixture.changeDetectorRef.markForCheck();
+        tick(200);
 
-        fixture.detectChanges();
-        tick(50);
+        console.log(compiled);
+        expect(compiled.querySelector('button[name="addStepBtn"]')).toBeTruthy('Add step button must be displayed');
+        compiled.querySelector('button[name="addStepBtn"]').click();
 
         expect(fixture.componentInstance.onEvent.emit).toHaveBeenCalledWith(
             new StepEvent('add', fixture.componentInstance.selected)
         );
+
+        flush();
     }));
 });
diff --git a/ui/src/app/shared/action/step/form/step.form.component.ts b/ui/src/app/shared/action/step/form/step.form.component.ts
index 366cba10e..c625e0ae2 100644
--- a/ui/src/app/shared/action/step/form/step.form.component.ts
+++ b/ui/src/app/shared/action/step/form/step.form.component.ts
@@ -1,4 +1,12 @@
-import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
+import {
+    ChangeDetectionStrategy,
+    ChangeDetectorRef,
+    Component,
+    EventEmitter,
+    Input,
+    OnInit,
+    Output
+} from '@angular/core';
 import { Action } from 'app/model/action.model';
 import { StepEvent } from 'app/shared/action/step/step.event';
 
@@ -13,14 +21,16 @@ export class ActionStepFormComponent implements OnInit {
     @Output() onEvent = new EventEmitter<StepEvent>();
 
     expended: boolean;
-    selectedID: number;
     selected: Action;
 
+    constructor(private _cd: ChangeDetectorRef) {
+    }
+
     ngOnInit(): void {
         let script = this.actions.find(a => a.name === 'Script' && a.type === 'Builtin');
         if (script) {
-            this.selectedID = script.id;
             this.selected = script;
+            this._cd.markForCheck();
         }
     }
 
diff --git a/ui/src/app/shared/action/step/form/step.form.html b/ui/src/app/shared/action/step/form/step.form.html
index 387ccb93f..ff5bc865f 100644
--- a/ui/src/app/shared/action/step/form/step.form.html
+++ b/ui/src/app/shared/action/step/form/step.form.html
@@ -1,34 +1,36 @@
-<div class="ui form">
-    <div class="field" *ngIf="expended">
-        <sm-card class="ui card" *ngIf="selected">
-            <card-title><b>{{selected.group ? selected.group.name + '/' + selected.name : selected?.name}}</b></card-title>
-            <card-subtitle>{{selected?.type}}</card-subtitle>
-            <card-content>
-                <markdown [data]="selected?.description"></markdown>
-            </card-content>
-        </sm-card>
-        <sm-select name="step" class="search" [options]="{'fullTextSearch': true}" [model]="selectedID" (onChange)="selectAction($event)">
-            <option *ngFor="let a of actions" [value]="a.id" [disabled]="a.deprecated">
-                {{a.group ? a.group.name + '/' + a.name : a.name}}
-                <span class="right floated" *ngIf="a.deprecated">
-                    <i class="warning orange sign icon"></i>
-                    {{'common_deprecated' | translate}}
-                </span>
-            </option>
-        </sm-select>
-    </div>
-    <div class="field" *ngIf="expended">
-        <div class="ui buttons fluid">
-            <button class="ui green button" (click)="clickAddStep()" [disabled]="!selected">
-                <i class="plus icon"></i>{{ 'job_add_step' | translate }}
-            </button>
-            <div class="or"></div>
-            <button class="ui grey button" (click)="clickCancel()">{{'btn_cancel' | translate}}</button>
-        </div>
-    </div>
-    <div class="field" *ngIf="!expended">
-        <button class="ui fluid blue button" (click)="showActions()">
-            <i class="plus icon"></i>{{ 'job_add_step' | translate }}
+<div>
+    <ng-container *ngIf="expended">
+        <form nz-form>
+            <nz-form-item *ngIf="expended">
+                <nz-form-control nzSpan="24">
+                    <nz-card *ngIf="selected">
+                        <nz-card-meta [nzTitle]="selected.group ? selected.group.name + '/' + selected.name : selected?.name"
+                                      [nzDescription]="selected?.type"></nz-card-meta>
+                        <markdown [data]="selected?.description"></markdown>
+                    </nz-card>
+                </nz-form-control>
+
+            </nz-form-item>
+            <nz-form-item *ngIf="selected && actions.length > 0">
+                <nz-select [(ngModel)]="selected" name="action" nzShowSearch>
+                    <ng-container *ngFor="let a of actions">
+                        <nz-option nzCustomContent [nzValue]="a" [nzLabel]="a.name" [nzDisabled]="a.deprecated">
+                            {{a.group ? a.group.name + '/' + a.name : a.name}}
+                            <span class="right floated" *ngIf="a.deprecated">
+                        <i class="warning orange sign icon"></i>
+                                {{'common_deprecated' | translate}}
+                        </span>
+                        </nz-option>
+                    </ng-container>
+                </nz-select>
+            </nz-form-item>
+            <button (click)="clickCancel()" class="validButton" nz-button>Cancel</button>
+            <button (click)="clickAddStep()" class="validButton ml10" nzType="primary" [disabled]="!selected" name="addStepBtn" nz-button>Add step</button>
+        </form>
+    </ng-container>
+    <ng-container *ngIf="!expended">
+        <button nz-button nzType="default" (click)="showActions()" name="addStepMenuBtn" nzBlock>
+            <i nz-icon nzType="plus-square" nzTheme="outline"></i>Add Step
         </button>
-    </div>
-</div>
\ No newline at end of file
+    </ng-container>
+</div>
diff --git a/ui/src/app/shared/action/step/form/step.form.scss b/ui/src/app/shared/action/step/form/step.form.scss
index 56403c0eb..0213d5ea1 100644
--- a/ui/src/app/shared/action/step/form/step.form.scss
+++ b/ui/src/app/shared/action/step/form/step.form.scss
@@ -1,15 +1,8 @@
 @import '../../../../../common';
-.ui.card {
-  width: 400px;
-  max-width: 400px;
+.validButton {
+  width: 200px;
 }
 
-.addbtn {
-  margin-bottom: 1em;
+.ml10 {
+  margin-left: 10px;
 }
-
-.ui.dropdown {
-  &.text:not(.icon) {
-    width: 100%;
-  }
-}
\ No newline at end of file
diff --git a/ui/src/app/shared/shared.module.ts b/ui/src/app/shared/shared.module.ts
index 59c2d7e9c..4c0f33b1d 100644
--- a/ui/src/app/shared/shared.module.ts
+++ b/ui/src/app/shared/shared.module.ts
@@ -116,16 +116,40 @@ import { ZoneComponent } from './zone/zone.component';
 import { NgxAutoScrollDirective } from 'app/shared/directives/auto-scroll.directive';
 import { NZ_CONFIG, NzConfig } from 'ng-zorro-antd/core/config';
 import { NzNotificationModule } from 'ng-zorro-antd/notification';
+import { NzMenuModule } from 'ng-zorro-antd/menu';
+import { NzButtonModule } from 'ng-zorro-antd/button';
+import { NzCardModule } from 'ng-zorro-antd/card';
+import { NzSelectModule } from 'ng-zorro-antd/select';
+import { NzFormModule } from 'ng-zorro-antd/form';
+import { NzLayoutModule } from 'ng-zorro-antd/layout';
+import { NzGridModule } from 'ng-zorro-antd/grid';
+import { NzPopconfirmModule } from 'ng-zorro-antd/popconfirm';
+import { en_US, NZ_I18N } from 'ng-zorro-antd/i18n';
+import { NzIconModule } from 'ng-zorro-antd/icon';
+import { IconDefinition } from '@ant-design/icons-angular';
+import { PlusSquareOutline, RestOutline, SaveOutline, ShareAltOutline } from '@ant-design/icons-angular/icons'
+import { NzDropDownModule } from 'ng-zorro-antd/dropdown';
 
 const ngZorroConfig: NzConfig = {
-    notification: {nzPauseOnHover: true, nzPlacement: "topRight"}
+    notification: {nzPauseOnHover: true, nzPlacement: "topRight"},
+    icon: {nzTheme: "outline"}
 };
 
-
+const icons: IconDefinition[] = [ PlusSquareOutline, RestOutline, SaveOutline, ShareAltOutline ];
 
 @NgModule({
     imports: [
+        NzButtonModule,
+        NzCardModule,
+        NzDropDownModule,
+        NzFormModule,
+        NzGridModule,
+        NzIconModule.forRoot(icons),
+        NzLayoutModule,
         NzNotificationModule,
+        NzMenuModule,
+        NzPopconfirmModule,
+        NzSelectModule,
         CommonModule,
         ClipboardModule,
         NgxSemanticModule,
@@ -249,6 +273,7 @@ const ngZorroConfig: NzConfig = {
         NoAuthenticationGuard,
         FeatureGuard,
         { provide: NZ_CONFIG, useValue: ngZorroConfig },
+        { provide: NZ_I18N, useValue: en_US }
     ],
     schemas: [
         CUSTOM_ELEMENTS_SCHEMA
@@ -296,7 +321,17 @@ const ngZorroConfig: NzConfig = {
         NgForNumber,
         NgxSemanticModule,
         NgxAutoScrollDirective,
+        NzButtonModule,
+        NzCardModule,
+        NzDropDownModule,
+        NzFormModule,
+        NzGridModule,
+        NzIconModule,
+        NzLayoutModule,
+        NzMenuModule,
         NzNotificationModule,
+        NzPopconfirmModule,
+        NzSelectModule,
         PaginationComponent,
         ParameterFormComponent,
         ParameterListComponent,
diff --git a/ui/src/app/shared/tabs/tab.component.ts b/ui/src/app/shared/tabs/tab.component.ts
index 74771010a..f8a8ee963 100644
--- a/ui/src/app/shared/tabs/tab.component.ts
+++ b/ui/src/app/shared/tabs/tab.component.ts
@@ -3,10 +3,8 @@ import {
     ChangeDetectionStrategy,
     ChangeDetectorRef,
     Component,
-    EventEmitter,
     Input,
     OnChanges,
-    Output,
     ViewChild,
     ViewContainerRef
 } from '@angular/core';
@@ -21,10 +19,7 @@ import { Tab } from './tabs.component';
 @AutoUnsubscribe()
 export class TabComponent implements AfterViewInit, OnChanges {
     @ViewChild('templateSibling', { read: ViewContainerRef }) templateSibling: ViewContainerRef;
-
     @Input() tab: Tab;
-    @Input() active: boolean;
-    @Output() onSelect = new EventEmitter();
 
     constructor(
         private _cd: ChangeDetectorRef
@@ -45,8 +40,4 @@ export class TabComponent implements AfterViewInit, OnChanges {
             this._cd.markForCheck();
         }
     }
-
-    click() {
-        this.onSelect.emit();
-    }
 }
diff --git a/ui/src/app/shared/tabs/tab.html b/ui/src/app/shared/tabs/tab.html
index 0e9dde32c..8549ade94 100644
--- a/ui/src/app/shared/tabs/tab.html
+++ b/ui/src/app/shared/tabs/tab.html
@@ -1,7 +1,9 @@
-<a sm-item [class.active]="active" (click)="click()">
-    <ng-container *ngIf="!tab.template">
-        <i *ngIf="tab.icon" [ngClass]="[tab.icon, 'icon']"></i>
-        {{tab.translate | translate:tab.translate_args}}
+<ng-container *ngIf="!tab.template">
+    <i *ngIf="tab.icon" [ngClass]="[tab.icon, 'icon']"></i>
+    {{tab.title}}
+    <ng-container *ngIf="tab.warningText || tab.warningTemplate">
+        <i class="exclamation triangle orange icon" suiPopup [popupTemplate]="tab.warningTemplate" [popupText]="tab.warningText"></i>
     </ng-container>
-    <span #templateSibling></span>
-</a>
+</ng-container>
+<span #templateSibling></span>
+
diff --git a/ui/src/app/shared/tabs/tabs.component.ts b/ui/src/app/shared/tabs/tabs.component.ts
index b16891e1c..3a6eded71 100644
--- a/ui/src/app/shared/tabs/tabs.component.ts
+++ b/ui/src/app/shared/tabs/tabs.component.ts
@@ -4,12 +4,14 @@ import { AutoUnsubscribe } from 'app/shared/decorator/autoUnsubscribe';
 import { Subscription } from 'rxjs/Subscription';
 
 export class Tab {
-    translate: string;
-    translate_args?: {};
+    title: string;
     icon: string;
     key: string;
     default: boolean;
     template: TemplateRef<any>;
+    warningText: string;
+    warningTemplate: TemplateRef<any>;
+    disabled: boolean;
 }
 
 @Component({
@@ -52,6 +54,9 @@ export class TabsComponent implements OnInit, OnChanges, OnDestroy {
     }
 
     clickSelect(tab: Tab) {
+        if (tab.disabled) {
+            return;
+        }
         if (!this.disableNavigation) {
             this._router.navigate([], {
                 relativeTo: this._route,
diff --git a/ui/src/app/shared/tabs/tabs.html b/ui/src/app/shared/tabs/tabs.html
index 39f4e750b..f56973c99 100644
--- a/ui/src/app/shared/tabs/tabs.html
+++ b/ui/src/app/shared/tabs/tabs.html
@@ -1,3 +1,7 @@
-<sm-menu class="pointing secondary stackable">
-    <app-tab *ngFor="let t of tabs" [tab]="t" [active]="selected.key === t.key" (onSelect)="clickSelect(t)"></app-tab>
-</sm-menu>
+<ul nz-menu nzMode="horizontal">
+    <ng-container *ngFor="let t of tabs">
+        <li nz-menu-item [nzSelected]="selected.key === t.key" (click)="clickSelect(t)" [nzDisabled]="t.disabled">
+            <app-tab [tab]="t"></app-tab>
+        </li>
+    </ng-container>
+</ul>
diff --git a/ui/src/app/shared/toast/ToastService.ts b/ui/src/app/shared/toast/ToastService.ts
index 8780b2a56..d7ac90070 100644
--- a/ui/src/app/shared/toast/ToastService.ts
+++ b/ui/src/app/shared/toast/ToastService.ts
@@ -48,7 +48,7 @@ export class ToastService {
         .subscribe(help => {
             this._toastQueue.next( {
                 nzPauseOnHover: true,
-                nzDuration: status < 500 ? 3000 : 0,
+                nzDuration: request_id != null && request_id !== '' ? 0: 3000,
                 nzData: {
                     status,
                     from,
diff --git a/ui/src/app/shared/workflow/modal/node-edit/node.edit.modal.html b/ui/src/app/shared/workflow/modal/node-edit/node.edit.modal.html
index dd99dc327..ec79b7243 100644
--- a/ui/src/app/shared/workflow/modal/node-edit/node.edit.modal.html
+++ b/ui/src/app/shared/workflow/modal/node-edit/node.edit.modal.html
@@ -33,7 +33,7 @@
                             </a>
                             <a class="item" [class.active]="selected === 'permissions'"
                                 (click)="changeView('permissions')">
-                                {{ 'common_permissions' | translate }}
+                                Permissions
                             </a>
                         </ng-container>
                         <ng-container *ngIf="hookSelected">
diff --git a/ui/src/app/views/application/show/admin/deployment/application.deployment.html b/ui/src/app/views/application/show/admin/deployment/application.deployment.html
index cff9fbeb4..4d04813e9 100644
--- a/ui/src/app/views/application/show/admin/deployment/application.deployment.html
+++ b/ui/src/app/views/application/show/admin/deployment/application.deployment.html
@@ -61,7 +61,7 @@
 
                                     <div class="six wide field">
                                         <label>
-                                            <b>{{k}}</b>
+                                            {{k}}
                                             <span *ngIf="application.deployment_strategies[pf][k].description" suiPopup
                                                 [popupText]="application.deployment_strategies[pf][k].description"
                                                 popupPlacement="right center">
diff --git a/ui/src/app/views/application/show/application.component.ts b/ui/src/app/views/application/show/application.component.ts
index 6950251fd..dd2dbf20b 100644
--- a/ui/src/app/views/application/show/application.component.ts
+++ b/ui/src/app/views/application/show/application.component.ts
@@ -6,7 +6,6 @@ import { Application } from 'app/model/application.model';
 import { Environment } from 'app/model/environment.model';
 import { Pipeline } from 'app/model/pipeline.model';
 import { Project } from 'app/model/project.model';
-import { AuthentifiedUser } from 'app/model/user.model';
 import { Workflow } from 'app/model/workflow.model';
 import { ApplicationStore } from 'app/service/application/application.store';
 import { AsCodeSaveModalComponent } from 'app/shared/ascode/save-modal/ascode.save-modal.component';
@@ -17,11 +16,11 @@ import { VariableEvent } from 'app/shared/variable/variable.event.model';
 import * as applicationsActions from 'app/store/applications.action';
 import { CancelApplicationEdition, ClearCacheApplication } from 'app/store/applications.action';
 import { ApplicationsState, ApplicationStateModel } from 'app/store/applications.state';
-import { AuthenticationState } from 'app/store/authentication.state';
 import { ProjectState, ProjectStateModel } from 'app/store/project.state';
 import cloneDeep from 'lodash-es/cloneDeep';
 import { Subscription } from 'rxjs';
 import { filter, finalize } from 'rxjs/operators';
+import { Tab } from 'app/shared/tabs/tabs.component';
 
 @Component({
     selector: 'app-application-show',
@@ -50,8 +49,9 @@ export class ApplicationShowComponent implements OnInit, OnDestroy {
     _routeParamsSub: Subscription;
     _queryParamsSub: Subscription;
 
-    // Selected tab
-    selectedTab = 'home';
+    // tabs
+    tabs: Array<Tab>;
+    selectedTab: Tab;
 
     @ViewChild('varWarning')
     private varWarningModal: WarningModalComponent;
@@ -132,6 +132,7 @@ export class ApplicationShowComponent implements OnInit, OnDestroy {
                     this.pipelines = this.application.usage.pipelines || [];
                     this.usageCount = this.pipelines.length + this.environments.length + this.workflows.length;
                 }
+                this.initTabs();
 
                 // Update recent application viewed
                 this._applicationStore.updateRecentApplication(s.currentProjectKey, this.application);
@@ -142,17 +143,55 @@ export class ApplicationShowComponent implements OnInit, OnDestroy {
     }
 
     ngOnInit() {
+        this.initTabs();
         this._queryParamsSub = this._routeActivated.queryParams.subscribe(params => {
             let tab = params['tab'];
             if (tab) {
-                this.selectedTab = tab;
+                let current_tab = this.tabs.find((t) => t.key === tab);
+                if (current_tab) {
+                    this.selectTab(current_tab);
+                }
             }
             this._cd.markForCheck();
         });
     }
 
-    showTab(tab: string): void {
-        this._router.navigateByUrl('/project/' + this.project.key + '/application/' + this.application.name + '?tab=' + tab);
+    initTabs() {
+        let usageText = 'Usage';
+        if (this.application) {
+            usageText = 'Usage (' + this.usageCount + ')';
+        }
+        this.tabs = [<Tab>{
+            title: 'Overview',
+            key: 'home',
+            default: true,
+        }, <Tab>{
+            title: 'Variables',
+            key: 'variables',
+            icon: 'font'
+        }, <Tab>{
+            title: 'Vulnerabilities',
+            key: 'vuln'
+        }, <Tab>{
+            title: usageText,
+            icon: 'map signs',
+            key: 'usage'
+        }, <Tab>{
+            title: 'Keys',
+            icon: 'privacy',
+            key: 'keys'
+        }]
+        if (this.project?.permissions?.writable) {
+            this.tabs.push(<Tab>{
+                title: 'Advanced',
+                icon: 'graduation',
+                key: 'advanced'
+            })
+        }
+    }
+
+    selectTab(tab: Tab): void {
+        this.selectedTab = tab;
     }
 
     /**
diff --git a/ui/src/app/views/application/show/application.html b/ui/src/app/views/application/show/application.html
index d540a251b..bae928002 100644
--- a/ui/src/app/views/application/show/application.html
+++ b/ui/src/app/views/application/show/application.html
@@ -34,43 +34,20 @@
                 </ng-template>
             </div>
         </app-project-breadcrumb>
-
-        <div class="CDSmenu" *ngIf="application">
-            <sm-menu title="" class="pointing secondary">
-                <a sm-item [class.active]="selectedTab === 'home'" (click)="showTab('home')">
-                    <i class=""></i>{{ 'common_home' | translate }}
-                </a>
-                <a sm-item [class.active]="selectedTab === 'variables'" (click)="showTab('variables')">
-                    <i class="font icon"></i>{{ 'common_variables' | translate }}
-                </a>
-                <a sm-item [class.active]="selectedTab === 'vuln'" (click)="showTab('vuln')">
-                    {{ 'common_vulnerabilities' | translate}}
-                </a>
-                <a sm-item [class.active]="selectedTab === 'usage'" (click)="showTab('usage')">
-                    <i class="map signs icon"></i>{{ 'common_usage' | translate }}{{ ' (' + usageCount + ')' }}
-                </a>
-                <a sm-item [class.active]="selectedTab === 'keys'" id="ApplicationKeysTab" (click)="showTab('keys')"><i
-                        class="privacy icon"></i>
-                    {{ 'common_keys' | translate }}
-                </a>
-                <a sm-item [class.active]="selectedTab === 'advanced'" (click)="showTab('advanced')">
-                    <i class="graduation icon"></i>{{ 'common_advanced' | translate }}
-                </a>
-                <div class="ui small buttons" *ngIf="editMode && !readOnly">
-                    <ng-container *ngIf="application.editModeChanged">
-                        <button class="ui button" (click)="cancelApplication()">{{'common_cancel' | translate }}</button>
-                        <div class="or"></div>
-                    </ng-container>
-                    <button class="ui green button" [disabled]="!application.editModeChanged"
-                            (click)="saveEditMode()">{{'btn_save_application' | translate }}</button>
-                </div>
-            </sm-menu>
-        </div>
+        <app-tabs [tabs]="tabs" (onSelect)="selectTab($event)"></app-tabs>
+        <ng-container *ngIf="editMode && !readOnly">
+            <nz-button-group class="ascodeButton">
+                <ng-container *ngIf="application.editModeChanged">
+                    <button nz-button nzType="default" (click)="cancelApplication()">{{'common_cancel' | translate }}</button>
+                </ng-container>
+                <button nz-button nzType="primary" [disabled]="!application.editModeChanged" (click)="saveEditMode()">{{'btn_save_application' | translate }}</button>
+            </nz-button-group>
+        </ng-container>
 
         <div class="scrollingContent">
             <div class="paddingContent">
                 <div class="viewContent">
-                    <div [ngSwitch]="selectedTab">
+                    <div [ngSwitch]="selectedTab.key">
                         <div *ngSwitchCase="'home'">
                             <app-home [project]="project" [application]="application"></app-home>
                         </div>
diff --git a/ui/src/app/views/application/show/application.scss b/ui/src/app/views/application/show/application.scss
index f33cdef75..72367169b 100644
--- a/ui/src/app/views/application/show/application.scss
+++ b/ui/src/app/views/application/show/application.scss
@@ -13,11 +13,9 @@
   height: 100%;
 }
 
-sm-menu {
-  .ui.buttons {
-    position: absolute;
-    right: 15px;
-    margin-top: 2px;
-  }
+.ascodeButton {
+  position: absolute;
+  right: 15px;
+  top: 107px;
 }
 
diff --git a/ui/src/app/views/application/show/home/application.home.html b/ui/src/app/views/application/show/home/application.home.html
index 4c268a706..bea717660 100644
--- a/ui/src/app/views/application/show/home/application.home.html
+++ b/ui/src/app/views/application/show/home/application.home.html
@@ -52,7 +52,7 @@
                     [queryParams]="{tab: 'advanced'}">{{
                     'application_home_no_integration' | translate }}</a>
             </div>
-            <h4>{{ 'common_usage' | translate}}</h4>
+            <h4>Usage</h4>
             <ng-container *ngIf="application.usage && application.usage.workflows">
                 <ul>
                     <li *ngFor="let w of application.usage.workflows">
diff --git a/ui/src/app/views/environment/show/environment.show.component.ts b/ui/src/app/views/environment/show/environment.show.component.ts
index b24faf585..7bc6224f3 100644
--- a/ui/src/app/views/environment/show/environment.show.component.ts
+++ b/ui/src/app/views/environment/show/environment.show.component.ts
@@ -4,13 +4,11 @@ import { TranslateService } from '@ngx-translate/core';
 import { Store } from '@ngxs/store';
 import { Environment } from 'app/model/environment.model';
 import { Project } from 'app/model/project.model';
-import { AuthentifiedUser } from 'app/model/user.model';
 import { Workflow } from 'app/model/workflow.model';
 import { AsCodeSaveModalComponent } from 'app/shared/ascode/save-modal/ascode.save-modal.component';
 import { AutoUnsubscribe } from 'app/shared/decorator/autoUnsubscribe';
 import { ToastService } from 'app/shared/toast/ToastService';
 import { VariableEvent } from 'app/shared/variable/variable.event.model';
-import { AuthenticationState } from 'app/store/authentication.state';
 import { CleanEnvironmentState } from 'app/store/environment.action';
 import * as envActions from 'app/store/environment.action';
 import { EnvironmentState, EnvironmentStateModel } from 'app/store/environment.state';
@@ -18,6 +16,7 @@ import { ProjectState, ProjectStateModel } from 'app/store/project.state';
 import { cloneDeep } from 'lodash-es';
 import { Subscription } from 'rxjs';
 import { finalize } from 'rxjs/operators';
+import { Tab } from 'app/shared/tabs/tabs.component';
 
 @Component({
     selector: 'app-environment-show',
@@ -50,8 +49,9 @@ export class EnvironmentShowComponent implements OnInit, OnDestroy {
     _routeDataSub: Subscription;
     _queryParamsSub: Subscription;
 
-    // Selected tab
-    selectedTab = 'variables';
+    // tabs
+    tabs: Array<Tab>;
+    selectedTab: Tab;
 
     // queryparam for breadcrum
     workflowName: string;
@@ -123,6 +123,7 @@ export class EnvironmentShowComponent implements OnInit, OnDestroy {
                                 this.workflows = this.environment.usage.workflows || [];
                                 this.usageCount = this.workflows.length;
                             }
+                            this.initTabs();
                             this._cd.markForCheck();
                         }, () => {
                             this._router.navigate(['/project', key], { queryParams: { tab: 'environments' } });
@@ -133,14 +134,51 @@ export class EnvironmentShowComponent implements OnInit, OnDestroy {
     }
 
     ngOnInit() {
+        this.initTabs();
         this._queryParamsSub = this._route.queryParams.subscribe(params => {
             let tab = params['tab'];
             if (tab) {
-                this.selectedTab = tab;
+                let current_tab = this.tabs.find((t) => t.key === tab);
+                if (current_tab) {
+                    this.selectTab(current_tab);
+                }
+                this._cd.markForCheck();
             }
         });
     }
 
+    initTabs() {
+        let usageText = 'Usage';
+        if (this.environment) {
+            usageText = 'Usage (' + this.usageCount + ')';
+        }
+        this.tabs = [<Tab>{
+            title: 'Variables',
+            key: 'variables',
+            default: true,
+            icon: 'font'
+        }, <Tab>{
+            title: 'Keys',
+            key: 'keys',
+            icon: 'privacy'
+        }, <Tab>{
+            title: usageText,
+            icon: 'map signs',
+            key: 'usage'
+        }]
+        if (this.project?.permissions?.writable) {
+            this.tabs.push(<Tab>{
+                title: 'Advanced',
+                icon: 'graduation',
+                key: 'advanced'
+            })
+        }
+    }
+
+    selectTab(tab: Tab): void {
+        this.selectedTab = tab;
+    }
+
     ngOnDestroy() {
         this._store.dispatch(new CleanEnvironmentState());
     }
diff --git a/ui/src/app/views/environment/show/environment.show.html b/ui/src/app/views/environment/show/environment.show.html
index 889837946..f3893355e 100644
--- a/ui/src/app/views/environment/show/environment.show.html
+++ b/ui/src/app/views/environment/show/environment.show.html
@@ -20,38 +20,20 @@
                 </ng-template>
             </div>
         </app-project-breadcrumb>
-
-        <div class="CDSmenu" *ngIf="environment">
-            <sm-menu title="" class="pointing secondary">
-                <a sm-item [class.active]="selectedTab === 'variables'" (click)="showTab('variables')">
-                    <i class="font icon"></i>{{ 'common_variables' | translate }}
-                </a>
-                <a sm-item [class.active]="selectedTab === 'keys'" id="EnvironmentKeysTab" (click)="showTab('keys')"><i
-                        class="privacy icon"></i>
-                    {{ 'common_keys' | translate }}
-                </a>
-                <a sm-item [class.active]="selectedTab === 'usage'" (click)="showTab('usage')">
-                    <i class="map signs icon"></i>{{ 'common_usage' | translate }}{{ ' (' + usageCount + ')' }}
-                </a>
-                <a sm-item [class.active]="selectedTab === 'advanced'" (click)="showTab('advanced')">
-                    <i class="graduation icon"></i>
-                    {{ 'common_advanced' | translate }}
-                </a>
-                <div class="ui small buttons" *ngIf="editMode && !readonly">
-                    <ng-container *ngIf="environment.editModeChanged">
-                        <button class="ui button" (click)="cancelEnvironment()">{{'common_cancel' | translate }}</button>
-                        <div class="or"></div>
-                    </ng-container>
-                    <button class="ui green button" [disabled]="!environment.editModeChanged"
-                            (click)="saveEditMode()">{{'btn_save_environment' | translate }}</button>
-                </div>
-            </sm-menu>
-        </div>
+        <app-tabs [tabs]="tabs" (onSelect)="selectTab($event)"></app-tabs>
+        <ng-container *ngIf="editMode && !readonly">
+            <nz-button-group class="ascodeButton">
+                <ng-container *ngIf="environment.editModeChanged">
+                    <button nz-button nzType="default" (click)="cancelEnvironment()">{{'common_cancel' | translate }}</button>
+                </ng-container>
+                <button nz-button nzType="primary" [disabled]="!environment.editModeChanged" (click)="saveEditMode()">{{'btn_save_environment' | translate }}</button>
+            </nz-button-group>
+        </ng-container>
 
         <div class="scrollingContent">
             <div class="paddingContent">
                 <div class="viewContent">
-                    <div [ngSwitch]="selectedTab">
+                    <div [ngSwitch]="selectedTab.key">
                         <div *ngSwitchCase="'variables'">
                             <ng-container
                                 *ngIf="!readonly">
diff --git a/ui/src/app/views/environment/show/environment.show.scss b/ui/src/app/views/environment/show/environment.show.scss
index e662d1a1b..3e114fd91 100644
--- a/ui/src/app/views/environment/show/environment.show.scss
+++ b/ui/src/app/views/environment/show/environment.show.scss
@@ -8,12 +8,10 @@
         overflow-x: auto;
     }
 
-  sm-menu {
-    .ui.buttons {
-      position: absolute;
-      right: 15px;
-      margin-top: 2px;
-    }
+  .ascodeButton {
+    position: absolute;
+    right: 15px;
+    top: 107px;
   }
 }
 
diff --git a/ui/src/app/views/navbar/navbar.html b/ui/src/app/views/navbar/navbar.html
index 3f5cc3722..56e5a9f28 100644
--- a/ui/src/app/views/navbar/navbar.html
+++ b/ui/src/app/views/navbar/navbar.html
@@ -141,7 +141,7 @@
                             <div class="ui relaxed divided list">
                                 <div class="item">
                                     <div class="content">
-                                        {{ 'common_workflows' | translate }}
+                                        Workflows
                                     </div>
                                 </div>
                             </div>
diff --git a/ui/src/app/views/pipeline/show/pipeline.show.component.spec.ts b/ui/src/app/views/pipeline/show/pipeline.show.component.spec.ts
index 4e660cce0..f4fc2fc40 100644
--- a/ui/src/app/views/pipeline/show/pipeline.show.component.spec.ts
+++ b/ui/src/app/views/pipeline/show/pipeline.show.component.spec.ts
@@ -18,18 +18,18 @@ import { AddPipelineParameter, DeletePipelineParameter, FetchPipeline, UpdatePip
 import { NgxsStoreModule } from 'app/store/store.module';
 import { of } from 'rxjs';
 import 'rxjs/add/observable/of';
-import { Parameter } from '../../../model/parameter.model';
-import { Pipeline } from '../../../model/pipeline.model';
-import { Project } from '../../../model/project.model';
-import { KeyService } from '../../../service/keys/keys.service';
-import { NavbarService } from '../../../service/navbar/navbar.service';
-import { PipelineCoreService } from '../../../service/pipeline/pipeline.core.service';
-import { PipelineService } from '../../../service/pipeline/pipeline.service';
-import { ProjectService } from '../../../service/project/project.service';
-import { ProjectStore } from '../../../service/project/project.store';
-import { ParameterEvent } from '../../../shared/parameter/parameter.event.model';
-import { SharedModule } from '../../../shared/shared.module';
-import { ToastService } from '../../../shared/toast/ToastService';
+import { Parameter } from 'app/model/parameter.model';
+import { Pipeline } from 'app/model/pipeline.model';
+import { Project } from 'app/model/project.model';
+import { KeyService } from 'app/service/keys/keys.service';
+import { NavbarService } from 'app/service/navbar/navbar.service';
+import { PipelineCoreService } from 'app/service/pipeline/pipeline.core.service';
+import { PipelineService } from 'app/service/pipeline/pipeline.service';
+import { ProjectService } from 'app/service/project/project.service';
+import { ProjectStore } from 'app/service/project/project.store';
+import { ParameterEvent } from 'app/shared/parameter/parameter.event.model';
+import { SharedModule } from 'app/shared/shared.module';
+import { ToastService } from 'app/shared/toast/ToastService';
 import { PipelineModule } from '../pipeline.module';
 import { PipelineShowComponent } from './pipeline.show.component';
 
@@ -96,7 +96,6 @@ describe('CDS: Pipeline Show', () => {
         fixture.componentInstance.project = project;
         fixture.componentInstance.ngOnInit();
 
-        expect(fixture.componentInstance.selectedTab).toBe('workflow');
         expect(fixture.componentInstance.pipeline.name).toBe('pip1');
         expect(fixture.componentInstance.project.key).toBe('key1');
 
diff --git a/ui/src/app/views/pipeline/show/pipeline.show.component.ts b/ui/src/app/views/pipeline/show/pipeline.show.component.ts
index f5eb43d28..d0132193f 100644
--- a/ui/src/app/views/pipeline/show/pipeline.show.component.ts
+++ b/ui/src/app/views/pipeline/show/pipeline.show.component.ts
@@ -26,6 +26,7 @@ import { ProjectState, ProjectStateModel } from 'app/store/project.state';
 import cloneDeep from 'lodash-es/cloneDeep';
 import { Subscription } from 'rxjs';
 import { filter, finalize, first } from 'rxjs/operators';
+import { Tab } from 'app/shared/tabs/tabs.component';
 
 @Component({
     selector: 'app-pipeline-show',
@@ -72,8 +73,9 @@ export class PipelineShowComponent implements OnInit, OnDestroy {
     keys: AllKeys;
     asCodeEditorOpen: boolean;
 
-    // Selected tab
-    selectedTab = 'pipeline';
+    // tabs
+    tabs: Array<Tab>;
+    selectedTab: Tab;
 
     readOnly: boolean;
 
@@ -133,6 +135,7 @@ export class PipelineShowComponent implements OnInit, OnDestroy {
     }
 
     ngOnInit() {
+        this.initTabs();
         this.projectKey = this._routeActivated.snapshot.params['key'];
         this.pipName = this._routeActivated.snapshot.params['pipName'];
 
@@ -153,7 +156,10 @@ export class PipelineShowComponent implements OnInit, OnDestroy {
             this.queryParams = params;
             let tab = params['tab'];
             if (tab) {
-                this.selectedTab = tab;
+                let current_tab = this.tabs.find((t) => t.key === tab);
+                if (current_tab) {
+                    this.selectTab(current_tab);
+                }
             }
             this._cd.markForCheck();
         });
@@ -199,14 +205,50 @@ export class PipelineShowComponent implements OnInit, OnDestroy {
                 }
 
                 this.usageCount = this.applications.length + this.environments.length + this.workflows.length;
+                this.initTabs();
                 this._cd.markForCheck();
             }, () => {
                 this._router.navigate(['/project', this.projectKey], { queryParams: { tab: 'pipelines' } });
             });
     }
 
-    showTab(tab: string): void {
-        this._router.navigateByUrl(`/project/${this.project.key}/pipeline/${this.pipeline.name}?tab=${tab}`);
+    initTabs() {
+        let usageText = 'Usage';
+        if (this.pipeline) {
+            usageText = 'Usage (' + this.usageCount + ')';
+        }
+        this.tabs = [<Tab>{
+            title: 'Pipeline',
+            key: 'pipeline',
+            default: true,
+            icon: 'sitemap'
+        }, <Tab>{
+            title: 'Parameters',
+            key: 'parameters',
+            icon: 'font'
+        }, <Tab>{
+            title: usageText,
+            icon: 'map signs',
+            key: 'usage'
+        }]
+        if (!this.pipeline?.from_repository) {
+            this.tabs.push(<Tab>{
+                title: 'Audits',
+                icon: 'history',
+                key: 'audits'
+            })
+        }
+        if (this.project?.permissions?.writable) {
+            this.tabs.push( <Tab>{
+                title: 'Advanced',
+                icon: 'graduation',
+                key: 'advanced'
+            })
+        }
+    }
+
+    selectTab(tab: Tab): void {
+        this.selectedTab = tab;
     }
 
     parameterEvent(event: ParameterEvent, skip?: boolean): void {
diff --git a/ui/src/app/views/pipeline/show/pipeline.show.html b/ui/src/app/views/pipeline/show/pipeline.show.html
index 8dcc29b22..074990331 100644
--- a/ui/src/app/views/pipeline/show/pipeline.show.html
+++ b/ui/src/app/views/pipeline/show/pipeline.show.html
@@ -37,40 +37,19 @@
                 </ng-template>
             </div>
         </app-project-breadcrumb>
-
-        <div class="CDSmenu">
-            <sm-menu title="" class="pointing secondary">
-                <a sm-item [class.active]="selectedTab === 'pipeline'" (click)="showTab('pipeline')">
-                    <i class="sitemap icon"></i>{{ 'common_pipeline' | translate }}
-                </a>
-                <a sm-item [class.active]="selectedTab === 'parameters'" (click)="showTab('parameters')">
-                    <i class="font icon"></i>{{ 'common_parameters' | translate }}
-                </a>
-                <a sm-item [class.active]="selectedTab === 'usage'" [class.disabled]="usageCount === 0"
-                    (click)="usageCount > 0 && showTab('usage')">
-                    <i class="map signs icon"></i>{{ 'common_usage' | translate }}{{ ' (' + usageCount + ')' }}
-                </a>
-                <a sm-item [class.active]="selectedTab === 'audits'" (click)="showTab('audits')">
-                    <i class="history icon"></i>{{'common_audit' | translate }}
-                </a>
-                <a sm-item [class.active]="selectedTab === 'advanced'" (click)="showTab('advanced')"
-                    *ngIf="project.permissions.writable">
-                    <i class="graduation icon"></i>{{ 'common_advanced' | translate }}
-                </a>
-                <div class="ui small buttons" *ngIf="editMode && !readOnly">
-                    <ng-container *ngIf="pipeline.editModeChanged">
-                        <button class="ui button" (click)="cancelPipeline()">{{'common_cancel' | translate }}</button>
-                        <div class="or"></div>
-                    </ng-container>
-                    <button class="ui green button" [disabled]="!pipeline.editModeChanged"
-                        (click)="saveEditMode()">{{'btn_save_pipeline' | translate }}</button>
-                </div>
-            </sm-menu>
-        </div>
+        <app-tabs [tabs]="tabs" (onSelect)="selectTab($event)"></app-tabs>
+        <ng-container *ngIf="editMode && !readOnly">
+            <nz-button-group class="ascodeButton">
+                <ng-container *ngIf="pipeline.editModeChanged">
+                    <button nz-button nzType="default" (click)="cancelPipeline()">{{'common_cancel' | translate }}</button>
+                </ng-container>
+                <button nz-button nzType="primary" [disabled]="!pipeline.editModeChanged" (click)="saveEditMode()">{{'btn_save_pipeline' | translate }}</button>
+            </nz-button-group>
+        </ng-container>
 
         <div class="scrollingContent">
             <div class="paddingContent">
-                <div class="viewContent" [ngSwitch]="selectedTab">
+                <div class="viewContent" [ngSwitch]="selectedTab.key">
                     <div *ngSwitchCase="'pipeline'">
                         <app-pipeline-workflow [project]="project" [currentPipeline]="pipeline"
                             [queryParams]="queryParams" [editMode]="editMode" [readOnly]="readOnly">
diff --git a/ui/src/app/views/pipeline/show/pipeline.show.scss b/ui/src/app/views/pipeline/show/pipeline.show.scss
index 3a9567173..cc78d0a22 100644
--- a/ui/src/app/views/pipeline/show/pipeline.show.scss
+++ b/ui/src/app/views/pipeline/show/pipeline.show.scss
@@ -5,10 +5,8 @@
   height: 100%;
 }
 
-sm-menu {
-  .ui.buttons {
-    position: absolute;
-    right: 15px;
-    margin-top: 2px;
-  }
+.ascodeButton {
+  position: absolute;
+  right: 15px;
+  top: 107px;
 }
diff --git a/ui/src/app/views/project/show/project.component.ts b/ui/src/app/views/project/show/project.component.ts
index 12d663398..a1f06ed92 100644
--- a/ui/src/app/views/project/show/project.component.ts
+++ b/ui/src/app/views/project/show/project.component.ts
@@ -97,46 +97,48 @@ export class ProjectShowComponent implements OnInit, OnDestroy, AfterViewInit {
 
     ngOnInit() {
         this.tabs = [<Tab>{
-            translate: 'common_workflows',
+            title: 'Workflows',
             icon: 'share alternate',
             key: 'workflows',
             default: true,
         }, <Tab>{
-            translate: 'common_applications',
+            title: 'Applications',
             icon: 'rocket',
             key: 'applications'
         }, <Tab>{
-            translate: 'common_pipelines',
+            title: 'Pipelines',
             icon: 'sitemap',
             key: 'pipelines'
         }, <Tab>{
-            translate: 'common_environments',
+            title: 'Environments',
             icon: 'tree',
             key: 'environments'
         }, <Tab>{
-            translate: 'common_variables',
+            title: 'Variables',
             icon: 'font',
             key: 'variables'
         }, <Tab>{
             key: 'permissions',
         }, <Tab>{
-            translate: 'common_keys',
+            title: 'Keys',
             icon: 'privacy',
             key: 'keys'
         }, <Tab>{
-            translate: 'common_integrations',
+            title: 'Integrations',
             icon: 'plug',
             key: 'integrations'
         }, <Tab>{
-            translate: 'common_warnings',
+            title: 'Warnings',
             icon: 'bug',
             key: 'warnings'
-        }, <Tab>{
-            translate: 'common_advanced',
-            icon: 'graduation',
-            key: 'advanced'
-        }];
-
+        }]
+        if (this.project?.permissions?.writable) {
+            this.tabs.push(<Tab>{
+                title: 'Advanced',
+                icon: 'graduation',
+                key: 'advanced'
+            })
+        }
         this._route.queryParams.subscribe((queryParams) => {
             if (queryParams['tab']) {
                 let current_tab = this.tabs.find((tab) => tab.key === queryParams['tab']);
diff --git a/ui/src/app/views/project/show/project.html b/ui/src/app/views/project/show/project.html
index 91c673804..c626e1392 100644
--- a/ui/src/app/views/project/show/project.html
+++ b/ui/src/app/views/project/show/project.html
@@ -1,6 +1,6 @@
 <ng-template #tabPermissionTemplate>
     <i [ngClass]="['users', 'icon']"></i>
-    {{'common_permissions' | translate}}
+    Permissions
     <ng-template let-popup #warnPermission>
         Following groups that are not from organization "{{project.organization}}" can read the
         project:
diff --git a/ui/src/app/views/settings/action/edit/action.edit.component.ts b/ui/src/app/views/settings/action/edit/action.edit.component.ts
index 9c639553e..170413ffc 100644
--- a/ui/src/app/views/settings/action/edit/action.edit.component.ts
+++ b/ui/src/app/views/settings/action/edit/action.edit.component.ts
@@ -51,16 +51,16 @@ export class ActionEditComponent implements OnInit, OnDestroy {
 
     ngOnInit() {
         this.tabs = [<Tab>{
-            translate: 'common_action',
+            title: 'Action',
             icon: 'list',
             key: 'action',
             default: true
         }, <Tab>{
-            translate: 'common_audit',
+            title: 'Audit',
             icon: 'history',
             key: 'audits'
         }, <Tab>{
-            translate: 'common_usage',
+            title: 'Usage',
             icon: 'map signs',
             key: 'usage'
         }];
@@ -85,7 +85,7 @@ export class ActionEditComponent implements OnInit, OnDestroy {
             <Column<AuditAction>>{
                 disabled: true,
                 type: ColumnType.CONFIRM_BUTTON,
-                name: 'common_action',
+                name: 'Action',
                 class: 'two right aligned',
                 selector: (aa: AuditAction) => ({
                         title: 'common_rollback',
diff --git a/ui/src/app/views/settings/action/form/action.form.html b/ui/src/app/views/settings/action/form/action.form.html
index 92a04ee43..199bdd35f 100644
--- a/ui/src/app/views/settings/action/form/action.form.html
+++ b/ui/src/app/views/settings/action/form/action.form.html
@@ -2,7 +2,7 @@
     {{'common_help_no_group' | translate}}
 </div>
 
-<form class="ui form">
+<form class="ui form" #myform>
     <div class="ui segment">
         <div class="field">
             <div class="fields">
@@ -111,29 +111,29 @@
     <div class="ui info message" *ngIf="steps.length === 0">
         {{ 'step_nonfinal_no' | translate }}
     </div>
-
-    <div class="ui bottomBtnsWrapper segment" *ngIf="action.editable">
-        <div class="ui grid">
-            <div class="sixteen wide column" *ngIf="actions.length > 0">
-                <app-action-step-form [actions]="actions" (onEvent)="stepManagement($event)"></app-action-step-form>
-            </div>
-            <ng-container *ngIf="!stepFormExpended">
-                <div class="eight wide column" *ngIf="action.id">
-                    <button class="ui fluid green button" name="updatebtn" (click)="saveAction()"
-                        [class.loading]="loading">
-                        <i class="save icon"></i>{{ 'btn_save' | translate }}
-                    </button>
-                </div>
-                <div class="eight wide column" *ngIf="action.id">
-                    <app-delete-button (event)="deleteAction()" [loading]="loading" [class]="'fluid'" title="action_delete">
-                    </app-delete-button>
-                </div>
-                <div class="sixteen wide column" *ngIf="!action.id">
-                    <button class="ui green button" name="addbtn" (click)="saveAction()" [class.loading]="loading">
-                        <i class="save icon"></i>{{ 'btn_create' | translate }}
-                    </button>
+</form>
+<nz-layout class="bottomBtnsWrapper"  *ngIf="action.editable">
+    <div nz-row>
+        <div nz-col nzSpan="24" *ngIf="actions.length > 0">
+            <app-action-step-form [actions]="actions" (onEvent)="stepManagement($event)"></app-action-step-form>
+        </div>
+    </div>
+    <div nz-row *ngIf="!stepFormExpended">
+            <ng-container *ngIf="action.id">
+                <button (click)="saveAction()" nzType="primary" class="validButton" name="updatebtn" [nzLoading]="loading" nz-button>
+                    <i nz-icon nzType="save" nzTheme="outline"></i>Save
+                </button>
+                <button nz-popconfirm nzDanger  class="validButton ml10"
+                                nzType="default"
+                                nzPopconfirmTitle="Are you sure you want to delete this action?"
+                                (nzOnConfirm)="deleteAction()"
+                                nzPopconfirmPlacement="topLeft"
+                                nz-button><i nz-icon nzType="rest" nzTheme="outline"></i>Delete</button>
+            </ng-container>
+            <ng-container *ngIf="!action.id">
+                <div nz-col nzSpan="24">
+                    <button nz-button nzType="primary"  name="addbtn" (click)="saveAction()" [nzLoading]="loading" nzBlock><i class="save icon"></i>Create</button>
                 </div>
             </ng-container>
-        </div>
     </div>
-</form>
+</nz-layout>
diff --git a/ui/src/app/views/settings/action/form/action.form.scss b/ui/src/app/views/settings/action/form/action.form.scss
index b7efd631f..1fd676b6e 100644
--- a/ui/src/app/views/settings/action/form/action.form.scss
+++ b/ui/src/app/views/settings/action/form/action.form.scss
@@ -56,15 +56,24 @@
 }
 
 .bottomBtnsWrapper {
-  background-color: #f9f9f9;
   position: fixed;
   bottom: 0;
   right: 0;
   width: 430px;
-  padding-top: 20px;
-  padding-bottom: 20px;
+  padding: 10px 10px 10px 10px;
   .column {
     padding-top: 5px;
     padding-bottom: 5px;
   }
+  .ant-row {
+    padding-bottom: 4px;
+    padding-top: 4px;
+
+    .validButton {
+      width: 200px;
+    }
+    .ml10 {
+      margin-left: 10px;
+    }
+  }
 }
diff --git a/ui/src/app/views/settings/action/list/action.list.component.ts b/ui/src/app/views/settings/action/list/action.list.component.ts
index ce4eac3ff..43195fcca 100644
--- a/ui/src/app/views/settings/action/list/action.list.component.ts
+++ b/ui/src/app/views/settings/action/list/action.list.component.ts
@@ -28,12 +28,12 @@ export class ActionListComponent {
         private _cd: ChangeDetectorRef
     ) {
         this.tabs = [<Tab>{
-            translate: 'action_custom',
+            title: 'Custom',
             icon: '',
             key: 'custom',
             default: true
         }, <Tab>{
-            translate: 'action_builtin',
+            title: 'Builtin',
             icon: '',
             key: 'builtin'
         }];
diff --git a/ui/src/app/views/settings/action/show/action.show.component.ts b/ui/src/app/views/settings/action/show/action.show.component.ts
index 9847006fe..19a380244 100644
--- a/ui/src/app/views/settings/action/show/action.show.component.ts
+++ b/ui/src/app/views/settings/action/show/action.show.component.ts
@@ -37,12 +37,12 @@ export class ActionShowComponent implements OnInit, OnDestroy {
 
     ngOnInit() {
         this.tabs = [<Tab>{
-            translate: 'common_action',
+            title: 'Action',
             icon: 'list',
             key: 'action',
             default: true
         }, <Tab>{
-            translate: 'common_usage',
+            title: 'Usage',
             icon: 'map signs',
             key: 'usage'
         }];
diff --git a/ui/src/app/views/settings/user/consumer-create-modal/scope-detail.html b/ui/src/app/views/settings/user/consumer-create-modal/scope-detail.html
index c27f6cf69..da4a36c4b 100644
--- a/ui/src/app/views/settings/user/consumer-create-modal/scope-detail.html
+++ b/ui/src/app/views/settings/user/consumer-create-modal/scope-detail.html
@@ -4,7 +4,7 @@
             {{scopeDetail.scope}}
         </div>
         <div class="ui twelve wide column right aligned">
-            <a class="pointing advanced" (click)="clickAdvanced()">{{'common_advanced' | translate}}</a>
+            <a class="pointing advanced" (click)="clickAdvanced()">Advanced</a>
             <div class="ui checkbox">
                 <input type="checkbox" [ngModel]="allReadRoutesSelected" (click)="clickSelectAllRoutesRead()">
                 <label>{{'common_read' | translate}}</label>
diff --git a/ui/src/app/views/settings/user/consumer-details-modal/consumer-details-modal.component.ts b/ui/src/app/views/settings/user/consumer-details-modal/consumer-details-modal.component.ts
index 6ce26dcb7..f3d78ec0e 100644
--- a/ui/src/app/views/settings/user/consumer-details-modal/consumer-details-modal.component.ts
+++ b/ui/src/app/views/settings/user/consumer-details-modal/consumer-details-modal.component.ts
@@ -120,7 +120,7 @@ export class ConsumerDetailsModalComponent {
             },
             <Column<AuthConsumer>>{
                 type: ColumnType.BUTTON,
-                name: 'common_action',
+                name: 'Action',
                 class: 'two right aligned',
                 selector: (c: AuthConsumer) => ({
                         title: 'common_details',
diff --git a/ui/src/app/views/settings/user/edit/user.edit.component.ts b/ui/src/app/views/settings/user/edit/user.edit.component.ts
index 44a9f2cb6..54e126372 100644
--- a/ui/src/app/views/settings/user/edit/user.edit.component.ts
+++ b/ui/src/app/views/settings/user/edit/user.edit.component.ts
@@ -244,7 +244,7 @@ export class UserEditComponent implements OnInit {
             },
             <Column<AuthConsumer>>{
                 type: ColumnType.BUTTON,
-                name: 'common_action',
+                name: 'Action',
                 class: 'two right aligned',
                 selector: (c: AuthConsumer) => ({
                     title: 'common_details',
@@ -345,7 +345,7 @@ export class UserEditComponent implements OnInit {
             },
             <Column<AuthSession>>{
                 type: ColumnType.CONFIRM_BUTTON,
-                name: 'common_action',
+                name: 'Action',
                 class: 'two right aligned',
                 disabled: true,
                 selector: (s: AuthSession) => ({
diff --git a/ui/src/app/views/settings/user/edit/user.edit.html b/ui/src/app/views/settings/user/edit/user.edit.html
index a9a51d77f..03d2af1c3 100644
--- a/ui/src/app/views/settings/user/edit/user.edit.html
+++ b/ui/src/app/views/settings/user/edit/user.edit.html
@@ -15,7 +15,7 @@
                                     <div class="field">
                                         <label>{{'user_label_username' | translate}}</label>
                                         <input class="ui input" type="text" name="username" [(ngModel)]="user.username"
-                                            [disabled]="loading" [readonly]="!editable">
+                                            [disabled]="loading" readonly>
                                     </div>
                                     <div class="field">
                                         <label>{{'user_label_fullname' | translate}}</label>
diff --git a/ui/src/app/views/settings/worker-model/edit/worker-model.edit.component.ts b/ui/src/app/views/settings/worker-model/edit/worker-model.edit.component.ts
index 081b9b419..775c1dc75 100644
--- a/ui/src/app/views/settings/worker-model/edit/worker-model.edit.component.ts
+++ b/ui/src/app/views/settings/worker-model/edit/worker-model.edit.component.ts
@@ -64,16 +64,16 @@ export class WorkerModelEditComponent implements OnInit, OnDestroy {
 
     ngOnInit() {
         this.tabs = [<Tab>{
-            translate: 'worker_model',
+            title: 'Worker Model',
             icon: '',
             key: 'worker_model',
             default: true
         }, <Tab>{
-            translate: 'common_capabilities',
+            title: 'Capabilities',
             icon: 'file outline',
             key: 'capabilities'
         }, <Tab>{
-            translate: 'common_usage',
+            title: 'Usage',
             icon: 'map signs',
             key: 'usage'
         }];
diff --git a/ui/src/app/views/settings/workflow-template/edit/workflow-template.edit.component.ts b/ui/src/app/views/settings/workflow-template/edit/workflow-template.edit.component.ts
index b05e2c7df..4ed829317 100644
--- a/ui/src/app/views/settings/workflow-template/edit/workflow-template.edit.component.ts
+++ b/ui/src/app/views/settings/workflow-template/edit/workflow-template.edit.component.ts
@@ -76,20 +76,20 @@ export class WorkflowTemplateEditComponent implements OnInit, OnDestroy {
 
     ngOnInit() {
         this.tabs = [<Tab>{
-            translate: 'workflow_template',
+            title: 'Workflow template',
             icon: 'paste',
             key: 'workflow_template',
             default: true
         }, <Tab>{
-            translate: 'workflow_template_instances',
+            title: 'Instances',
             icon: 'file outline',
             key: 'instances'
         }, <Tab>{
-            translate: 'common_audit',
+            title: 'Audit',
             icon: 'history',
             key: 'audits'
         }, <Tab>{
-            translate: 'common_usage',
+            title: 'Usage',
             icon: 'map signs',
             key: 'usage'
         }];
@@ -172,7 +172,7 @@ export class WorkflowTemplateEditComponent implements OnInit, OnDestroy {
                 }
             }, <Column<WorkflowTemplateInstance>>{
                 type: ColumnType.BUTTON,
-                name: 'common_action',
+                name: 'Action',
                 class: 'two right aligned',
                 selector: (i: WorkflowTemplateInstance) => ({
                         title: 'common_update',
@@ -208,7 +208,7 @@ export class WorkflowTemplateEditComponent implements OnInit, OnDestroy {
                 if (this.workflowTemplate.editable) {
                     this.columnsAudits.push(<Column<AuditWorkflowTemplate>>{
                         type: ColumnType.CONFIRM_BUTTON,
-                        name: 'common_action',
+                        name: 'Action',
                         class: 'two right aligned',
                         selector: (a: AuditWorkflowTemplate) => ({
                                 title: 'common_rollback',
@@ -349,10 +349,7 @@ export class WorkflowTemplateEditComponent implements OnInit, OnDestroy {
                 this.tabs = this.tabs.map((tab) => {
                     tab.default = false;
                     if (tab.key === 'instances') {
-                        tab.translate = 'workflow_template_instances_with_nb';
-                        tab.translate_args = {
-                            nb: this.instances.length,
-                        };
+                        tab.title = 'Instances ' + this.instances.length;
                     }
 
                     return tab;
diff --git a/ui/src/app/views/settings/workflow-template/form/workflow-template.form.html b/ui/src/app/views/settings/workflow-template/form/workflow-template.form.html
index a4f4ae92a..16fdb6f48 100644
--- a/ui/src/app/views/settings/workflow-template/form/workflow-template.form.html
+++ b/ui/src/app/views/settings/workflow-template/form/workflow-template.form.html
@@ -131,7 +131,7 @@
                 </div>
                 <div *ngIf="(workflowTemplate.editable && !importFromURL) || pipelineKeys.length > 0"
                     class="wide field">
-                    <label>{{ 'common_pipelines' | translate }}</label>
+                    <label>Pipelines</label>
                     <div class="codeContainer field" *ngFor="let k of pipelineKeys">
                         <app-workflow-template-editor name="pipeline-value-{{k}}"
                             [editable]="workflowTemplate.editable && !importFromURL" [removable]="true"
@@ -146,7 +146,7 @@
                 </div>
                 <div *ngIf="(workflowTemplate.editable && !importFromURL) || applicationKeys.length > 0"
                     class="wide field">
-                    <label>{{ 'common_applications' | translate }}</label>
+                    <label>Applications</label>
                     <div class="codeContainer field" *ngFor="let k of applicationKeys">
                         <app-workflow-template-editor name="application-value-{{k}}"
                             [editable]="workflowTemplate.editable && !importFromURL" [removable]="true"
@@ -161,7 +161,7 @@
                 </div>
                 <div *ngIf="(workflowTemplate.editable && !importFromURL) || environmentKeys.length > 0"
                     class="wide field">
-                    <label>{{ 'common_environments' | translate }}</label>
+                    <label>Environments</label>
                     <div class="codeContainer field" *ngFor="let k of environmentKeys">
                         <app-workflow-template-editor name="environment-value-{{k}}"
                             [editable]="workflowTemplate.editable && !importFromURL" [removable]="true"
diff --git a/ui/src/app/views/workflow/run/node/node.html b/ui/src/app/views/workflow/run/node/node.html
index ca4d2070f..e1601630c 100644
--- a/ui/src/app/views/workflow/run/node/node.html
+++ b/ui/src/app/views/workflow/run/node/node.html
@@ -2,61 +2,13 @@
     <ng-container>
         <app-workflow-node-run-summary></app-workflow-node-run-summary>
     </ng-container>
-    <div class="CDSmenu">
-        <sm-menu title="" class="pointing secondary">
-            <a sm-item [class.active]="selectedTab === 'pipeline'" (click)="showTab('pipeline')">
-                <i class="sitemap icon"></i>
-                {{ 'common_pipeline' | translate }}
-            </a>
-            <a sm-item [class.active]="selectedTab === 'commit'" [class.disabled]="commitsLength === 0"
-                (click)="commitsLength !== 0 && showTab('commit')">
-                {{ commitsLength + ' '}}
-                <span *ngIf="commitsLength > 1">{{ 'common_commits' | translate }}</span>
-                <span *ngIf="commitsLength < 2">{{ 'common_commit' | translate }}</span>
-            </a>
-            <a sm-item [class.active]="selectedTab === 'vuln'" [class.disabled]="!hasVulnerability"
-                (click)="showTab('vuln')">
-                <span *ngIf="nbVuln > 0 || deltaVul !== 0">{{nbVuln + ' '}}</span>
-                <span>{{ 'common_vulnerabilities' | translate }}</span>
-                <span *ngIf="deltaVul !== 0">
-                    (
-                    <span *ngIf="deltaVul < 0"><i class="green caret down icon"></i></span>
-                    <span *ngIf="deltaVul > 0"><i class="red caret up icon"></i></span>
-                    <span *ngIf="deltaVul !== 0">{{deltaVul}}</span>
-                    )
-                </span>
-            </a>
-            <a sm-item [class.active]="selectedTab === 'test'" (click)="nodeRunTests?.total !== 0 && showTab('test')" [class.disabled]="!nodeRunTests?.total">
-                <span *ngIf="nodeRunTests?.total > 1">{{ 'common_tests' | translate }}</span>
-                <span *ngIf="nodeRunTests?.total < 2">{{ 'common_test' | translate }}</span>
-                <ng-container *ngIf="nodeRunTests?.total > 0">
-                (
-                    <ng-container *ngIf="nodeRunTests?.ok"><i class="green check icon no-mrr"></i>{{nodeRunTests.ok}}</ng-container>
-                    <ng-container *ngIf="nodeRunTests?.ko"><i class="red remove icon status"></i>{{nodeRunTests.ko}}</ng-container>
-                    <ng-container *ngIf="nodeRunTests?.skipped"><i class="grey ban icon status"></i>{{nodeRunTests.skipped}}</ng-container>
-                )
-                </ng-container>
-            </a>
-            <a sm-item [class.active]="selectedTab === 'artifact'"
-                (click)="artifactLength !== 0 && showTab('artifact')"
-                [class.disabled]="artifactLength === 0">
-                <span *ngIf="artifactLength > 1">{{ 'common_artifacts' | translate }}</span>
-                <span *ngIf="artifactLength < 2">{{ 'common_artifact' | translate }}</span>
-                <span *ngIf="artifactLength">{{ ' (' + artifactLength + ')'}}</span>
-            </a>
-            <a sm-item [class.active]="selectedTab === 'history'" (click)="historyLength !== 0 && showTab('history')"
-                [class.disabled]="historyLength === 0">
-                {{ 'common_history' | translate }}
-                {{ ' (' + historyLength + ')'}}
-            </a>
-        </sm-menu>
-    </div>
-    <div class="pipeline" *ngIf="selectedTab === 'pipeline'">
+    <app-tabs [tabs]="tabs" (onSelect)="selectTab($event)"></app-tabs>
+    <div class="pipeline" *ngIf="selectedTab.key === 'pipeline'">
         <app-node-run-pipeline></app-node-run-pipeline>
     </div>
-    <div *ngIf="selectedTab !== 'pipeline'" class="scrollingContent">
+    <div *ngIf="selectedTab.key !== 'pipeline'" class="scrollingContent">
         <div class="paddingContent">
-            <div class="viewContent" [ngSwitch]="selectedTab">
+            <div class="viewContent" [ngSwitch]="selectedTab.key">
                 <div *ngSwitchCase="'artifact'">
                     <app-workflow-artifact-list></app-workflow-artifact-list>
                 </div>
diff --git a/ui/src/app/views/workflow/run/node/workflow.run.node.component.ts b/ui/src/app/views/workflow/run/node/workflow.run.node.component.ts
index b38d31dc2..0dc2320a5 100644
--- a/ui/src/app/views/workflow/run/node/workflow.run.node.component.ts
+++ b/ui/src/app/views/workflow/run/node/workflow.run.node.component.ts
@@ -1,6 +1,6 @@
 import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
 import { Title } from '@angular/platform-browser';
-import { ActivatedRoute, NavigationExtras, Router } from '@angular/router';
+import { ActivatedRoute, Router } from '@angular/router';
 import { Select, Store } from '@ngxs/store';
 import { Tests } from 'app/model/pipeline.model';
 import { Project } from 'app/model/project.model';
@@ -11,6 +11,7 @@ import { ProjectState } from 'app/store/project.state';
 import { GetWorkflowNodeRun, GetWorkflowRun } from 'app/store/workflow.action';
 import { WorkflowState, WorkflowStateModel } from 'app/store/workflow.state';
 import { Observable, Subscription } from 'rxjs';
+import { Tab } from 'app/shared/tabs/tabs.component';
 
 @Component({
     selector: 'app-workflow-run-node',
@@ -41,7 +42,10 @@ export class WorkflowNodeRunComponent implements OnInit, OnDestroy {
 
     // History
     nodeRunsHistory = new Array<WorkflowNodeRun>();
-    selectedTab: string;
+
+    // tabs
+    tabs: Array<Tab>;
+    selectedTab: Tab;
 
     nbVuln = 0;
     deltaVul = 0;
@@ -56,14 +60,16 @@ export class WorkflowNodeRunComponent implements OnInit, OnDestroy {
         private _titleService: Title,
         private _cd: ChangeDetectorRef
     ) {
+        this.initTabs();
         this.project = this._store.selectSnapshot(ProjectState.projectSnapshot);
 
         // Tab selection
         this._activatedRoute.queryParams.subscribe(q => {
             if (q['tab']) {
-                this.selectedTab = q['tab'];
-            } else {
-                this.selectedTab = 'pipeline';
+                let current_tab = this.tabs.find((t) => t.key === q['tab']);
+                if (current_tab) {
+                    this.selectTab(current_tab);
+                }
             }
             this.pipelineName = q['name'] || '';
             this._cd.markForCheck();
@@ -154,18 +160,62 @@ export class WorkflowNodeRunComponent implements OnInit, OnDestroy {
                 }
             }
             if (refresh) {
+                this.initTabs();
                 this._cd.markForCheck();
             }
         });
     }
 
-    showTab(tab: string): void {
-        let queryParams = Object.assign({}, this._activatedRoute.snapshot.queryParams, { tab });
-        let navExtras: NavigationExtras = { queryParams };
-        this._router.navigate(['project', this.project.key,
-            'workflow', this.workflowName,
-            'run', this.currentNodeRunNum,
-            'node', this.currentNodeRunID], navExtras);
+    initTabs() {
+        let commitTitle = this.commitsLength > 1? 'Commits' : 'Commit';
+        if (this.commitsLength > 0) {
+            commitTitle = this.commitsLength.toString() + ' ' + commitTitle;
+        }
+
+        let testTitle = this.nodeRunTests?.total > 1? 'Tests' : 'Test';
+        let testIcon: string
+        if (this.nodeRunTests?.total > 0) {
+            testIcon = 'green check no-mrr';
+            testTitle = this.nodeRunTests?.total + ' ' + testTitle;
+        }
+        if (this.nodeRunTests?.ko > 0) {
+            testTitle += ` (${this.nodeRunTests.ko} ko)`
+            testIcon = 'red remove status';
+        }
+
+        let artifactTitle = this.artifactLength > 1 ? 'Artifacts' : 'Artifact';
+        if (this.artifactLength > 0) {
+            artifactTitle = artifactTitle + ' ('+ this.artifactLength+')';
+        }
+
+        let historyTitle = 'History' + ' ('+ this.historyLength+')';
+
+        this.tabs = [<Tab>{
+            title: 'Pipeline',
+            key: 'pipeline',
+            default: true,
+            icon: 'sitemap'
+        }, <Tab>{
+            title: commitTitle,
+            key: 'commit',
+            disabled: this.commitsLength === 0
+        }, <Tab>{
+            title: testTitle,
+            key: 'test',
+            icon: testIcon,
+            disabled: !this.nodeRunTests || this.nodeRunTests?.total === 0
+        }, <Tab>{
+            title: artifactTitle,
+            key: 'artifact',
+            disabled: this.artifactLength === 0
+        }, <Tab>{
+            title: historyTitle,
+            key: 'history'
+        }]
+    }
+
+    selectTab(tab: Tab): void {
+        this.selectedTab = tab;
     }
 
     initVulnerabilitySummary(nodeRun: WorkflowNodeRun): any[] {
diff --git a/ui/src/app/views/workflow/show/workflow.component.ts b/ui/src/app/views/workflow/show/workflow.component.ts
index 7ade176d4..3f2438f37 100644
--- a/ui/src/app/views/workflow/show/workflow.component.ts
+++ b/ui/src/app/views/workflow/show/workflow.component.ts
@@ -1,4 +1,13 @@
-import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
+import {
+    AfterViewInit,
+    ChangeDetectionStrategy,
+    ChangeDetectorRef,
+    Component,
+    OnDestroy,
+    OnInit,
+    TemplateRef,
+    ViewChild
+} from '@angular/core';
 import { ActivatedRoute, Router } from '@angular/router';
 import { TranslateService } from '@ngx-translate/core';
 import { Store } from '@ngxs/store';
@@ -19,6 +28,7 @@ import { WorkflowState, WorkflowStateModel } from 'app/store/workflow.state';
 import cloneDeep from 'lodash-es/cloneDeep';
 import { Subscription } from 'rxjs';
 import { finalize, first, tap } from 'rxjs/operators';
+import { Tab } from 'app/shared/tabs/tabs.component';
 
 @Component({
     selector: 'app-workflow',
@@ -27,7 +37,7 @@ import { finalize, first, tap } from 'rxjs/operators';
     changeDetection: ChangeDetectionStrategy.OnPush
 })
 @AutoUnsubscribe()
-export class WorkflowShowComponent implements OnInit, OnDestroy {
+export class WorkflowShowComponent implements OnInit, OnDestroy, AfterViewInit {
 
     project: Project;
     detailedWorkflow: Workflow;
@@ -51,9 +61,12 @@ export class WorkflowShowComponent implements OnInit, OnDestroy {
     @ViewChild('updateAsCode')
     updateAsCodeModal: AsCodeSaveModalComponent;
 
+    @ViewChild('warnPermission') warnPermission: TemplateRef<any>;
+
     selectedHookRef: string;
 
-    selectedTab = 'workflows';
+    tabs: Array<Tab>;
+    selectedTab: Tab;
 
     permFormLoading = false;
 
@@ -72,6 +85,12 @@ export class WorkflowShowComponent implements OnInit, OnDestroy {
         private _cd: ChangeDetectorRef
     ) { }
 
+    ngAfterViewInit(): void {
+        if (this.detailedWorkflow) {
+            this.initTabs();
+        }
+    }
+
     ngOnDestroy(): void { } // Should be set to use @AutoUnsubscribe with AOT
 
     ngOnInit(): void {
@@ -114,6 +133,8 @@ export class WorkflowShowComponent implements OnInit, OnDestroy {
 
                 this.usageCount = Object.keys(this.detailedWorkflow.usage).reduce((total, key) =>
                     total + this.detailedWorkflow.usage[key].length, 0);
+
+                this.initTabs();
             }
         });
 
@@ -125,7 +146,10 @@ export class WorkflowShowComponent implements OnInit, OnDestroy {
 
         this.qpsSubs = this.activatedRoute.queryParams.subscribe(params => {
             if (params['tab']) {
-                this.selectedTab = params['tab'];
+                let current_tab = this.tabs.find((t) => t.key === params['tab']);
+                if (current_tab) {
+                    this.selectTab(current_tab);
+                }
             }
             if (params['hook_ref']) {
                 this.selectedHookRef = params['hook_ref'];
@@ -143,6 +167,54 @@ export class WorkflowShowComponent implements OnInit, OnDestroy {
             });
     }
 
+    initTabs() {
+        let graphTab = <Tab> {
+            title: 'Workflows',
+            key: 'workflows',
+            icon: 'share alternate',
+            default: true
+        }
+        if (this.previewWorkflow) {
+            graphTab.warningText = this._translate.instant('workflow_preview_mode');
+        }
+        let notificationTab = <Tab>{
+            title: 'Notifications',
+            key: 'notifications',
+            icon: 'alarm'
+        };
+        let permissionTab = <Tab>{
+            title: 'Permissions',
+            key: 'permissions',
+            icon: 'users'
+        }
+        if (this.groupsOutsideOrganization && this.groupsOutsideOrganization.length > 0) {
+            permissionTab.warningTemplate = this.warnPermission
+        }
+
+        this.tabs = new Array<Tab>();
+        this.tabs.push(graphTab, notificationTab, permissionTab)
+
+        if (!this.detailedWorkflow.from_repository)   {
+            this.tabs.push(<Tab>{
+                title: 'Audit',
+                icon: 'history',
+                key: 'audits',
+            });
+        }
+        this.tabs.push(<Tab>{
+            title: 'Usage',
+            icon: 'map signs',
+            key: 'usage'
+        });
+        if (this.detailedWorkflow.permissions.writable) {
+            this.tabs.push( <Tab>{
+                title: 'Advanced',
+                icon: 'graduation',
+                key: 'advanced'
+            });
+        }
+    }
+
     savePreview() {
         this._workflowCoreService.toggleAsCodeEditor({ open: false, save: true });
     }
@@ -151,8 +223,8 @@ export class WorkflowShowComponent implements OnInit, OnDestroy {
         this.direction = this.direction === 'LR' ? 'TB' : 'LR';
     }
 
-    showTab(tab: string): void {
-        this._router.navigateByUrl('/project/' + this.project.key + '/workflow/' + this.detailedWorkflow.name + '?tab=' + tab);
+    selectTab(tab: Tab): void {
+        this.selectedTab = tab;
     }
 
     showAsCodeEditor() {
diff --git a/ui/src/app/views/workflow/show/workflow.html b/ui/src/app/views/workflow/show/workflow.html
index 08778e6b1..43ab252d6 100644
--- a/ui/src/app/views/workflow/show/workflow.html
+++ b/ui/src/app/views/workflow/show/workflow.html
@@ -1,63 +1,16 @@
 <div *ngIf="detailedWorkflow && project; then workflowPart;else loadingPart"></div>
 <ng-template #workflowPart>
     <div class="workflowShow">
-        <div class="CDSmenu">
-            <sm-menu title="" class="pointing secondary">
-                <a sm-item [class.active]="selectedTab === 'workflows'" id="WorkflowGraphTabs"
-                    (click)="showTab('workflows')">
-                    <i class="share alternate icon"></i>
-                    {{'common_workflows' | translate }}
-                    <i class="orange warning sign icon ml5" *ngIf="previewWorkflow" suiPopup
-                        [popupText]="'workflow_preview_mode' | translate" popupPlacement="top center">
-                    </i>
-                </a>
-                <a sm-item [class.active]="selectedTab === 'notifications'" id="WorkflowNotificationTabs"
-                    (click)="showTab('notifications')">
-                    <i class="alarm icon"></i>
-                    {{'common_notifications' | translate }}</a>
-                <a sm-item [class.active]="selectedTab === 'permissions'" id="WorkflowPermissionsTabs"
-                    (click)="showTab('permissions')">
-                    <i class="users icon"></i>
-                    {{'common_permissions' | translate }}
-                    <ng-template let-popup #warnPermission>
-                        Following groups that are not from organization "{{detailedWorkflow.organization}}" can read the
-                        workflow:
-                        <span *ngFor="let gp of groupsOutsideOrganization;let i = index">{{gp.group.name}}
-                            ({{gp.group.organization}}){{ i < groupsOutsideOrganization.length - 1 ? ', ' : '' }}</span>
-                                .
-                    </ng-template>
-                    <i *ngIf="detailedWorkflow && groupsOutsideOrganization && groupsOutsideOrganization.length > 0"
-                        class="exclamation triangle orange icon" suiPopup [popupTemplate]="warnPermission"
-                        popupPlacement="bottom left"></i>
-                </a>
-                <ng-container *ngIf="!detailedWorkflow.from_repository">
-                    <a sm-item [class.active]="selectedTab === 'audits'" id="WorkflowAuditsTabs"
-                        (click)="showTab('audits')">
-                        <i class="history icon"></i>
-                        {{'common_audit' | translate }}
-                    </a>
+        <app-tabs [tabs]="tabs" (onSelect)="selectTab($event)"></app-tabs>
+        <ng-container *ngIf="editMode && !isReadOnly">
+            <nz-button-group class="ascodeButton">
+                <ng-container *ngIf="editModeWorkflowChanged">
+                    <button nz-button nzType="default" (click)="rollbackWorkflow()">{{'common_cancel' | translate }}</button>
                 </ng-container>
-
-                <a sm-item [class.active]="selectedTab === 'usage'" id="WorkflowUsageTabs"
-                    [class.disabled]="usageCount === 0" (click)="usageCount !== 0 && showTab('usage')">
-                    <i class="map signs icon"></i>
-                    {{'common_usage' | translate }}{{ ' (' + usageCount + ')' }}
-                </a>
-                <a sm-item [class.active]="selectedTab === 'advanced'" id="WorkflowAdvancesTabs"
-                    (click)="showTab('advanced')" *ngIf="detailedWorkflow.permissions.writable">
-                    <i class="graduation icon"></i>
-                    {{'common_advanced' | translate }}</a>
-                <div class="ui small buttons" *ngIf="editMode && !isReadOnly">
-                    <ng-container *ngIf="editModeWorkflowChanged">
-                        <button class="ui button" (click)="rollbackWorkflow()">{{'common_cancel' | translate }}</button>
-                        <div class="or"></div>
-                    </ng-container>
-                    <button class="ui green button" [disabled]="!editModeWorkflowChanged"
-                        (click)="saveWorkflow()">{{'btn_save_workflow' | translate }}</button>
-                </div>
-            </sm-menu>
-        </div>
-        <div *ngIf="selectedTab === 'workflows'" class="workflowShowGraph">
+                    <button nz-button nzType="primary" [disabled]="!editModeWorkflowChanged" (click)="saveWorkflow()">{{'btn_save_workflow' | translate }}</button>
+            </nz-button-group>
+        </ng-container>
+        <div *ngIf="selectedTab.key === 'workflows'" class="workflowShowGraph">
             <div class="ui grid">
                 <div class="row">
                     <div class="two wide column"></div>
@@ -117,7 +70,7 @@
             <app-workflow-graph *ngIf="detailedWorkflow" [workflowData]="previewWorkflow || detailedWorkflow"
                 [project]="project" [direction]="direction"></app-workflow-graph>
         </div>
-        <app-scrollview class="scrollview" *ngIf="selectedTab !== 'workflows'" [ngSwitch]="selectedTab">
+        <app-scrollview class="scrollview" *ngIf="selectedTab.key !== 'workflows'" [ngSwitch]="selectedTab.key">
             <div *ngSwitchCase="'notifications'">
                 <app-workflow-notification-list [project]="project" [workflow]="detailedWorkflow" [editMode]="editMode"
                     [readOnly]="isReadOnly"></app-workflow-notification-list>
@@ -173,3 +126,9 @@
     <app-ascode-save-modal [project]="project" [workflow]="detailedWorkflow" [name]="detailedWorkflow.name"
         #updateAsCode></app-ascode-save-modal>
 </ng-container>
+<ng-template #warnPermission>
+    Following groups that are not from organization "{{detailedWorkflow.organization}}" can read the workflow:
+    <span *ngFor="let gp of groupsOutsideOrganization;let i = index">{{gp.group.name}}
+        ({{gp.group.organization}}){{ i < groupsOutsideOrganization.length - 1 ? ', ' : '' }}
+    </span>
+</ng-template>
diff --git a/ui/src/app/views/workflow/show/workflow.scss b/ui/src/app/views/workflow/show/workflow.scss
index bb5009be6..4382c5f07 100644
--- a/ui/src/app/views/workflow/show/workflow.scss
+++ b/ui/src/app/views/workflow/show/workflow.scss
@@ -36,11 +36,9 @@
     }
   }
 
-  sm-menu {
-    .ui.buttons {
-      position: absolute;
-      right: 55px;
-      margin-top: 2px;
-    }
+  .ascodeButton {
+    position: absolute;
+    right: 15px;
+    top: 107px;
   }
 }
diff --git a/ui/src/app/views/workflowv3/run/workflowv3-run-job.component.ts b/ui/src/app/views/workflowv3/run/workflowv3-run-job.component.ts
index 71eca3f37..811ac0b23 100644
--- a/ui/src/app/views/workflowv3/run/workflowv3-run-job.component.ts
+++ b/ui/src/app/views/workflowv3/run/workflowv3-run-job.component.ts
@@ -46,11 +46,11 @@ export class WorkflowV3RunJobComponent implements OnInit, OnChanges {
         private _store: Store
     ) {
         this.tabs = [<Tab>{
-            translate: 'common_logs',
+            title: 'Logs',
             key: 'logs',
             default: true
         }, <Tab>{
-            translate: 'common_variables',
+            title: 'Variables',
             key: 'variables'
         }];
     }
diff --git a/ui/src/app/views/workflowv3/run/workflowv3-run.component.ts b/ui/src/app/views/workflowv3/run/workflowv3-run.component.ts
index 92b810723..fa96e2b40 100644
--- a/ui/src/app/views/workflowv3/run/workflowv3-run.component.ts
+++ b/ui/src/app/views/workflowv3/run/workflowv3-run.component.ts
@@ -58,16 +58,16 @@ export class WorkflowV3RunComponent implements OnInit, OnDestroy {
         private _toast: ToastService
     ) {
         this.tabs = [<Tab>{
-            translate: 'common_problems',
+            title: 'Problems',
             icon: 'exclamation triangle',
             key: 'problems',
             default: true
         }, <Tab>{
-            translate: 'common_infos',
+            title: 'Infos',
             icon: 'exclamation circle',
             key: 'infos'
         }, <Tab>{
-            translate: 'common_results',
+            title: 'Results',
             icon: 'list',
             key: 'results'
         }];
diff --git a/ui/src/assets/i18n/en.json b/ui/src/assets/i18n/en.json
index 03195bef7..9f941d201 100644
--- a/ui/src/assets/i18n/en.json
+++ b/ui/src/assets/i18n/en.json
@@ -51,9 +51,6 @@
   "action_usage_not_used": "This action is not used by your pipelines or actions.",
   "action_usage_warning_pipeline": "The pipeline will not be able to run this action because of missing group permission.",
   "action_usage_warning_action": "The parent action will not be able to run this action because they are not in the same group.",
-  "action_custom": "Custom",
-  "action_builtin": "Builtin",
-  "action_btn_init_from_job": "Init a new action from job",
   "action_help_add_from": "Current action was initialized with data from \"{{jobName}}\" job of \"{{pipelineName}}\" pipeline in \"{{projectKey}}\" project. The data will not be stored until you save the action.",
   "admin_queue_title": "Current CDS jobs queue",
   "cdsapi_loading": "CDS API is starting. Please wait.",
@@ -178,7 +175,6 @@
   "commit_message": "Message",
   "commit_date": "Date",
   "common_id": "ID",
-  "common_action": "Action",
   "common_search": "Search",
   "common_run": "Run",
   "common_created": "Creation date",
@@ -190,24 +186,16 @@
   "common_settings": "Settings",
   "common_admin": "Admin",
   "common_optional": "Optional",
-  "common_audit": "Audit",
-  "common_advanced": "Advanced",
   "common_all": "All",
   "common_read": "Read",
   "common_write": "Write",
   "common_application": "Application",
   "common_application_title": "Application: ",
-  "common_applications": "Applications",
-  "common_artifact": "Artifact",
-  "common_artifacts": "Artifacts",
   "common_close": "Close",
   "common_cancel": "Cancel",
-  "common_commit": "Commit",
-  "common_commits": "Commits",
   "common_date_end": "End",
   "common_date_start": "Start",
   "common_description": "Description",
-  "common_home": "Overview",
   "common_icon": "Icon",
   "common_disabled_f": "Disabled",
   "common_see_documentation": "See documentation",
@@ -218,11 +206,8 @@
   "common_enabled_f": "Enabled",
   "common_environment": "Environment",
   "common_environment_title": "Environment: ",
-  "common_environments": "Environments",
   "common_filter": "Search...",
   "common_format": "Format",
-  "common_keys": "Keys",
-  "common_history": "History",
   "common_loading": "Loading...",
   "common_loading_project": "Loading project...",
   "common_loading_environments": "Loading environments",
@@ -235,20 +220,15 @@
   "common_labels": "Labels",
   "common_value": "Value",
   "common_no": "No",
-  "common_notifications": "Notifications",
+
   "common_parameters": "Parameters",
-  "common_permissions": "Permissions",
   "common_pipeline_start_title": "Start: ",
   "common_pipeline": "Pipeline",
-  "common_pipelines": "Pipelines",
-  "common_integrations": "Integrations",
   "common_integration": "Integration",
   "common_integration_title": "Integration: ",
   "common_project_title": "Project : ",
   "common_requirements": "Requirements",
   "common_status": "Status",
-  "common_test": "Test",
-  "common_tests": "Tests",
   "common_trigger_by": "Triggered by",
   "common_service": "Service",
   "common_component": "Component",
@@ -256,15 +236,12 @@
   "common_session": "Session",
   "common_consumer": "Consumer",
   "common_type": "Type",
-  "common_variables": "Variables",
   "common_version": "Version",
-  "common_warnings": "Warnings",
   "common_workflow": "Workflow",
   "common_workflow_title": "Workflow: ",
   "common_yes": "Yes",
   "common_clone": "Clone",
   "common_click_here": "Click here",
-  "common_workflows": "Workflows",
   "common_stop": "Stop",
   "common_deprecated": "Deprecated",
   "common_no_description": "No description available",
@@ -272,7 +249,6 @@
   "common_no_application": "There is no application linked",
   "common_no_workflow": "There is no workflow linked",
   "common_no_environment": "There is no environment linked",
-  "common_usage": "Usage",
   "common_orientation": "Orientation",
   "common_or": "OR",
   "common_copy_clipboard": "Copy to clipboad",
@@ -295,7 +271,6 @@
   "common_no_favorites": "You didn't add projects or workflows in your bookmarks yet.",
   "common_favorites_all": "All bookmarks",
   "common_file_too_large": "Your file is too large (max 100Ko)",
-  "common_vulnerabilities": "Vulnerabilities",
   "common_no_data": "No data found",
   "common_required": "Required",
   "common_created_by": "Created By",
@@ -310,17 +285,12 @@
   "common_edit_as_code": "Edit as code",
   "common_edit_ui": "Edit with UI",
   "common_migrate": "Migrate",
-  "common_capabilities": "Capabilities",
   "common_help_no_group": "No group can be found, you will not be able to save current resource.",
   "common_find_help": "Maybe you could find some help",
   "common_here": "here",
   "common_deleted": "Deleted with success",
   "common_click_more_informations": "Click here for more information.",
   "common_informations": "Informations",
-  "common_logs": "Logs",
-  "common_problems": "Problems",
-  "common_infos": "Infos",
-  "common_results": "Results",
   "coverage_branches_title": "Covered branches: ",
   "coverage_functions_title": "Covered functions: ",
   "coverage_lines_title": "Covered lines: ",
@@ -425,7 +395,6 @@
   "hook_task_execs_todo": "Task executions to do",
   "hook_task_execs_total": "Total task executions",
   "hook_task_execs": "Executions",
-  "job_add_step": "Add a step",
   "job_delete": "Delete job",
   "maintenance_title": "CDS is currently on maintenance. Thank you for your patience.",
   "monitoring": "Monitoring",
@@ -917,8 +886,6 @@
   "workflow_template_deleted": "Workflow template deleted",
   "workflow_template": "Workflow template",
   "workflow_templates": "Workflow templates",
-  "workflow_template_instances": "Instances",
-  "workflow_template_instances_with_nb": "Instances ({{nb}})",
   "workflow_template_help_line_1": "Your workflow was not generated from a template.",
   "workflow_template_help_line_2": "A workflow template allows you to quickly create then update a workflow and its components.",
   "workflow_template_help_line_3": "You can init a new template from current workflow.",
diff --git a/ui/src/ngzorro.dark.less b/ui/src/ngzorro.dark.less
new file mode 100644
index 000000000..0482cbe20
--- /dev/null
+++ b/ui/src/ngzorro.dark.less
@@ -0,0 +1,5 @@
+@import "node_modules/ng-zorro-antd/ng-zorro-antd.dark.less";
+
+@component-background: #383736;
+
+@layout-body-background: #2d2c2c;


Test patch:
diff --git a/engine/api/application/dao_application_integration_test.go b/engine/api/application/dao_application_integration_test.go
index f90bf770e..bcd7fd7b7 100644
--- a/engine/api/application/dao_application_integration_test.go
+++ b/engine/api/application/dao_application_integration_test.go
@@ -75,4 +75,23 @@ func Test_LoadAllDeploymentAllApps(t *testing.T) {
 	require.Len(t, deps[app2.ID], 1)
 	require.Equal(t, "secret1", deps[app1.ID][pp.ID]["token"].Value)
 	require.Equal(t, "secret2", deps[app2.ID][pp.ID]["token"].Value)
+
+	pf.AdditionalDefaultConfig = sdk.IntegrationConfig{
+		"token": sdk.IntegrationConfigValue{
+			Type:  sdk.IntegrationConfigTypePassword,
+			Value: "my-secret-token",
+		},
+		"bar": sdk.IntegrationConfigValue{
+			Type:  sdk.IntegrationConfigTypeString,
+			Value: "foo",
+		},
+	}
+
+	test.NoError(t, integration.UpdateModel(context.TODO(), db, &pf))
+
+	st, err := application.LoadDeploymentStrategies(context.TODO(), db, app1.ID, false)
+	require.NoError(t, err)
+	require.Len(t, st, 1)
+	require.Equal(t, st[pf.Name]["token"].Value, "**********")
+	require.Equal(t, st[pf.Name]["bar"].Value, "foo")
 }
diff --git a/engine/api/application_deployment_test.go b/engine/api/application_deployment_test.go
index 5fb5de93d..739139866 100644
--- a/engine/api/application_deployment_test.go
+++ b/engine/api/application_deployment_test.go
@@ -353,7 +353,7 @@ func Test_postApplicationDeploymentStrategyConfigHandlerAsProvider(t *testing.T)
 	})
 	test.NoError(t, err)
 
-	cfg, err := application.LoadDeploymentStrategies(api.mustDB(), app.ID, true)
+	cfg, err := application.LoadDeploymentStrategies(context.TODO(), api.mustDB(), app.ID, true)
 	test.NoError(t, err)
 
 	var assertCfg = func(key string, cfg sdk.IntegrationConfig, expected sdk.IntegrationConfigValue) {
diff --git a/engine/api/user_test.go b/engine/api/user_test.go
index bf2a7e049..b38b09051 100644
--- a/engine/api/user_test.go
+++ b/engine/api/user_test.go
@@ -65,7 +65,6 @@ func Test_putUserHandler(t *testing.T) {
 	assets.DeleteAdmins(t, db)
 
 	initial, jwtInitialRaw := assets.InsertLambdaUser(t, db)
-	initialNewUsername := sdk.RandomString(10)
 	initialNewFullname := sdk.RandomString(10)
 	admin1, jwtAdmin1Raw := assets.InsertAdminUser(t, db)
 	admin2, jwtAdmin2Raw := assets.InsertAdminUser(t, db)
@@ -79,43 +78,32 @@ func Test_putUserHandler(t *testing.T) {
 		ExpectedStatus int
 	}{
 		{
-			Name:           "A lambda user can change its username or fullname",
+			Name:           "A lambda user can't change username but can change fullname",
 			JWT:            jwtInitialRaw,
 			TargetUsername: initial.Username,
 			Data: sdk.AuthentifiedUser{
-				Username: initialNewUsername,
+				Username: sdk.RandomString(10),
 				Fullname: initialNewFullname,
 				Ring:     initial.Ring,
 			},
 			Expected: sdk.AuthentifiedUser{
-				Username: initialNewUsername,
+				Username: initial.Username,
 				Fullname: initialNewFullname,
 				Ring:     initial.Ring,
 			},
 			ExpectedStatus: http.StatusOK,
 		},
-		{
-			Name:           "A lambda user can rename to an existing username",
-			JWT:            jwtInitialRaw,
-			TargetUsername: initialNewUsername,
-			Data: sdk.AuthentifiedUser{
-				Username: admin1.Username,
-				Fullname: initialNewFullname,
-				Ring:     initial.Ring,
-			},
-			ExpectedStatus: http.StatusBadRequest,
-		},
 		{
 			Name:           "A lambda user can't change its ring",
 			JWT:            jwtInitialRaw,
-			TargetUsername: initialNewUsername,
+			TargetUsername: initial.Username,
 			Data: sdk.AuthentifiedUser{
-				Username: initialNewUsername,
+				Username: initial.Username,
 				Fullname: initialNewFullname,
 				Ring:     sdk.UserRingAdmin,
 			},
 			Expected: sdk.AuthentifiedUser{
-				Username: initialNewUsername,
+				Username: initial.Username,
 				Fullname: initialNewFullname,
 				Ring:     initial.Ring,
 			},
@@ -124,14 +112,14 @@ func Test_putUserHandler(t *testing.T) {
 		{
 			Name:           "A admin user can change the ring of a user",
 			JWT:            jwtAdmin1Raw,
-			TargetUsername: initialNewUsername,
+			TargetUsername: initial.Username,
 			Data: sdk.AuthentifiedUser{
-				Username: initialNewUsername,
+				Username: initial.Username,
 				Fullname: initialNewFullname,
 				Ring:     sdk.UserRingMaintainer,
 			},
 			Expected: sdk.AuthentifiedUser{
-				Username: initialNewUsername,
+				Username: initial.Username,
 				Fullname: initialNewFullname,
 				Ring:     sdk.UserRingMaintainer,
 			},
@@ -167,15 +155,15 @@ func Test_putUserHandler(t *testing.T) {
 		{
 			Name:           "A lambda user can't change its organization",
 			JWT:            jwtInitialRaw,
-			TargetUsername: initialNewUsername,
+			TargetUsername: initial.Username,
 			Data: sdk.AuthentifiedUser{
-				Username:     initialNewUsername,
+				Username:     initial.Username,
 				Fullname:     initialNewFullname,
 				Ring:         sdk.UserRingMaintainer,
 				Organization: "my-org",
 			},
 			Expected: sdk.AuthentifiedUser{
-				Username:     initialNewUsername,
+				Username:     initial.Username,
 				Fullname:     initialNewFullname,
 				Ring:         sdk.UserRingMaintainer,
 				Organization: "",
@@ -185,15 +173,15 @@ func Test_putUserHandler(t *testing.T) {
 		{
 			Name:           "A admin user can set user organization",
 			JWT:            jwtAdmin2Raw,
-			TargetUsername: initialNewUsername,
+			TargetUsername: initial.Username,
 			Data: sdk.AuthentifiedUser{
-				Username:     initialNewUsername,
+				Username:     initial.Username,
 				Fullname:     initialNewFullname,
 				Ring:         sdk.UserRingMaintainer,
 				Organization: "my-org",
 			},
 			Expected: sdk.AuthentifiedUser{
-				Username:     initialNewUsername,
+				Username:     initial.Username,
 				Fullname:     initialNewFullname,
 				Ring:         sdk.UserRingMaintainer,
 				Organization: "my-org",
@@ -203,9 +191,9 @@ func Test_putUserHandler(t *testing.T) {
 		{
 			Name:           "A admin user can't change user organization",
 			JWT:            jwtAdmin2Raw,
-			TargetUsername: initialNewUsername,
+			TargetUsername: initial.Username,
 			Data: sdk.AuthentifiedUser{
-				Username:     initialNewUsername,
+				Username:     initial.Username,
 				Fullname:     initialNewFullname,
 				Ring:         sdk.UserRingMaintainer,
 				Organization: "my-other-org",
diff --git a/sdk/common_test.go b/sdk/common_test.go
index 55d672c74..1fbe69b51 100644
--- a/sdk/common_test.go
+++ b/sdk/common_test.go
@@ -46,3 +46,83 @@ func TestPathIsAbs(t *testing.T) {
 	assert.True(t, PathIsAbs(`/tmp`))
 	assert.False(t, PathIsAbs(`tmp`))
 }
+
+func TestCleanPath(t *testing.T) {
+	type args struct {
+		path string
+	}
+	tests := []struct {
+		name string
+		args args
+		want string
+	}{
+		{
+			name: "test1",
+			args: args{
+				path: "../../foobar",
+			},
+			want: "foobar",
+		},
+		{
+			name: "test1",
+			args: args{
+				path: "./foobar",
+			},
+			want: "foobar",
+		},
+		{
+			name: "test2",
+			args: args{
+				path: "/foo/bar/biz",
+			},
+			want: "/foo/bar/biz",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := CleanPath(tt.args.path); got != tt.want {
+				t.Errorf("CleanPath() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func TestNoPath(t *testing.T) {
+	type args struct {
+		path string
+	}
+	tests := []struct {
+		name string
+		args args
+		want string
+	}{
+		{
+			name: "test1",
+			args: args{
+				path: "/foo/bar/biz",
+			},
+			want: "biz",
+		},
+		{
+			name: "test2",
+			args: args{
+				path: "",
+			},
+			want: "",
+		},
+		{
+			name: "test3",
+			args: args{
+				path: ".",
+			},
+			want: ".",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := NoPath(tt.args.path); got != tt.want {
+				t.Errorf("NoPath() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}