Repository: ovh/cds
Pull request #6185 (2022-05-31 07:31:17)

refactor(ui): remove sm-modal from UI + fix requirement autocomplete
1. Description
1. Related issues
1. About tests
1. Mentions

@ovh/cds


ovh-cds: 
CDS Report build-all-cds#17440.0 ✘  
* Build
  * Build and Package All ✘  
  * Build Debpacker ✔ 
  * Build OS-Ansible-Inventory ✔ 
  * Build SMTPMock ✔ 
  * Lint UI Translate ✘  
  * Test CLI ✔ 
  * Test Engine ✔ 
  * Test SDK ✔ 
  * Test SDK Rust ✔ 
  * Test UI ✘  

ovh-cds: 
CDS Report build-all-cds#17441.0 ✘  
* Build
  * Build and Package All ✔ 
  * Build Debpacker ✔ 
  * Build OS-Ansible-Inventory ✔ 
  * Build SMTPMock ✔ 
  * Lint UI Translate ✔ 
  * Test CLI ✔ 
  * Test Engine ✔ 
  * Test SDK ✔ 
  * Test SDK Rust ✔ 
  * Test UI ✘  

sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=ovh_cds&pullRequest=6185)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=ovh_cds&pullRequest=6185&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=ovh_cds&pullRequest=6185&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=ovh_cds&pullRequest=6185&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=ovh_cds&pullRequest=6185&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=ovh_cds&pullRequest=6185&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=ovh_cds&pullRequest=6185&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=ovh_cds&pullRequest=6185&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=ovh_cds&pullRequest=6185&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=ovh_cds&pullRequest=6185&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=ovh_cds&pullRequest=6185&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=ovh_cds&pullRequest=6185&resolved=false&types=CODE_SMELL) [10 Code Smells](https://sonarcloud.io/project/issues?id=ovh_cds&pullRequest=6185&resolved=false&types=CODE_SMELL)

[![No Coverage information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo-16px.png 'No Coverage information')](https://sonarcloud.io/component_measures?id=ovh_cds&pullRequest=6185&metric=coverage&view=list) No Coverage information  
[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=ovh_cds&pullRequest=6185&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=ovh_cds&pullRequest=6185&metric=new_duplicated_lines_density&view=list)



Patch:
diff --git a/contrib/integrations/artifactory/plugin-artifactory-release/main.go b/contrib/integrations/artifactory/plugin-artifactory-release/main.go
index f8a9f5918..034dec384 100644
--- a/contrib/integrations/artifactory/plugin-artifactory-release/main.go
+++ b/contrib/integrations/artifactory/plugin-artifactory-release/main.go
@@ -86,6 +86,8 @@ func (e *artifactoryReleasePlugin) Run(ctx context.Context, opts *integrationplu
 		return fail("unable to list run results: %v", err)
 	}
 
+	fmt.Printf("Found %d run results\n", len(runResult))
+
 	log.SetLogger(log.NewLogger(log.INFO, os.Stdout))
 	if distributionURL == "" {
 		fmt.Printf("Using %s to release\n", artifactoryURL)
@@ -137,8 +139,15 @@ func (e *artifactoryReleasePlugin) Run(ctx context.Context, opts *integrationplu
 				break
 			}
 		}
+		name, err := r.ComputeName()
+		if err != nil {
+			return fail("unable to read result %s: %v", r.ID, err)
+		}
 		if skip {
+			fmt.Printf("Result %q skipped\n", name)
 			continue
+		} else {
+			fmt.Printf("Result %q to promote\n", name)
 		}
 		switch rData.RepoType {
 		case "docker":
diff --git a/engine/api/workflow/execute_node_run.go b/engine/api/workflow/execute_node_run.go
index 7d9019acb..831218159 100644
--- a/engine/api/workflow/execute_node_run.go
+++ b/engine/api/workflow/execute_node_run.go
@@ -121,6 +121,8 @@ func executeNodeRun(ctx context.Context, db gorpmapper.SqlExecutorWithTx, store
 		return nil, sdk.WrapError(err, "unable to load workflow run with id %d", workflowNodeRun.WorkflowRunID)
 	}
 
+	n := wr.Workflow.WorkflowData.NodeByID(workflowNodeRun.WorkflowNodeID)
+
 	report := new(ProcessorReport)
 	defer func(wnr *sdk.WorkflowNodeRun) {
 		report.Add(ctx, *wnr)
@@ -133,10 +135,12 @@ func executeNodeRun(ctx context.Context, db gorpmapper.SqlExecutorWithTx, store
 
 	var newStatus = workflowNodeRun.Status
 
-	// If no stages ==> success
-	if len(workflowNodeRun.Stages) == 0 {
+	// For join and fork, reuse the status from parent
+	if n.Type == sdk.NodeTypeJoin || n.Type == sdk.NodeTypeFork {
+		parentStatus := sdk.ParameterFind(workflowNodeRun.BuildParameters, "cds.status")
+		newStatus = parentStatus.Value
+	} else if len(workflowNodeRun.Stages) == 0 { // If no stages ==> success
 		newStatus = sdk.StatusSuccess
-		workflowNodeRun.Done = time.Now()
 	}
 
 	stagesTerminated := 0
diff --git a/engine/api/workflow/workflow_run_results.go b/engine/api/workflow/workflow_run_results.go
index 76da85c15..66a373c1e 100644
--- a/engine/api/workflow/workflow_run_results.go
+++ b/engine/api/workflow/workflow_run_results.go
@@ -354,42 +354,38 @@ func insertResult(tx gorpmapper.SqlExecutorWithTx, runResult *sdk.WorkflowRunRes
 	return nil
 }
 
-func getAll(ctx context.Context, db gorp.SqlExecutor, query gorpmapping.Query) ([]sdk.WorkflowRunResult, error) {
+func getAll(ctx context.Context, db gorp.SqlExecutor, query gorpmapping.Query) (sdk.WorkflowRunResults, error) {
 	var dbResults []dbRunResult
 	if err := gorpmapping.GetAll(ctx, db, query, &dbResults); err != nil {
 		return nil, err
 	}
-	results := make([]sdk.WorkflowRunResult, 0, len(dbResults))
+	results := make(sdk.WorkflowRunResults, 0, len(dbResults))
 	for _, r := range dbResults {
 		results = append(results, sdk.WorkflowRunResult(r))
 	}
 	return results, nil
 }
 
-func LoadRunResultsByRunID(ctx context.Context, db gorp.SqlExecutor, runID int64) ([]sdk.WorkflowRunResult, error) {
-	dbQuery := `
-	WITH allResults AS (
-		SELECT data->>'name' AS name, sub_num, id
-		FROM workflow_run_result
-		WHERE workflow_run_id = $1
-	),
-	deduplication AS (
-		SELECT distinct on (name) *
-		FROM allResults
-		ORDER BY name, sub_num DESC
-	)
-	SELECT * FROM workflow_run_result WHERE id IN (SELECT id FROM deduplication);`
-	query := gorpmapping.NewQuery(dbQuery).Args(runID)
+func LoadRunResultsByRunID(ctx context.Context, db gorp.SqlExecutor, runID int64) (sdk.WorkflowRunResults, error) {
+	query := gorpmapping.NewQuery("SELECT * FROM workflow_run_result WHERE workflow_run_id = $1 ORDER BY sub_num DESC").Args(runID)
 	return getAll(ctx, db, query)
 }
 
-func LoadRunResultsByNodeRunID(ctx context.Context, db gorp.SqlExecutor, nodeRunID int64) ([]sdk.WorkflowRunResult, error) {
-	query := gorpmapping.NewQuery("SELECT * FROM workflow_run_result where workflow_node_run_id = $1").Args(nodeRunID)
-	return getAll(ctx, db, query)
+func LoadRunResultsByRunIDUnique(ctx context.Context, db gorp.SqlExecutor, runID int64) (sdk.WorkflowRunResults, error) {
+	query := gorpmapping.NewQuery("SELECT * FROM workflow_run_result WHERE workflow_run_id = $1 ORDER BY sub_num DESC").Args(runID)
+	rs, err := getAll(ctx, db, query)
+	if err != nil {
+		return nil, err
+	}
+	return rs.Unique()
 }
 
-func LoadRunResultsByRunIDAndType(ctx context.Context, db gorp.SqlExecutor, runID int64, t sdk.WorkflowRunResultType) ([]sdk.WorkflowRunResult, error) {
-	query := gorpmapping.NewQuery("SELECT * FROM workflow_run_result where workflow_run_id = $1 AND type = $2").Args(runID, t)
+func LoadRunResultsByNodeRunID(ctx context.Context, db gorp.SqlExecutor, nodeRunID int64) (sdk.WorkflowRunResults, error) {
+	query := gorpmapping.NewQuery("SELECT * FROM workflow_run_result WHERE workflow_node_run_id = $1").Args(nodeRunID)
 	return getAll(ctx, db, query)
+}
 
+func LoadRunResultsByRunIDAndType(ctx context.Context, db gorp.SqlExecutor, runID int64, t sdk.WorkflowRunResultType) (sdk.WorkflowRunResults, error) {
+	query := gorpmapping.NewQuery("SELECT * FROM workflow_run_result WHERE workflow_run_id = $1 AND type = $2").Args(runID, t)
+	return getAll(ctx, db, query)
 }
diff --git a/engine/api/workflow_run.go b/engine/api/workflow_run.go
index 4857a4559..eaae8b285 100644
--- a/engine/api/workflow_run.go
+++ b/engine/api/workflow_run.go
@@ -1348,7 +1348,7 @@ func (api *API) getWorkflowRunResultsHandler() service.Handler {
 			return sdk.WrapError(err, "unable to load workflow run for workflow %s and number %d", name, number)
 		}
 
-		results, err := workflow.LoadRunResultsByRunID(ctx, api.mustDB(), wr.ID)
+		results, err := workflow.LoadRunResultsByRunIDUnique(ctx, api.mustDB(), wr.ID)
 		if err != nil {
 			return err
 		}
diff --git a/engine/cdn/cdn_file.go b/engine/cdn/cdn_file.go
index 3a7ddad7d..fd6aba0fe 100644
--- a/engine/cdn/cdn_file.go
+++ b/engine/cdn/cdn_file.go
@@ -152,7 +152,9 @@ func (s *Service) storeFile(ctx context.Context, sig cdn.Signature, reader io.Re
 			switch runResultApiRef.RunResultType {
 			case sdk.WorkflowRunResultTypeArtifact:
 				result = sdk.WorkflowRunResultArtifact{
-					Name:       apiRef.ToFilename(),
+					WorkflowRunResultArtifactCommon: sdk.WorkflowRunResultArtifactCommon{
+						Name: apiRef.ToFilename(),
+					},
 					Size:       it.Size,
 					MD5:        it.MD5,
 					CDNRefHash: it.APIRefHash,
@@ -160,7 +162,9 @@ func (s *Service) storeFile(ctx context.Context, sig cdn.Signature, reader io.Re
 				}
 			case sdk.WorkflowRunResultTypeCoverage:
 				result = sdk.WorkflowRunResultCoverage{
-					Name:       apiRef.ToFilename(),
+					WorkflowRunResultArtifactCommon: sdk.WorkflowRunResultArtifactCommon{
+						Name: apiRef.ToFilename(),
+					},
 					Size:       it.Size,
 					MD5:        it.MD5,
 					CDNRefHash: it.APIRefHash,
diff --git a/engine/sql/api/243_delete_install_key_action.sql b/engine/sql/api/243_delete_install_key_action.sql
new file mode 100644
index 000000000..c5322ce84
--- /dev/null
+++ b/engine/sql/api/243_delete_install_key_action.sql
@@ -0,0 +1,5 @@
+-- +migrate Up
+DELETE FROM "action" WHERE name = 'InstallKey' and type = 'Builtin';
+
+-- +migrate Down
+SELECT 1;
diff --git a/engine/worker/cmd_run_result.go b/engine/worker/cmd_run_result.go
index 77c1d9153..f1bda828f 100644
--- a/engine/worker/cmd_run_result.go
+++ b/engine/worker/cmd_run_result.go
@@ -68,7 +68,9 @@ func addStaticFileRunResultCmd() func(cmd *cobra.Command, args []string) {
 		}
 
 		payload := sdk.WorkflowRunResultStaticFile{
-			Name:      name,
+			WorkflowRunResultArtifactCommon: sdk.WorkflowRunResultArtifactCommon{
+				Name: name,
+			},
 			RemoteURL: remoteURL.String(),
 		}
 		data, _ := json.Marshal(payload)
@@ -109,7 +111,9 @@ func addArtifactManagerRunResultCmd() func(cmd *cobra.Command, args []string) {
 		}
 
 		payload := sdk.WorkflowRunResultArtifactManager{
-			Name:     fileName,
+			WorkflowRunResultArtifactCommon: sdk.WorkflowRunResultArtifactCommon{
+				Name: fileName,
+			},
 			Perm:     0,
 			Path:     filePath,
 			RepoName: repositoryName,
diff --git a/engine/worker/internal/action/builtin_artifact_upload.go b/engine/worker/internal/action/builtin_artifact_upload.go
index 32ef30fc6..4b25c8c76 100644
--- a/engine/worker/internal/action/builtin_artifact_upload.go
+++ b/engine/worker/internal/action/builtin_artifact_upload.go
@@ -250,7 +250,9 @@ func addWorkflowRunResult(ctx context.Context, wk workerruntime.Runtime, filePat
 	}
 
 	data := sdk.WorkflowRunResultArtifactManager{
-		Name:     uploadResult.Outputs[sdk.ArtifactUploadPluginOutputPathFileName],
+		WorkflowRunResultArtifactCommon: sdk.WorkflowRunResultArtifactCommon{
+			Name: uploadResult.Outputs[sdk.ArtifactUploadPluginOutputPathFileName],
+		},
 		Perm:     uint32(perm),
 		RepoName: uploadResult.Outputs[sdk.ArtifactUploadPluginOutputPathRepoName],
 		Path:     uploadResult.Outputs[sdk.ArtifactUploadPluginOutputPathFilePath],
diff --git a/engine/worker/internal/action/grpc_plugin.go b/engine/worker/internal/action/grpc_plugin.go
index 68c6360f9..154b74503 100644
--- a/engine/worker/internal/action/grpc_plugin.go
+++ b/engine/worker/internal/action/grpc_plugin.go
@@ -84,9 +84,6 @@ func RunGRPCPlugin(ctx context.Context, actionName string, params []sdk.Paramete
 		if (p.Type == sdk.KeyPGPParameter || p.Type == sdk.KeySSHParameter) && strings.HasSuffix(p.Name, ".priv") {
 			continue
 		}
-		if p.Type == sdk.KeyParameter && !strings.HasSuffix(p.Name, ".pub") {
-			continue
-		}
 		envs = append(envs, sdk.EnvVartoENV(p)...)
 		envName := strings.Replace(p.Name, ".", "_", -1)
 		envName = strings.ToUpper(envName)
diff --git a/engine/worker/internal/builtin.go b/engine/worker/internal/builtin.go
index 4bd4f61bc..fc466cb97 100644
--- a/engine/worker/internal/builtin.go
+++ b/engine/worker/internal/builtin.go
@@ -26,7 +26,6 @@ func init() {
 	mapBuiltinActions[sdk.CheckoutApplicationAction] = action.RunCheckoutApplication
 	mapBuiltinActions[sdk.DeployApplicationAction] = action.RunDeployApplication
 	mapBuiltinActions[sdk.CoverageAction] = action.RunParseCoverageResultAction
-	mapBuiltinActions[sdk.InstallKeyAction] = action.RunInstallKey
 	mapBuiltinActions[sdk.PushBuildInfo] = action.PushBuildInfo
 }
 
diff --git a/engine/worker/internal/run.go b/engine/worker/internal/run.go
index 196c86500..ef0a9540c 100644
--- a/engine/worker/internal/run.go
+++ b/engine/worker/internal/run.go
@@ -6,7 +6,6 @@ import (
 	"context"
 	"crypto/md5"
 	"fmt"
-	"io/fs"
 	"os"
 	"os/exec"
 	"os/user"
@@ -738,64 +737,82 @@ func (w *CurrentWorker) setupHooks(ctx context.Context, jobInfo sdk.WorkflowNode
 				// The error contains 'Executable file not found', the capa is not on the worker
 				continue
 			}
+
 			hookFilename := fmt.Sprintf("%d-%s-%s", hookConfig.Priority, integrationName, slug.Convert(hookConfig.Label))
-			hookFilePath := path.Join(workingDir, "setup", hookFilename)
-			log.Info(ctx, "setting up hook %q", hookFilePath)
 
-			hookFile, err := fs.Create(hookFilePath)
+			w.hooks = append(w.hooks, workerHook{
+				Config:       hookConfig,
+				SetupPath:    path.Join(workingDir, "setup", hookFilename),
+				TeardownPath: path.Join(workingDir, "teardown", hookFilename),
+			})
+		}
+
+		for _, h := range w.hooks {
+			infos := []sdk.SpawnInfo{{
+				RemoteTime: time.Now(),
+				Message:    sdk.SpawnMsg{ID: sdk.MsgSpawnInfoWorkerHookSetup.ID, Args: []interface{}{h.Config.Label}},
+			}}
+			if err := w.Client().QueueJobSendSpawnInfo(ctx, w.currentJob.wJob.ID, infos); err != nil {
+				return sdk.WrapError(err, "cannot record QueueJobSendSpawnInfo for job (err spawn): %d", w.currentJob.wJob.ID)
+			}
+
+			log.Info(ctx, "setting up hook at %q", h.SetupPath)
+
+			hookFile, err := fs.Create(h.SetupPath)
 			if err != nil {
-				return errors.Errorf("unable to open hook file %q in %q: %v", hookFilePath, w.basedir.Name(), err)
+				return errors.Errorf("unable to open hook file %q in %q: %v", h.SetupPath, w.basedir.Name(), err)
 			}
-			if _, err := hookFile.WriteString(hookConfig.Setup); err != nil {
+			if _, err := hookFile.WriteString(h.Config.Setup); err != nil {
 				_ = hookFile.Close
-				return errors.Errorf("unable to setup hook %q: %v", hookFilePath, err)
+				return errors.Errorf("unable to setup hook %q: %v", h.SetupPath, err)
 			}
 			if err := hookFile.Close(); err != nil {
-				return errors.Errorf("unable to setup hook %q: %v", hookFilePath, err)
+				return errors.Errorf("unable to setup hook %q: %v", h.SetupPath, err)
 			}
 
-			hookFilePath = path.Join(workingDir, "teardown", hookFilename)
-			hookFile, err = fs.Create(hookFilePath)
+			hookFile, err = fs.Create(h.TeardownPath)
 			if err != nil {
-				return errors.Errorf("unable to open hook file %q: %v", hookFilePath, err)
+				return errors.Errorf("unable to open hook file %q: %v", h.TeardownPath, err)
 			}
-			if _, err := hookFile.WriteString(hookConfig.Teardown); err != nil {
+			if _, err := hookFile.WriteString(h.Config.Teardown); err != nil {
 				_ = hookFile.Close
-				return errors.Errorf("unable to setup hook %q: %v", hookFilePath, err)
+				return errors.Errorf("unable to setup hook %q: %v", h.TeardownPath, err)
 			}
 			if err := hookFile.Close(); err != nil {
-				return errors.Errorf("unable to setup hook %q: %v", hookFilePath, err)
+				return errors.Errorf("unable to setup hook %q: %v", h.TeardownPath, err)
 			}
 		}
 	}
 	return nil
 }
 
-func (w *CurrentWorker) executeHooksSetup(ctx context.Context, basedir afero.Fs, workingDir string) error {
+func (w *CurrentWorker) executeHooksSetup(ctx context.Context, fs afero.Fs, workingDir string) error {
 	if strings.EqualFold(runtime.GOOS, "windows") {
 		log.Warn(ctx, "hooks are not supported on windows")
 		return nil
 	}
 
 	var result = make(map[string]string)
-	var setupDir = path.Join(workingDir, "setup")
 
-	var absPath string
-	if x, ok := basedir.(*afero.BasePathFs); ok {
-		absPath, _ = x.RealPath(setupDir)
-		absPath, _ = filepath.Abs(path.Dir(absPath))
+	basedir, ok := fs.(*afero.BasePathFs)
+	if !ok {
+		return sdk.WithStack(fmt.Errorf("invalid given basedir"))
 	}
 
-	setupDir = filepath.Join(absPath, filepath.Base(setupDir))
-
 	workerEnv := w.Environ()
 
-	err := filepath.Walk(setupDir, func(filepath string, info os.FileInfo, err error) error {
+	for _, h := range w.hooks {
+		filepath, err := basedir.RealPath(h.SetupPath)
 		if err != nil {
-			return err
+			return sdk.WrapError(err, "cannot get real path for: %s", h.SetupPath)
 		}
-		if info.IsDir() {
-			return nil
+
+		infos := []sdk.SpawnInfo{{
+			RemoteTime: time.Now(),
+			Message:    sdk.SpawnMsg{ID: sdk.MsgSpawnInfoWorkerHookRun.ID, Args: []interface{}{h.Config.Label}},
+		}}
+		if err := w.Client().QueueJobSendSpawnInfo(ctx, w.currentJob.wJob.ID, infos); err != nil {
+			return sdk.WrapError(err, "cannot record QueueJobSendSpawnInfo for job (err spawn): %d", w.currentJob.wJob.ID)
 		}
 
 		str := fmt.Sprintf("source %s ; echo '<<<ENVIRONMENT>>>' ; env", filepath)
@@ -821,22 +838,35 @@ func (w *CurrentWorker) executeHooksSetup(ctx context.Context, basedir afero.Fs,
 				}
 			}
 		}
-		return nil
-	})
+	}
 	w.currentJob.envFromHooks = result
-	return errors.WithStack(err)
+	return nil
 }
 
-func (w *CurrentWorker) executeHooksTeardown(_ context.Context, basedir afero.Fs, workingDir string) error {
-	err := afero.Walk(basedir, path.Join(workingDir, "setup"), func(path string, info fs.FileInfo, err error) error {
-		if info.IsDir() {
-			return nil
+func (w *CurrentWorker) executeHooksTeardown(ctx context.Context, fs afero.Fs, workingDir string) error {
+	basedir, ok := fs.(*afero.BasePathFs)
+	if !ok {
+		return sdk.WithStack(fmt.Errorf("invalid given basedir"))
+	}
+
+	for _, h := range w.hooks {
+		filepath, err := basedir.RealPath(h.SetupPath)
+		if err != nil {
+			return sdk.WrapError(err, "cannot get real path for: %s", h.SetupPath)
+		}
+
+		infos := []sdk.SpawnInfo{{
+			RemoteTime: time.Now(),
+			Message:    sdk.SpawnMsg{ID: sdk.MsgSpawnInfoWorkerHookRunTeardown.ID, Args: []interface{}{h.Config.Label}},
+		}}
+		if err := w.Client().QueueJobSendSpawnInfo(ctx, w.currentJob.wJob.ID, infos); err != nil {
+			return sdk.WrapError(err, "cannot record QueueJobSendSpawnInfo for job (err spawn): %d", w.currentJob.wJob.ID)
 		}
-		cmd := exec.Command("bash", "-c", path)
+
+		cmd := exec.Command("bash", "-c", filepath)
 		if output, err := cmd.CombinedOutput(); err != nil {
 			return errors.WithMessage(err, w.blur.String(string(output)))
 		}
-		return nil
-	})
-	return err
+	}
+	return nil
 }
diff --git a/engine/worker/internal/types.go b/engine/worker/internal/types.go
index 88e96a5eb..2208e92f6 100644
--- a/engine/worker/internal/types.go
+++ b/engine/worker/internal/types.go
@@ -69,6 +69,13 @@ type CurrentWorker struct {
 	}
 	client cdsclient.WorkerInterface
 	blur   *sdk.Blur
+	hooks  []workerHook
+}
+
+type workerHook struct {
+	Config       sdk.WorkerHookSetupTeardownScripts
+	SetupPath    string
+	TeardownPath string
 }
 
 // BuiltInAction defines builtin action signature
@@ -304,9 +311,6 @@ func (wk *CurrentWorker) Environ() []string {
 		if strings.HasPrefix(p.Name, "cds.key.") && strings.HasSuffix(p.Name, ".priv") {
 			continue
 		}
-		if p.Type == sdk.KeyParameter && !strings.HasSuffix(p.Name, ".pub") {
-			continue
-		}
 
 		newEnv = append(newEnv, sdk.EnvVartoENV(p)...)
 
diff --git a/sdk/action/action.go b/sdk/action/action.go
index 24f708f28..f9c85fd25 100644
--- a/sdk/action/action.go
+++ b/sdk/action/action.go
@@ -20,7 +20,6 @@ var List = []Manifest{
 	DeployApplication,
 	GitClone,
 	GitTag,
-	InstallKey,
 	JUnit,
 	Promote,
 	ReleaseVCS,
diff --git a/sdk/action/install-key.go b/sdk/action/install-key.go
deleted file mode 100644
index a0fb543ab..000000000
--- a/sdk/action/install-key.go
+++ /dev/null
@@ -1,50 +0,0 @@
-package action
-
-import (
-	"github.com/ovh/cds/sdk"
-	"github.com/ovh/cds/sdk/exportentities"
-)
-
-var installKeyExample = exportentities.StepInstallKey("proj-mykey")
-
-// InstallKey action definition.
-var InstallKey = Manifest{
-	Action: sdk.Action{
-		Name: sdk.InstallKeyAction,
-		Description: `CDS Builtin Action.
-Checkout a repository into a new directory.
-
-This action installs a SSH/PGP key generated in CDS. And if it's a SSH key it will export in the environment variable named PKEY the path to the private key.
-For example to use with 'ssh -i $PKEY'
-`,
-		Parameters: []sdk.Parameter{
-			{
-				Name:        "key",
-				Value:       "",
-				Description: `Set the key to install in your workspace`,
-				Type:        sdk.KeyParameter,
-			},
-			{
-				Name:        "file",
-				Value:       "",
-				Description: `Write key to destination file`,
-				Type:        sdk.StringParameter,
-				Advanced:    true,
-			},
-		},
-	},
-	Example: exportentities.PipelineV1{
-		Version: exportentities.PipelineVersion1,
-		Name:    "Pipeline1",
-		Stages:  []string{"Stage1"},
-		Jobs: []exportentities.Job{{
-			Name:  "Job1",
-			Stage: "Stage1",
-			Steps: []exportentities.Step{
-				{
-					InstallKey: &installKeyExample,
-				},
-			},
-		}},
-	},
-}
diff --git a/sdk/exportentities/step.go b/sdk/exportentities/step.go
index 1a5aec161..6765e735d 100644
--- a/sdk/exportentities/step.go
+++ b/sdk/exportentities/step.go
@@ -470,9 +470,6 @@ func (s Step) IsValid() bool {
 	if s.isCheckout() {
 		count++
 	}
-	if s.isInstallKey() {
-		count++
-	}
 	if s.isDeploy() {
 		count++
 	}
@@ -515,8 +512,6 @@ func (s Step) toAction() (*sdk.Action, error) {
 		a, err = s.asRelease()
 	} else if s.isCheckout() {
 		a = s.asCheckoutApplication()
-	} else if s.isInstallKey() {
-		a = s.asInstallKey()
 	} else if s.isDeploy() {
 		a = s.asDeployApplication()
 	} else if s.isCoverage() {
@@ -609,49 +604,6 @@ func (s Step) asCheckoutApplication() sdk.Action {
 	}
 }
 
-func (s Step) asInstallKey() sdk.Action {
-	switch v := (*s.InstallKey).(type) {
-	case string:
-		return sdk.Action{
-			Name: sdk.InstallKeyAction,
-			Type: sdk.BuiltinAction,
-			Parameters: []sdk.Parameter{
-				{
-					Name:  "key",
-					Value: v,
-					Type:  sdk.KeyParameter,
-				},
-			},
-		}
-	case map[string]string:
-		params := make([]sdk.Parameter, 0, len(v))
-
-		for paramName, paramValue := range v {
-			paramType := sdk.StringParameter
-			if paramName == "name" {
-				paramType = sdk.KeyParameter
-			}
-			params = append(params, sdk.Parameter{
-				Name:  paramName,
-				Value: paramValue,
-				Type:  paramType,
-			})
-		}
-		return sdk.Action{
-			Name:       sdk.InstallKeyAction,
-			Type:       sdk.BuiltinAction,
-			Parameters: params,
-		}
-	}
-
-	return sdk.Action{
-		Name: sdk.InstallKeyAction,
-		Type: sdk.BuiltinAction,
-	}
-}
-
-func (s Step) isInstallKey() bool { return s.InstallKey != nil }
-
 func (s Step) isPushBuildInfo() bool { return s.PushBuildInfo != nil }
 
 func (s Step) asPushBuildInfo() sdk.Action {
diff --git a/sdk/messages.go b/sdk/messages.go
index 64b194fda..680568b14 100644
--- a/sdk/messages.go
+++ b/sdk/messages.go
@@ -97,6 +97,9 @@ var (
 	MsgWorkflowV3Preview                    = &Message{"MsgWorkflowV3Preview", trad{FR: "Le workflow a été généré en version 3 à partir d'une ancienne version", EN: "The workflow was generated in version 3 from an old version"}, nil, RunInfoTypeWarning}
 	MsgSpawnInfoDisableSecretInjection      = &Message{"MsgSpawnInfoDisableSecretInjection", trad{EN: "⚠ Project's secrets were not automatically injected for this job because of a region prerequisite: %s"}, nil, RunInfoTypInfo}
 	MsgSpawnInfoManualSecretInjection       = &Message{"MsgSpawnInfoManualSecretInjection", trad{EN: "Prerequisites of type secret matched %s secret(s)"}, nil, RunInfoTypInfo}
+	MsgSpawnInfoWorkerHookSetup             = &Message{"MsgSpawnInfoWorkerHookSetup", trad{EN: "Setting up worker hook %q"}, nil, RunInfoTypInfo}
+	MsgSpawnInfoWorkerHookRun               = &Message{"MsgSpawnInfoWorkerHookRun", trad{EN: "Running worker hook %q"}, nil, RunInfoTypInfo}
+	MsgSpawnInfoWorkerHookRunTeardown       = &Message{"MsgSpawnInfoWorkerHookRunTeardown", trad{EN: "Running worker hook %q teardown"}, nil, RunInfoTypInfo}
 )
 
 // Messages contains all sdk Messages
@@ -178,6 +181,9 @@ var Messages = map[string]*Message{
 	MsgWorkflowV3Preview.ID:                    MsgWorkflowV3Preview,
 	MsgSpawnInfoDisableSecretInjection.ID:      MsgSpawnInfoDisableSecretInjection,
 	MsgSpawnInfoManualSecretInjection.ID:       MsgSpawnInfoManualSecretInjection,
+	MsgSpawnInfoWorkerHookSetup.ID:             MsgSpawnInfoWorkerHookSetup,
+	MsgSpawnInfoWorkerHookRun.ID:               MsgSpawnInfoWorkerHookRun,
+	MsgSpawnInfoWorkerHookRunTeardown.ID:       MsgSpawnInfoWorkerHookRunTeardown,
 }
 
 //Message represent a struc format translated messages
diff --git a/sdk/parameter.go b/sdk/parameter.go
index 13cabdc6a..04ba3c083 100644
--- a/sdk/parameter.go
+++ b/sdk/parameter.go
@@ -7,16 +7,13 @@ import (
 
 // Different type of Parameter
 const (
-	EnvironmentParameter = "env"
-	PipelineParameter    = "pipeline"
-	ListParameter        = "list"
-	NumberParameter      = "number"
-	StringParameter      = "string"
-	TextParameter        = "text"
-	BooleanParameter     = "boolean"
-	KeySSHParameter      = "ssh-key"
-	KeyPGPParameter      = "pgp-key"
-	KeyParameter         = "key"
+	ListParameter    = "list"
+	NumberParameter  = "number"
+	StringParameter  = "string"
+	TextParameter    = "text"
+	BooleanParameter = "boolean"
+	KeySSHParameter  = "ssh-key"
+	KeyPGPParameter  = "pgp-key"
 )
 
 var (
@@ -25,13 +22,10 @@ var (
 		StringParameter,
 		NumberParameter,
 		TextParameter,
-		EnvironmentParameter,
 		BooleanParameter,
 		ListParameter,
-		PipelineParameter,
 		KeySSHParameter,
 		KeyPGPParameter,
-		KeyParameter,
 	}
 )
 
diff --git a/sdk/workflow_run_result.go b/sdk/workflow_run_result.go
index b159e2f5b..7c5bbd1f5 100644
--- a/sdk/workflow_run_result.go
+++ b/sdk/workflow_run_result.go
@@ -2,6 +2,8 @@ package sdk
 
 import (
 	"encoding/json"
+	"fmt"
+	"sort"
 	"time"
 )
 
@@ -15,6 +17,28 @@ const (
 type WorkflowRunResultType string
 type WorkflowRunResultDataKey string
 
+type WorkflowRunResults []WorkflowRunResult
+
+// Unique returns the last version of each results
+func (w WorkflowRunResults) Unique() (WorkflowRunResults, error) {
+	m := make(map[string]WorkflowRunResult, len(w))
+	for i := range w {
+		key, err := w[i].ComputeUniqueKey()
+		if err != nil {
+			return nil, err
+		}
+		if v, ok := m[key]; !ok || v.SubNum < w[i].SubNum {
+			m[key] = w[i]
+		}
+	}
+	filtered := make(WorkflowRunResults, 0, len(m))
+	for _, v := range m {
+		filtered = append(filtered, v)
+	}
+	sort.Slice(filtered, func(i, j int) bool { return filtered[i].Created.Before(filtered[j].Created) })
+	return filtered, nil
+}
+
 type WorkflowRunResult struct {
 	ID                string                `json:"id" db:"id"`
 	Created           time.Time             `json:"created" db:"created"`
@@ -26,12 +50,47 @@ type WorkflowRunResult struct {
 	DataRaw           json.RawMessage       `json:"data" db:"data"`
 }
 
+func (r WorkflowRunResult) ComputeUniqueKey() (string, error) {
+	key := fmt.Sprintf("%d-%s", r.WorkflowRunID, r.Type)
+	switch r.Type {
+	case WorkflowRunResultTypeArtifactManager:
+		var data WorkflowRunResultArtifactManager
+		if err := json.Unmarshal(r.DataRaw, &data); err != nil {
+			return "", WithStack(err)
+		}
+		key = key + "-" + data.Name + "-" + data.RepoType
+	default:
+		var data WorkflowRunResultArtifactCommon
+		if err := json.Unmarshal(r.DataRaw, &data); err != nil {
+			return "", WithStack(err)
+		}
+		key = key + "-" + data.Name
+	}
+	return key, nil
+}
+
+func (r WorkflowRunResult) ComputeName() (string, error) {
+	switch r.Type {
+	case WorkflowRunResultTypeArtifactManager:
+		var data WorkflowRunResultArtifactManager
+		if err := json.Unmarshal(r.DataRaw, &data); err != nil {
+			return "", WithStack(err)
+		}
+		return fmt.Sprintf("%s (%s: %s)", data.Name, r.Type, data.RepoType), nil
+	default:
+		var data WorkflowRunResultArtifactCommon
+		if err := json.Unmarshal(r.DataRaw, &data); err != nil {
+			return "", WithStack(err)
+		}
+		return fmt.Sprintf("%s (%s)", data.Name, r.Type), nil
+	}
+}
+
 func (r *WorkflowRunResult) GetArtifact() (WorkflowRunResultArtifact, error) {
 	var data WorkflowRunResultArtifact
 	if err := JSONUnmarshal(r.DataRaw, &data); err != nil {
 		return data, WithStack(err)
 	}
-
 	return data, nil
 }
 
@@ -70,8 +129,12 @@ type WorkflowRunResultCheck struct {
 	ResultType WorkflowRunResultType `json:"result_type"`
 }
 
+type WorkflowRunResultArtifactCommon struct {
+	Name string `json:"name"`
+}
+
 type WorkflowRunResultArtifactManager struct {
-	Name     string `json:"name"`
+	WorkflowRunResultArtifactCommon
 	Size     int64  `json:"size"`
 	MD5      string `json:"md5"`
 	Path     string `json:"path"`
@@ -98,7 +161,7 @@ func (a *WorkflowRunResultArtifactManager) IsValid() error {
 }
 
 type WorkflowRunResultStaticFile struct {
-	Name      string `json:"name"`
+	WorkflowRunResultArtifactCommon
 	RemoteURL string `json:"remote_url"`
 }
 
@@ -113,7 +176,7 @@ func (a *WorkflowRunResultStaticFile) IsValid() error {
 }
 
 type WorkflowRunResultArtifact struct {
-	Name       string `json:"name"`
+	WorkflowRunResultArtifactCommon
 	Size       int64  `json:"size"`
 	MD5        string `json:"md5"`
 	CDNRefHash string `json:"cdn_hash"`
@@ -138,7 +201,7 @@ func (a *WorkflowRunResultArtifact) IsValid() error {
 }
 
 type WorkflowRunResultCoverage struct {
-	Name       string `json:"name"`
+	WorkflowRunResultArtifactCommon
 	Size       int64  `json:"size"`
 	MD5        string `json:"md5"`
 	CDNRefHash string `json:"cdn_hash"`
diff --git a/ui/src/app/shared/action/action.html b/ui/src/app/shared/action/action.html
index 101e0524c..c436c0554 100644
--- a/ui/src/app/shared/action/action.html
+++ b/ui/src/app/shared/action/action.html
@@ -140,7 +140,7 @@
                                 (nzOnConfirm)="sendActionEvent('delete')"
                                 nzPopconfirmPlacement="topLeft"
                                 nz-button>
-                            <i nz-icon nzType="rest" nzTheme="outline"></i>Delete
+                            <i nz-icon nzType="delete" nzTheme="outline"></i>Delete
                         </button>
                     </li>
                 </ul>
diff --git a/ui/src/app/shared/button/upload/upload.button.component.ts b/ui/src/app/shared/button/upload/upload.button.component.ts
index c32325bed..c07fa5a9a 100644
--- a/ui/src/app/shared/button/upload/upload.button.component.ts
+++ b/ui/src/app/shared/button/upload/upload.button.component.ts
@@ -1,4 +1,5 @@
 import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
+import { NzUploadFile } from 'ng-zorro-antd/upload';
 
 @Component({
     selector: 'app-upload-button',
@@ -16,24 +17,25 @@ export class UploadButtonComponent  {
 
     showConfirmation = false;
 
-    constructor() {}
-
-    fileEvent(event) {
-      if (!event || !event.target || !event.target.files || !event.target.files[0]) {
-        return;
-      }
-      let file = event.target.files[0];
-      let reader = new FileReader();
-      let that = this;
-
-      reader.onloadend = function(e: any) {
-        that.event.emit({content: e.target.result, file});
-      };
-
-      if (this.image) {
-        reader.readAsDataURL(file);
-      } else {
-        reader.readAsText(file);
-      }
+    that: UploadButtonComponent
+
+    constructor() {
+        this.that = this;
+    }
+
+    fileEvent = (file: NzUploadFile): boolean => {
+        const myReader = new FileReader();
+        let that = this;
+        myReader.onloadend = (e) => {
+            // @ts-ignore
+            this.event.emit({content: myReader.result, file: file as any})
+        };
+
+        if (this.image) {
+            myReader.readAsDataURL(file as any);
+        } else {
+            myReader.readAsText(file as any);
+        }
+        return false;
     }
 }
diff --git a/ui/src/app/shared/button/upload/upload.button.html b/ui/src/app/shared/button/upload/upload.button.html
index ba9e76fca..beca5ce4d 100644
--- a/ui/src/app/shared/button/upload/upload.button.html
+++ b/ui/src/app/shared/button/upload/upload.button.html
@@ -1,5 +1,6 @@
-<input type="file" (change)="fileEvent($event)" class="inputfile" id="embedpollfileinput" [accept]="accept"/>
-<label for="embedpollfileinput" class="ui {{size}} button">
-    <i class="ui upload icon"></i>
-    Upload
-</label>
+<nz-upload [nzBeforeUpload]="fileEvent" [nzAccept]="accept">
+    <button nz-button type="button">
+        <i nz-icon nzType="upload"></i>
+        Select File
+    </button>
+</nz-upload>
diff --git a/ui/src/app/shared/favorite-cards/favorite-cards.component.html b/ui/src/app/shared/favorite-cards/favorite-cards.component.html
index 6c172712e..760aec6a1 100644
--- a/ui/src/app/shared/favorite-cards/favorite-cards.component.html
+++ b/ui/src/app/shared/favorite-cards/favorite-cards.component.html
@@ -62,7 +62,7 @@
               <i class="share alternate icon"></i>
             </div>
             <img class="right floated mini ui image" [src]="favorite.icon" *ngIf="favorite.icon">
-            <a class="header" sm-item href="#" [routerLink]="['/project', favorite.key, 'workflow', favorite.workflow_name]">
+            <a class="header" href="#" [routerLink]="['/project', favorite.key, 'workflow', favorite.workflow_name]">
               {{favorite.workflow_name}}
             </a>
             <div class="meta">
@@ -77,7 +77,7 @@
               <i class="browser icon"></i>
             </div>
             <img class="right floated mini ui image" [src]="favorite.icon" *ngIf="favorite.icon">
-            <a class="header" sm-item href="#" [routerLink]="['/project', favorite.key]">
+            <a class="header" href="#" [routerLink]="['/project', favorite.key]">
               {{favorite.name}}
             </a>
             <div class="meta">
diff --git a/ui/src/app/shared/group/form/group.form.component.spec.ts b/ui/src/app/shared/group/form/group.form.component.spec.ts
deleted file mode 100644
index 3aae31637..000000000
--- a/ui/src/app/shared/group/form/group.form.component.spec.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-import { fakeAsync, TestBed, tick } from '@angular/core/testing';
-import { RouterTestingModule } from '@angular/router/testing';
-import { TranslateLoader, TranslateModule, TranslateParser, TranslateService } from '@ngx-translate/core';
-import { SharedModule } from 'app/shared/shared.module';
-import { GroupFormComponent } from './group.form.component';
-
-describe('CDS: Group form component', () => {
-    beforeEach(async () => {
-        await TestBed.configureTestingModule({
-            declarations: [
-            ],
-            providers: [
-                TranslateService,
-                TranslateLoader,
-                TranslateParser,
-            ],
-            imports: [
-                SharedModule,
-                TranslateModule.forRoot(),
-                RouterTestingModule.withRoutes([])
-            ]
-        }).compileComponents();
-
-    });
-
-    it('should load component and disable button', fakeAsync(() => {
-        // Create component
-        let fixture = TestBed.createComponent(GroupFormComponent);
-        let component = fixture.debugElement.componentInstance;
-        expect(component).toBeTruthy();
-
-        fixture.detectChanges();
-        tick(250);
-
-        expect(fixture.debugElement.nativeElement.querySelector('.ui.green.button.disabled')).toBeTruthy();
-    }));
-
-    it('should load component and enable button', fakeAsync(() => {
-        // Create component
-        let fixture = TestBed.createComponent(GroupFormComponent);
-        let component = fixture.debugElement.componentInstance;
-        expect(component).toBeTruthy();
-
-        fixture.componentInstance.group.name = 'My New Group';
-
-        fixture.detectChanges();
-        tick(250);
-        expect(fixture.debugElement.nativeElement.querySelector('.ui.green.button.disabled')).toBeFalsy();
-    }));
-});
-
diff --git a/ui/src/app/shared/group/form/group.form.component.ts b/ui/src/app/shared/group/form/group.form.component.ts
deleted file mode 100644
index 54050c61f..000000000
--- a/ui/src/app/shared/group/form/group.form.component.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import {ChangeDetectionStrategy, Component, Input} from '@angular/core';
-import {Group} from '../../../model/group.model';
-
-@Component({
-    selector: 'app-group-form',
-    templateUrl: './group.form.html',
-    styleUrls: ['./group.form.scss'],
-    changeDetection: ChangeDetectionStrategy.OnPush
-})
-export class GroupFormComponent {
-    @Input() group: Group;
-    // form ( with submit button ), noform ( no button, use group input )
-    @Input() mode = 'form';
-
-    constructor() {
-        this.group = new Group();
-    }
-
-    createGroup(): void {
-        // TODO later
-    }
-}
diff --git a/ui/src/app/shared/group/form/group.form.html b/ui/src/app/shared/group/form/group.form.html
deleted file mode 100644
index 4fb97970c..000000000
--- a/ui/src/app/shared/group/form/group.form.html
+++ /dev/null
@@ -1,9 +0,0 @@
-<form (ngSubmit)="createGroup()" class="ui form">
-    <div class="field">
-        <label>{{ 'group_name' | translate }}</label>
-        <input type="text" name="groupname" class="ui input" [(ngModel)]="group.name">
-    </div>
-    <div class="right aligned field" *ngIf="mode === 'form'">
-        <button [class.disabled]="!group.name || group.name === ''" type="submit" class="ui green button">{{ 'btn_create' | translate }}</button>
-    </div>
-</form>
\ No newline at end of file
diff --git a/ui/src/app/shared/modal/warning/warning.component.ts b/ui/src/app/shared/modal/warning/warning.component.ts
deleted file mode 100644
index f5306a1aa..000000000
--- a/ui/src/app/shared/modal/warning/warning.component.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, ViewChild } from '@angular/core';
-import { SemanticModalComponent } from '@sgu/ng-semantic';
-
-@Component({
-    selector: 'app-warning-modal',
-    templateUrl: './warning.html',
-    styleUrls: ['./warning.scss'],
-    changeDetection: ChangeDetectionStrategy.OnPush
-})
-export class WarningModalComponent {
-
-    private currentEvent: any;
-
-    @Input() title: string;
-    @Input() msg: string;
-    @Output() event = new EventEmitter<any>();
-
-    @ViewChild('myModal')
-    private modal: SemanticModalComponent;
-
-    constructor() { }
-
-    show(event?: any) {
-        if (event) {
-            this.currentEvent = event;
-        }
-        this.modal.show();
-    }
-
-    eventAndClose(modal: any) {
-        this.event.emit(this.currentEvent);
-        this.close(modal);
-    }
-
-    close(modal: any) {
-        modal.hide();
-    }
-}
diff --git a/ui/src/app/shared/modal/warning/warning.html b/ui/src/app/shared/modal/warning/warning.html
deleted file mode 100644
index 07c7f4eec..000000000
--- a/ui/src/app/shared/modal/warning/warning.html
+++ /dev/null
@@ -1,10 +0,0 @@
-<sm-modal title="{{title}}" class="basic warning" #myModal>
-    <modal-content>
-        {{msg}}
-    </modal-content>
-    <modal-actions>
-        <div class="ui grey button" (click)="close(myModal)">{{'btn_cancel' | translate}}</div>
-        <div class="ui green button" (click)="eventAndClose(myModal)">{{'btn_continue' | translate}}</div>
-    </modal-actions>
-</sm-modal>
-
diff --git a/ui/src/app/shared/modal/warning/warning.scss b/ui/src/app/shared/modal/warning/warning.scss
deleted file mode 100644
index 588a6dadd..000000000
--- a/ui/src/app/shared/modal/warning/warning.scss
+++ /dev/null
@@ -1,3 +0,0 @@
-.warning {
-  font-size: 2em;
-}
diff --git a/ui/src/app/shared/parameter/value/parameter.value.component.spec.ts b/ui/src/app/shared/parameter/value/parameter.value.component.spec.ts
index 2714dcb86..8954e9fe6 100644
--- a/ui/src/app/shared/parameter/value/parameter.value.component.spec.ts
+++ b/ui/src/app/shared/parameter/value/parameter.value.component.spec.ts
@@ -68,52 +68,5 @@ describe('CDS: Parameter Value Component', () => {
         expect(compiled.querySelector('input[type=checkbox]')).toBeTruthy('Input type checkbox must be displayed');
     }));
 
-    /*
-    it('should create a textarea', fakeAsync( () => {
-        // Create component
-        let fixture = TestBed.createComponent(ParameterValueComponent);
-        let component = fixture.debugElement.componentInstance;
-        expect(component).toBeTruthy();
-
-        fixture.componentInstance.type = 'text';
-
-        fixture.detectChanges();
-        tick(50);
-
-        let compiled = fixture.debugElement.nativeElement;
-        expect(compiled.querySelector('codemirror')).toBeTruthy('textarea must be displayed');
-
-    }));
-    */
-
-    it('should create a select for pipeline', fakeAsync(() => {
-        // Create component
-        let fixture = TestBed.createComponent(ParameterValueComponent);
-        let component = fixture.debugElement.componentInstance;
-        expect(component).toBeTruthy();
-
-        fixture.componentInstance.type = 'pipeline';
-
-        fixture.detectChanges();
-        tick(50);
-
-        let compiled = fixture.debugElement.nativeElement;
-        expect(compiled.querySelector('select')).toBeTruthy('select must be displayed');
-    }));
-
-    it('should create a select for environments', fakeAsync(() => {
-        // Create component
-        let fixture = TestBed.createComponent(ParameterValueComponent);
-        let component = fixture.debugElement.componentInstance;
-        expect(component).toBeTruthy();
-
-        fixture.componentInstance.type = 'env';
-
-        fixture.detectChanges();
-        tick(50);
-
-        let compiled = fixture.debugElement.nativeElement;
-        expect(compiled.querySelector('select')).toBeTruthy('select must be displayed');
-    }));
 });
 
diff --git a/ui/src/app/shared/parameter/value/parameter.value.component.ts b/ui/src/app/shared/parameter/value/parameter.value.component.ts
index 105c06c45..f8b574830 100644
--- a/ui/src/app/shared/parameter/value/parameter.value.component.ts
+++ b/ui/src/app/shared/parameter/value/parameter.value.component.ts
@@ -64,11 +64,8 @@ export class ParameterValueComponent implements OnInit, AfterViewChecked, OnDest
         if (data) {
             this.projectKey = data.key;
         }
-        this.projectRo = data;
     }
 
-    projectRo: Project;
-
     @Output() valueChange = new EventEmitter<string | number | boolean>();
     @Output() valueUpdating = new EventEmitter<boolean>();
 
diff --git a/ui/src/app/shared/parameter/value/parameter.value.html b/ui/src/app/shared/parameter/value/parameter.value.html
index 30fa10367..84268146a 100644
--- a/ui/src/app/shared/parameter/value/parameter.value.html
+++ b/ui/src/app/shared/parameter/value/parameter.value.html
@@ -1,113 +1,76 @@
 <ng-container [ngSwitch]="type" *ngIf="edit">
-    <div *ngSwitchCase="'number'" class="ui fluid input">
-        <input type="number" [(ngModel)]="editableValue" (change)="valueChanged()" (keydown)="sendValueChanged()"
+    <ng-container *ngSwitchCase="'number'">
+        <input nz-input type="number" [(ngModel)]="editableValue" (change)="valueChanged()" (keydown)="sendValueChanged()"
             name="value">
-    </div>
-    <div *ngSwitchCase="'text'" class="ui form">
+    </ng-container>
+    <ng-container *ngSwitchCase="'text'">
         <codemirror [(ngModel)]="editableValue" [config]="codeMirrorConfig" (change)="changeCodeMirror()" #codeMirror
             (keydown)="sendValueChanged()"></codemirror>
-    </div>
-    <div *ngSwitchCase="'boolean'" class="ui checkbox">
-        <input type="checkbox" [(ngModel)]="editableValue" (change)="valueChanged();sendValueChanged()" name="value">
-        <label>{{ ' '}}</label>
-    </div>
-    <div *ngSwitchCase="'ssh-key'">
+    </ng-container>
+    <ng-container *ngSwitchCase="'boolean'">
+        <label nz-checkbox [(ngModel)]="editableValue" (ngModelChange)="valueChanged();sendValueChanged()" name="value"></label>
+    </ng-container>
+    <ng-container *ngSwitchCase="'ssh-key'">
         <ng-container *ngIf="keys && keys.ssh && (!editableValue || keyExist(editableValue.toString()))">
-            <sui-select class="selection" name="type" [(ngModel)]="editableValue"
-                (ngModelChange)="valueChanged();sendValueChanged()" [options]="keys.ssh" labelField="name"
-                valueField="name" [isSearchable]="true" #sshkey>
-                <sui-select-option *ngFor="let t of sshkey.filteredOptions" [value]="t">
-                </sui-select-option>
-            </sui-select>
+            <nz-select nzShowSearch name="sshname" [(ngModel)]="editableValue"  (ngModelChange)="valueChanged();sendValueChanged()" >
+                <nz-option *ngFor="let t of keys.ssh" [nzLabel]="t.name" [nzValue]="t.name"></nz-option>
+            </nz-select>
         </ng-container>
         <ng-container *ngIf="!keys || (editableValue && keys && keys.ssh && !keyExist(editableValue.toString()))">
-            <input type="text" [(ngModel)]="editableValue" (change)="valueChanged()" (keydown)="sendValueChanged()"
+            <input nz-input type="text" [(ngModel)]="editableValue" (change)="valueChanged()" (keydown)="sendValueChanged()"
                 name="value">
         </ng-container>
-    </div>
-    <div *ngSwitchCase="'pgp-key'">
+    </ng-container>
+    <ng-container *ngSwitchCase="'pgp-key'">
         <ng-container *ngIf="keys">
-            <sui-select class="selection" name="type" [(ngModel)]="editableValue"
-                (ngModelChange)="valueChanged();sendValueChanged()" [options]="keys.pgp" labelField="name"
-                valueField="name" [isSearchable]="true" #pgpkey>
-                <sui-select-option *ngFor="let t of pgpkey.filteredOptions" [value]="t">
-                </sui-select-option>
-            </sui-select>
+            <nz-select nzShowSearch name="gpgname" [(ngModel)]="editableValue"  (ngModelChange)="valueChanged();sendValueChanged()">
+                <nz-option *ngFor="let t of keys.pgp" [nzLabel]="t.name" [nzValue]="t.name"></nz-option>
+            </nz-select>
         </ng-container>
-    </div>
-    <div *ngSwitchCase="'key'">
-        <ng-container *ngIf="keys">
-            <sui-select class="selection" name="type" [(ngModel)]="editableValue"
-                (ngModelChange)="valueChanged();sendValueChanged()" [options]="allKeys" labelField="name"
-                valueField="name" [isSearchable]="true" #allkeys>
-                <sui-select-option *ngFor="let t of allkeys.filteredOptions" [value]="t">
-                </sui-select-option>
-            </sui-select>
-        </ng-container>
-    </div>
-    <div *ngSwitchCase="'list'">
-        <div class="ui fluid input" *ngIf="editList">
-            <input type="text" [(ngModel)]="editableValue" (change)="valueChanged()" (keydown)="sendValueChanged()"
+    </ng-container>
+    <ng-container *ngSwitchCase="'list'">
+        <ng-container *ngIf="editList">
+            <input nz-input type="text" [(ngModel)]="editableValue" (change)="valueChanged()" (keydown)="sendValueChanged()"
                 name="value" placeholder="value1;value2;value3">
-        </div>
-        <div *ngIf="!editList && editableValue && list">
-            <sm-select class="search fluid" [(model)]="editableValue" [options]="{'fullTextSearch': true}"
-                (modelChange)="valueChanged();sendValueChanged()" (keydown)="sendValueChanged()" name="value">
-
+        </ng-container>
+        <ng-container *ngIf="!editList && editableValue && list">
+            <nz-select nzShowSearch name="listvalue" [(ngModel)]="editableValue" (ngModelChange)="valueChanged();sendValueChanged()">
                 <ng-container *ngIf="refValue">
-                    <option *ngFor="let v of refValue" [value]="v">{{v}}</option>
+                    <nz-option *ngFor="let v of refValue" [nzValue]="v" [nzLabel]="v"></nz-option>
                 </ng-container>
                 <ng-container *ngIf="!refValue">
-                    <option *ngFor="let v of list" [value]="v">{{v}}</option>
+                    <nz-option *ngFor="let v of list" [nzValue]="v" [nzLabel]="v"></nz-option>
                 </ng-container>
-
-            </sm-select>
-        </div>
-    </div>
-
-    <div *ngSwitchCase="'env'">
-        <sm-select class="search fluid" [options]="{'fullTextSearch': true}" [(model)]="editableValue"
-            (modelChange)="valueChanged()" (onChange)="sendValueChanged()">
-            <option *ngFor="let env of projectRo?.environments" value="{{env.name}}">{{env.name}}</option>
-        </sm-select>
-    </div>
-    <div *ngSwitchCase="'pipeline'">
-        <sm-select class="search fluid" [options]="{'fullTextSearch': true}" [(model)]="editableValue"
-            (modelChange)="valueChanged()" (onChange)="sendValueChanged()">
-            <option *ngFor="let pip of projectRo?.pipelines" value="{{pip.name}}">{{pip.name}}</option>
-        </sm-select>
-    </div>
-    <div *ngSwitchCase="'repository'">
-        <sm-select class="search fluid" [options]="{'fullTextSearch': true}" [model]="selectedRepoManager.name"
-            (modelChange)="updateRepoManager($event)">
-            <option *ngFor="let r of repositoriesManager" value="{{r.name}}">{{r.name}}</option>
-        </sm-select>
-        <sm-select *ngIf="selectedRepoManager && selectedRepoManager.name !== 'Git Url'"
-            placeholder="{{'common_loading' | translate}}" [options]="{'fullTextSearch': true}"
-            (modelChange)="valueRepoChanged($event)" [(model)]="selectedRepo" class="search item"
-            [disabled]="loadingRepos">
-            <option *ngFor="let r of repositories" value="{{r.fullname}}">{{r.fullname}}</option>
-        </sm-select>
-        <input *ngIf="selectedRepoManager && selectedRepoManager.name === 'Git Url'" type="text"
+            </nz-select>
+        </ng-container>
+    </ng-container>
+    <ng-container *ngSwitchCase="'repository'">
+        <nz-select nzShowSearch [(ngModel)]="selectedRepoManager.name" (ngModelChange)="updateRepoManager($event)">
+            <nz-option *ngFor="let r of repositoriesManager" [nzValue]="r.name" [nzLabel]="r.name"></nz-option>
+        </nz-select>
+        <nz-select *ngIf="selectedRepoManager && selectedRepoManager.name !== 'Git Url'" [(ngModel)]="selectedRepo"
+                   [disabled]="loadingRepos"
+                   (ngModelChange)="valueRepoChanged($event)">
+            <nz-option *ngFor="let r of repositories" [nzLabel]="r.fullname" [nzValue]="r.fullname"></nz-option>
+        </nz-select>
+        <input nz-input *ngIf="selectedRepoManager && selectedRepoManager.name === 'Git Url'" type="text"
             [(ngModel)]="editableValue" (change)="valueChanged()" (keydown)="sendValueChanged()" name="value"
             placeholder="{{'parameter_git_url' | translate }}">
-    </div>
+    </ng-container>
     <div *ngSwitchDefault>
-        <input auto-complete autocomplete="off" type="text" [(ngModel)]="editableValue" (change)="valueChanged()"
+        <input nz-input auto-complete autocomplete="off" type="text" [(ngModel)]="editableValue" (change)="valueChanged()"
             (keydown)="sendValueChanged()" name="value" [source]="suggest" [min-chars]="3"
             [no-match-found-text]="false">
     </div>
 </ng-container>
 <ng-container [ngSwitch]="type" *ngIf="!edit">
-    <div *ngSwitchCase="'text'" class="ui form">
-        <textarea readonly rows="{{_sharedService.getTextAreaheight(editableValue.toString())}}">{{editableValue}}</textarea>
-    </div>
-    <div *ngSwitchCase="'boolean'" class="ui checkbox">
-        <input disabled type="checkbox" [(ngModel)]="editableValue" (change)="valueChanged()"
-            (keydown)="sendValueChanged()" name="value">
-        <label>{{' '}}</label>
-    </div>
-    <div *ngSwitchDefault class="ui fluid input">
-        {{editableValue}}
-    </div>
+    <ng-container *ngSwitchCase="'text'">
+        <textarea nz-input readonly rows="{{_sharedService.getTextAreaheight(editableValue.toString())}}">{{editableValue}}</textarea>
+    </ng-container>
+    <ng-container *ngSwitchCase="'boolean'">
+        <label nz-checkbox [(ngModel)]="editableValue" name="value" [nzDisabled]="true"></label>
+    </ng-container>
+    <ng-container *ngSwitchDefault class="ui fluid input">
+        <input nz-input readonly [ngModel]="editableValue">
+    </ng-container>
 </ng-container>
diff --git a/ui/src/app/shared/parameter/value/parameter.value.scss b/ui/src/app/shared/parameter/value/parameter.value.scss
index 069d4b335..60f4c030a 100644
--- a/ui/src/app/shared/parameter/value/parameter.value.scss
+++ b/ui/src/app/shared/parameter/value/parameter.value.scss
@@ -1,5 +1,3 @@
-@import "../../../../common";
-
-.ui.fluid.input {
-  word-break: break-all;
-}
\ No newline at end of file
+nz-select {
+  width: 100%;
+}
diff --git a/ui/src/app/shared/repomanager/from/repomanager.form.component.ts b/ui/src/app/shared/repomanager/from/repomanager.form.component.ts
index c8bc953e2..7ad814c4f 100644
--- a/ui/src/app/shared/repomanager/from/repomanager.form.component.ts
+++ b/ui/src/app/shared/repomanager/from/repomanager.form.component.ts
@@ -1,9 +1,8 @@
-import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, ViewChild } from '@angular/core';
+import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input } from '@angular/core';
 import { TranslateService } from '@ngx-translate/core';
 import { Store } from '@ngxs/store';
 import { Project } from 'app/model/project.model';
 import { RepoManagerService } from 'app/service/repomanager/project.repomanager.service';
-import { WarningModalComponent } from 'app/shared/modal/warning/warning.component';
 import { ToastService } from 'app/shared/toast/ToastService';
 import {
     CallbackRepositoryManagerBasicAuthInProject,
@@ -24,10 +23,6 @@ export class RepoManagerFormComponent {
     // project
     @Input() project: Project;
 
-    // Warning modal
-    @ViewChild('linkRepoWarning')
-    linkRepoWarningModal: WarningModalComponent;
-
     public ready = false;
     public connectLoading = false;
     public verificationLoading = false;
@@ -39,11 +34,12 @@ export class RepoManagerFormComponent {
     // Repo manager validation
     public addRepoResponse: any;
     validationToken: string;
-    private modalInstance: any;
 
     basicUser: string;
     basicPassword: string;
 
+    repoModalVisible: boolean;
+
     constructor(
         private _repoManService: RepoManagerService,
         private _toast: ToastService,
@@ -52,35 +48,30 @@ export class RepoManagerFormComponent {
         private store: Store) {
         this._repoManService.getAll()
             .pipe(finalize(() => this._cd.markForCheck()))
-            .subscribe( res => {
-            this.ready = true;
-            this.reposManagerList = res;
-        });
+            .subscribe(res => {
+                this.ready = true;
+                this.reposManagerList = res;
+            });
     }
 
-    create(verificationModal: any, skip?: boolean): void {
+    create(): void {
         if (this.selectedRepoId && this.reposManagerList[this.selectedRepoId]) {
-            if (!skip && this.project.externalChange) {
-                this.linkRepoWarningModal.show();
-            } else {
-                this.connectLoading = true;
-                this.store.dispatch(new ConnectRepositoryManagerInProject({
-                    projectKey: this.project.key,
-                    repoManager: this.reposManagerList[this.selectedRepoId]
-                })).pipe(
-                    flatMap(() => this.store.selectOnce(ProjectState)),
-                    finalize(() => {
-                        this.connectLoading = false;
-                        this._cd.markForCheck();
-                    })
-                ).subscribe((projState: ProjectStateModel) => {
-                    this.addRepoResponse = projState.repoManager;
-                    this.modalInstance = verificationModal;
-                    setTimeout(() => {
-                        verificationModal.show();
-                    }, 1);
-                });
-            }
+
+            this.connectLoading = true;
+            this.store.dispatch(new ConnectRepositoryManagerInProject({
+                projectKey: this.project.key,
+                repoManager: this.reposManagerList[this.selectedRepoId]
+            })).pipe(
+                flatMap(() => this.store.selectOnce(ProjectState)),
+                finalize(() => {
+                    this.connectLoading = false;
+                    this._cd.markForCheck();
+                })
+            ).subscribe((projState: ProjectStateModel) => {
+                this.addRepoResponse = projState.repoManager;
+                this.repoModalVisible = true;
+            });
+
         }
     }
 
@@ -94,9 +85,10 @@ export class RepoManagerFormComponent {
         }))
             .pipe(finalize(() => {
                 this.verificationLoading = false;
+                this._cd.markForCheck();
             }))
-            .subscribe( () => {
-                this.modalInstance.hide();
+            .subscribe(() => {
+                this.repoModalVisible = false;
                 this.basicUser = '';
                 this.basicPassword = '';
                 this._toast.success('', this._translate.instant('repoman_verif_msg_ok'));
@@ -110,9 +102,12 @@ export class RepoManagerFormComponent {
             repoManager: this.reposManagerList[this.selectedRepoId],
             requestToken: this.addRepoResponse.request_token,
             code: this.validationToken
-        })).pipe(finalize(() => this.verificationLoading = false))
+        })).pipe(finalize(() => {
+            this.verificationLoading = false;
+            this.repoModalVisible = false;
+            this._cd.markForCheck();
+        }))
             .subscribe(() => {
-                this.modalInstance.hide();
                 this._toast.success('', this._translate.instant('repoman_verif_msg_ok'));
             });
     }
diff --git a/ui/src/app/shared/repomanager/from/repomanager.form.html b/ui/src/app/shared/repomanager/from/repomanager.form.html
index 07d32fd96..c80516144 100644
--- a/ui/src/app/shared/repomanager/from/repomanager.form.html
+++ b/ui/src/app/shared/repomanager/from/repomanager.form.html
@@ -1,4 +1,4 @@
-<form class="ui form" *ngIf="ready" (ngSubmit)="create(verificationModal)" novalidate>
+<form class="ui form" *ngIf="ready" (ngSubmit)="create()" novalidate>
     <div class="fields">
         <div class="seven wide field">
             <sm-select [(model)]="selectedRepoId" [disabled]="connectLoading">
@@ -11,33 +11,45 @@
             </button>
         </div>
     </div>
-    <app-warning-modal [title]="_translate.instant('warning_modal_title')" [msg]="_translate.instant('warning_modal_body')" (event)="create(verificationModal, true)" #linkRepoWarning></app-warning-modal>
 </form>
-<sm-modal title="{{ 'repoman_modal_verif_title' | translate }}" class="small" #verificationModal>
-    <modal-content>
+<nz-modal nzTitle="{{ 'repoman_modal_verif_title' | translate }}" [nzVisible]="repoModalVisible" (nzOnCancel)="repoModalVisible = false">
+    <div *nzModalContent>
+        <form nz-form>
+            <ng-container *ngIf="addRepoResponse?.auth_type === 'basic'">
+                <nz-form-item>
+                    <nz-form-label>Username</nz-form-label>
+                    <nz-form-control>
+                        <input nz-input type="text" name="first-name" placeholder="Username" [(ngModel)]="basicUser">
+                    </nz-form-control>
+                </nz-form-item>
+                <nz-form-item>
+                    <nz-form-label>Password</nz-form-label>
+                    <nz-form-control>
+                        <input nz-input type="password" name="last-name" placeholder="Password" [(ngModel)]="basicPassword">
+                    </nz-form-control>
+                </nz-form-item>
+            </ng-container>
+            <ng-container *ngIf="addRepoResponse?.auth_type === 'oauth'">
+                <p>
+                    {{ 'repoman_modal_verif_text' | translate}}
+                    <a href="{{addRepoResponse?.url}}" target="_blank">{{ 'common_click_here' | translate}}</a>
+                </p>
+                <nz-form-item  *ngIf="selectedRepoId != null">
+                    <nz-form-label>Verification code</nz-form-label>
+                    <nz-form-control>
+                        <input nz-input type="text" name="verifiercode" placeholder="{{ 'repoman_modal_verif_code_placeholder' | translate }}" [(ngModel)]="validationToken">
+                    </nz-form-control>
+                </nz-form-item>
+            </ng-container>
+        </form>
+    </div>
+    <div *nzModalFooter>
+        <button nz-button name="cancel" (click)="repoModalVisible = false">Cancel</button>
         <ng-container *ngIf="addRepoResponse?.auth_type === 'basic'">
-            <div class="ui form">
-                <div class="field">
-                    <label>{{ 'user_label_username' | translate }}</label>
-                    <input type="text" name="first-name" placeholder="Username" [(ngModel)]="basicUser">
-                </div>
-                <div class="field">
-                    <label>{{ 'user_label_password' | translate }}</label>
-                    <input type="password" name="last-name" placeholder="Password" [(ngModel)]="basicPassword">
-                </div>
-                <button class="ui green button" name="validationbtn" [class.loading]="verificationLoading" (click)="sendBasicAuth()">{{ 'btn_validate' | translate }}</button>
-            </div>
+            <button nz-button nzType="primary" name="validationbtn" [nzLoading]="verificationLoading" (click)="sendBasicAuth()">Validate</button>
         </ng-container>
-        <ng-container *ngIf="addRepoResponse?.auth_type === 'oauth'">
-            <div>
-                {{ 'repoman_modal_verif_text' | translate}}
-                <a href="{{addRepoResponse?.url}}" target="_blank">{{ 'common_click_here' | translate}}</a>
-            </div>
-            <div class="ui input" *ngIf="selectedRepoId != null">
-                <input type="text" name="verifiercode" placeholder="{{ 'repoman_modal_verif_code_placeholder' | translate }}" [(ngModel)]="validationToken">
-                <button name="validationbtn" class="ui green button" [class.loading]="verificationLoading" (click)="sendVerificationCode()">{{ 'btn_validate' | translate }}</button>
-            </div>
+        <ng-container *ngIf="selectedRepoId != null">
+            <button nz-button nzType="primary" name="validationbtn" [nzLoading]="verificationLoading" (click)="sendVerificationCode()">Validate</button>
         </ng-container>
-
-    </modal-content>
-</sm-modal>
+    </div>
+</nz-modal>
diff --git a/ui/src/app/shared/requirements/form/requirements.form.component.ts b/ui/src/app/shared/requirements/form/requirements.form.component.ts
index 4f70325c0..c7b073f05 100644
--- a/ui/src/app/shared/requirements/form/requirements.form.component.ts
+++ b/ui/src/app/shared/requirements/form/requirements.form.component.ts
@@ -28,17 +28,22 @@ export class RequirementsFormComponent implements OnInit {
         return this._suggest;
     }
 
+    workerModelsMap: Map<string, WorkerModel> = new Map();
     _workerModels: Array<WorkerModel>;
     @Input() set workerModels(wms: Array<WorkerModel>) {
         if (wms) {
-            this._workerModels = wms;
-
-            this.suggestWithWorkerModel = wms.map(wm => {
-                if (wm.group.name !== SharedInfraGroupName) {
-                    return `${wm.group.name}/${wm.name}`;
-                }
-                return wm.name;
-            }).concat(this._suggest);
+            this.workerModelsMap = new Map<string, WorkerModel>();
+            this.suggestWithWorkerModel = new Array<string>();
+            if (wms) {
+                wms.forEach(wm => {
+                    let name = wm.name;
+                    if (wm.group.name !== SharedInfraGroupName) {
+                        name = `${wm.group.name}/${wm.name}`;
+                    }
+                    this.suggestWithWorkerModel.push(name);
+                    this.workerModelsMap.set(name, wm);
+                })
+            }
         }
     }
     get workerModels() {
@@ -104,8 +109,8 @@ export class RequirementsFormComponent implements OnInit {
         });
     }
 
-    onSubmitAddRequirement(form): void {
-        this.computeFormValid(form);
+    onSubmitAddRequirement(): void {
+        this.setName();
         if (this.isFormValid) {
             this.event.emit(new RequirementEvent('add', this.newRequirement));
             this.newRequirement = new Requirement('binary');
@@ -118,12 +123,12 @@ export class RequirementsFormComponent implements OnInit {
         });
     }
 
-    computeFormValid(form): void {
+    computeFormValid(): void {
         this.popupText = '';
         let goodModel = this.newRequirement.type !== 'model' || !this.config.disableModel;
         let goodHostname = this.newRequirement.type !== 'hostname' || !this.config.disableHostname;
         let goodRegion = this.newRequirement.type !== 'region' || !this.config.disableRegion;
-        this.isFormValid = (form.valid === true && this.newRequirement.name !== '' && this.newRequirement.value !== '')
+        this.isFormValid = (this.newRequirement.name !== '' && this.newRequirement.value !== '')
             && goodModel && goodHostname && goodRegion;
         if (!goodModel) {
             this.popupText = this._translate.instant('requirement_error_model');
@@ -136,13 +141,12 @@ export class RequirementsFormComponent implements OnInit {
         }
     }
 
-    selectType(): void {
-        this.newRequirement.value = '';
-        this.newRequirement.opts = '';
-        this.newRequirement.name = '';
+    selectType(reqType: string): void {
+        this.newRequirement = new Requirement(reqType);
+        this._cd.markForCheck();
     }
 
-    setName(form): void {
+    setName(): void {
         switch (this.newRequirement.type) {
             case 'service':
                 // if type service, user have to choose a hostname
@@ -162,7 +166,7 @@ export class RequirementsFormComponent implements OnInit {
                 // else, name is the value of the requirement
                 this.newRequirement.name = this.newRequirement.value;
         }
-        this.computeFormValid(form);
+        this.computeFormValid();
     }
 
     computeDisplayLinkWorkerModel(): WorkerModel {
diff --git a/ui/src/app/shared/requirements/form/requirements.form.html b/ui/src/app/shared/requirements/form/requirements.form.html
index 5a161dbf2..45d0a47d4 100644
--- a/ui/src/app/shared/requirements/form/requirements.form.html
+++ b/ui/src/app/shared/requirements/form/requirements.form.html
@@ -2,52 +2,22 @@
     <div nz-row>
         <div nz-col nzSpan="6">
             <nz-form-item>
-                <nz-select [(ngModel)]="newRequirement.type" (ngModelChange)="selectType()" name="selectType"
+                <nz-select [(ngModel)]="newRequirement.type" (ngModelChange)="selectType($event)" name="selectType"
                            nzPlaceHolder="{{ 'requirement_type' | translate }}">
                     <nz-option *ngFor="let r of availableRequirements" [nzValue]="r" [nzLabel]="r"></nz-option>
                 </nz-select>
             </nz-form-item>
         </div>
         <div nz-col nzSpan="12">
-            <nz-form-item>
-                <div *ngIf="newRequirement.type === 'service'" class="container">
-                    {{ 'requirement_hostname_service' | translate }}
-                    <input nz-input [nzAutocomplete]="autoSuggestService" name="requirementname" id="requirementname" [disabled]="newRequirement.type !== 'service'" type="text" required pattern="[a-zA-Z0-9\-\._]*" placeholder="{{placeholderTypeName[newRequirement.type]}}" [(ngModel)]="newRequirement.name">
-                    <nz-autocomplete [nzDataSource]="suggest" nzBackfill #autoSuggestService></nz-autocomplete>
-                    <div *ngIf="formAddRequirement.invalid && !formAddRequirement.pristine" class="ui error message">
-                        <p>{{'requirement_hostname_error' | translate}}</p>
-                    </div>
-                </div>
-                <div class="container" *ngIf="newRequirement.type === 'os-architecture'">
-                    <input nz-input [nzAutocomplete]="autoSuggestArch"  name="name" [disabled]="newRequirement.type !== 'os-architecture'" type="text" required placeholder="{{'requirement_placeholder_name_' + newRequirement.type | translate}}"
-                           [(ngModel)]="newRequirement.value" (keyup)="setName(formAddRequirement)" (blur)="setName(formAddRequirement)">
-                    <nz-autocomplete [nzDataSource]="suggestWithOsArch" nzBackfill #autoSuggestArch></nz-autocomplete>
-                    <div *ngIf="formAddRequirement.invalid && !formAddRequirement.pristine" class="ui error message">
-                        <p>{{'requirement_os-architecture_error' | translate}}</p>
-                    </div>
-                </div>
-                <div class="container" *ngIf="newRequirement.type !== 'model' && newRequirement.type !== 'os-architecture'">
-                    <span *ngIf="newRequirement.type === 'service'">{{ 'requirement_value_service' | translate }}</span>
-                    <input nz-input name="value" id="value" [nzAutocomplete]="autoServiceImage" type="text" [(ngModel)]="newRequirement.value" (keyup)="setName(formAddRequirement)" placeholder="{{placeholderTypeValue[newRequirement.type]}}">
-                    <nz-autocomplete [nzDataSource]="suggest" nzBackfill #autoServiceImage></nz-autocomplete>
-                </div>
-
-                <div class="container" *ngIf="newRequirement.type === 'model' && workerModels != null">
-                    <input nz-input name="value-workermodel" [nzAutocomplete]="autoModel" type="text" [(ngModel)]="newRequirement.value" (keyup)="setName(formAddRequirement)" (blur)="setName(formAddRequirement)" placeholder="{{'requirement_placeholder_value_model' | translate}}">
-                    <nz-autocomplete [nzDataSource]="suggestWithWorkerModel" nzBackfill #autoModel></nz-autocomplete>
-                    <p *ngIf="workerModelLinked != null">
-                        <a class="ui" [routerLink]="['/settings', 'worker-model', newRequirement.value]" target="_blank">
-                            {{ 'requirement_settings_worker_model' | translate }} {{ newRequirement.value }}
-                        </a>
-                    </p>
-                    {{ 'requirement_options' | translate }}
-                    <textarea nz-input name="opts" id="opts" [(ngModel)]="newRequirement.opts" rows="3" placeholder="{{'requirement_placeholder_opts_model' | translate}}"></textarea>
-                </div>
-                <div class="container" *ngIf="newRequirement.type === 'service'">
-                    {{ 'requirement_options' | translate }}
-                    <textarea nz-input name="servopts" id="servopts" [(ngModel)]="newRequirement.opts" rows="3" placeholder="{{'requirement_placeholder_opts_service' | translate}}"></textarea>
-                </div>
-            </nz-form-item>
+            <div class="content">
+                <app-requirements-value [suggestArchOs]="suggestWithOsArch"
+                                        [suggestWorkerModels]="suggestWithWorkerModel"
+                                        [workerModels]="workerModelsMap"
+                                        [edit]="true"
+                                        [suggest]="suggest"
+                                        [(requirement)]="newRequirement">
+                </app-requirements-value>
+            </div>
         </div>
         <div nz-col nzSpan="2">
             <nz-form-item *ngIf="workerModelLinked">
@@ -58,21 +28,18 @@
         </div>
         <div nz-col nzSpan="4">
             <nz-form-item>
-                <div suiPopup
-                     [popupText]="popupText"
-                     popupPlacement="top"
+                <div nz-tooltip [nzTooltipTitle]="popupText"
+                     nzTooltipPlacement="top"
                      *ngIf="(newRequirement.type === 'model' && config.disableModel) || (newRequirement.type === 'hostname' && config.disableHostname) || (newRequirement.type === 'region' && config.disableRegion)">
-                    <button class="ui blue icon button"
-                            [disabled]="!isFormValid"
-                            (click)="onSubmitAddRequirement(formAddRequirement)">
-                        <i class="plus icon"></i>
+                    <button nz-button nzType="primary" name="addBtn"
+                            (click)="onSubmitAddRequirement()">
+                        <i nz-icon nzType="plus" nzTheme="outline"></i>
                     </button>
                 </div>
                 <div *ngIf="(newRequirement.type !== 'model' || !config.disableModel) && (newRequirement.type !== 'hostname' || !config.disableHostname) && (newRequirement.type !== 'region' || !config.disableRegion)">
-                    <button class="ui blue icon button"
-                            [disabled]="!isFormValid"
-                            (click)="onSubmitAddRequirement(formAddRequirement)">
-                        <i class="plus icon"></i>
+                    <button nz-button nzType="primary" name="addBtn"
+                            (click)="onSubmitAddRequirement()">
+                        <i nz-icon nzType="plus" nzTheme="outline"></i>
                     </button>
                 </div>
             </nz-form-item>
diff --git a/ui/src/app/shared/requirements/form/requirements.form.scss b/ui/src/app/shared/requirements/form/requirements.form.scss
index e40b133e2..d9ba9025b 100644
--- a/ui/src/app/shared/requirements/form/requirements.form.scss
+++ b/ui/src/app/shared/requirements/form/requirements.form.scss
@@ -4,15 +4,9 @@ nz-form-item {
   nz-select {
     width: 100%;
   }
+}
 
-  .container {
-    width: 100%;
-  }
-
-  input {
-    width: 100%;
-  }
-  textarea {
-    width: 100%;
-  }
+.content {
+  padding-left: 15px;
+  padding-right: 15px;
 }
diff --git a/ui/src/app/shared/requirements/form/requirements.form.spec.ts b/ui/src/app/shared/requirements/form/requirements.form.spec.ts
index 900ccdb75..628fca69e 100644
--- a/ui/src/app/shared/requirements/form/requirements.form.spec.ts
+++ b/ui/src/app/shared/requirements/form/requirements.form.spec.ts
@@ -1,6 +1,6 @@
 /* eslint-disable @typescript-eslint/no-unused-vars */
 
-import {TestBed, tick, fakeAsync} from '@angular/core/testing';
+import { TestBed, tick, fakeAsync, flush } from '@angular/core/testing';
 import {TranslateService, TranslateLoader, TranslateParser, TranslateModule} from '@ngx-translate/core';
 import {RouterTestingModule} from '@angular/router/testing';
 import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
@@ -48,27 +48,22 @@ describe('CDS: Requirement Form Component', () => {
 
         http.expectOne('/requirement/types').flush(['binary']);
 
-        let compiled = fixture.debugElement.nativeElement;
-
         let r = new Requirement('binary');
         r.name = 'foo';
         r.value = 'foo';
 
-        fixture.detectChanges();
-        tick(250);
-
-        // simulate typing new variable
-        let inputName = compiled.querySelector('input[name="value"]');
-        inputName.value = r.value;
-        inputName.dispatchEvent(new Event('input'));
-        inputName.dispatchEvent(new Event('keyup'));
+        fixture.componentInstance.newRequirement = r;
 
+        tick(250)
         fixture.detectChanges();
-        tick(250);
 
+        let compiled = fixture.debugElement.nativeElement;
         spyOn(fixture.componentInstance.event, 'emit');
-        compiled.querySelector('.ui.blue.button').click();
+        console.log(compiled);
+        compiled.querySelector('button[name="addBtn"]').click();
 
         expect(fixture.componentInstance.event.emit).toHaveBeenCalledWith(new RequirementEvent('add', r));
+
+        flush();
     }));
 });
diff --git a/ui/src/app/shared/requirements/list/requirements.list.component.ts b/ui/src/app/shared/requirements/list/requirements.list.component.ts
index 21cd5385c..35460b6c0 100644
--- a/ui/src/app/shared/requirements/list/requirements.list.component.ts
+++ b/ui/src/app/shared/requirements/list/requirements.list.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 { SharedInfraGroupName } from 'app/model/group.model';
 import { Requirement } from 'app/model/requirement.model';
 import { WorkerModel } from 'app/model/worker-model.model';
@@ -18,34 +26,25 @@ export class RequirementsListComponent implements OnInit {
     @Input() requirements: Requirement[];
     @Input() edit: boolean;
 
-    _suggest: string[] = [];
-    @Input() set suggest(data: string[]) {
-        if (data) {
-            this._suggest = data;
-        } else {
-            this._suggest = [];
-        }
-    }
-    get suggest() {
-        return this._suggest;
-    }
+    @Input() suggest: Array<string> = [];
 
-    _workerModels: Array<WorkerModel>;
     @Input() set workerModels(wms: Array<WorkerModel>) {
         if (wms) {
-            this._workerModels = wms;
-
-            this.suggestWithWorkerModel = wms.map(wm => {
-                if (wm.group.name !== SharedInfraGroupName) {
-                    return `${wm.group.name}/${wm.name}`;
-                }
-                return wm.name;
-            }).concat(this._suggest);
+            this.workerModelsMap = new Map<string, WorkerModel>();
+            this.suggestWithWorkerModel = new Array<string>();
+            if (wms) {
+                wms.forEach(wm => {
+                    let name = wm.name;
+                    if (wm.group.name !== SharedInfraGroupName) {
+                        name = `${wm.group.name}/${wm.name}`;
+                    }
+                    this.suggestWithWorkerModel.push(name);
+                    this.workerModelsMap.set(name, wm);
+                })
+            }
         }
     }
-    get workerModels() {
-        return this._workerModels;
-    }
+    workerModelsMap: Map<string, WorkerModel> = new Map();
 
     @Output() event = new EventEmitter<RequirementEvent>();
     @Output() onChange = new EventEmitter<Requirement[]>();
@@ -56,6 +55,7 @@ export class RequirementsListComponent implements OnInit {
 
     constructor(
         private _requirementStore: RequirementStore,
+        private _cd: ChangeDetectorRef
     ) {
         this._requirementStore.getAvailableRequirements()
             .subscribe(r => {
@@ -67,6 +67,7 @@ export class RequirementsListComponent implements OnInit {
     ngOnInit() {
         this._requirementStore.getRequirementsTypeValues('os-architecture').pipe(first()).subscribe(values => {
             this.suggestWithOSArch = values.concat(this.suggest);
+            this._cd.markForCheck();
         });
     }
 
@@ -93,10 +94,8 @@ export class RequirementsListComponent implements OnInit {
                 // else, name is the value of the requirement
                 req.name = req.value;
         }
-        this.onChange.emit(this.requirements);
-    }
 
-    getWorkerModel(name: string): WorkerModel {
-        return this.workerModels.find(m => m.name === name);
+        this.onChange.emit(this.requirements);
+        this._cd.markForCheck();
     }
 }
diff --git a/ui/src/app/shared/requirements/list/requirements.list.html b/ui/src/app/shared/requirements/list/requirements.list.html
index 19b32fd22..c7f3db996 100644
--- a/ui/src/app/shared/requirements/list/requirements.list.html
+++ b/ui/src/app/shared/requirements/list/requirements.list.html
@@ -10,65 +10,18 @@
         <tbody>
             <tr *ngFor="let r of reqList.data">
                 <td>
-                    <div *ngIf="edit;else rType">
-                        <ng-container  *ngIf="availableRequirements.length > 0">
-                            <nz-select [(ngModel)]="r.type" (ngModelChange)="change(r)" nzShowSearch>
-                                <nz-option *ngFor="let r of availableRequirements" [nzValue]="r" [nzLabel]="r"></nz-option>
-                            </nz-select>
-                        </ng-container>
-                    </div>
-                    <ng-template #rType>
-                        <span>{{r.type}}</span>
-                    </ng-template>
+                    {{r.type}}
                 </td>
                 <td>
-                    <div *ngIf="r.type === 'service'">
-                        <div *ngIf="edit;else rName">
-                            Hostname:
-                            <input nz-input [nzAutocomplete]="autoSuggestService" [(ngModel)]="r.name" (ngModelChange)="change(r)">
-                            <nz-autocomplete [nzDataSource]="suggest" nzBackfill #autoSuggestService></nz-autocomplete>
-                        </div>
-                        <ng-template #rName>
-                            <span>{{r.name}}</span>
-                        </ng-template>
-                    </div>
-                    <div *ngIf="edit;else rValue">
-                        <div *ngIf="r.type !== 'model' && r.type !== 'os-architecture'">
-                            <ng-container *ngIf="r.type === 'service'">
-                                Docker Image:
-                            </ng-container>
-                            <input nz-input [nzAutocomplete]="autoSuggestImage" [(ngModel)]="r.value" (ngModelChange)="change(r)">
-                            <nz-autocomplete [nzDataSource]="suggest" nzBackfill #autoSuggestImage></nz-autocomplete>
-                        </div>
-                        <div *ngIf="r.type === 'os-architecture'">
-                            <input nz-input [nzAutocomplete]="autoOsArch" name="value-os-architecture" [(ngModel)]="r.value" (ngModelChange)="change(r)">
-                            <nz-autocomplete [nzDataSource]="suggestWithOSArch" nzBackfill #autoOsArch></nz-autocomplete>
-                        </div>
-                        <div *ngIf="r.type === 'model' && workerModels">
-                            <div class="ui grid">
-                                <div class="ui fourteen wide column">
-                                    <input nz-input name="value-workermodel" [nzAutocomplete]="autoWorkerModel" [(ngModel)]="r.value" (ngModelChange)="change(r)">
-                                    <nz-autocomplete [nzDataSource]="suggestWithWorkerModel" nzBackfill #autoWorkerModel></nz-autocomplete>
-                                </div>
-                                <div class="ui two wide column valign">
-                                    <span *ngIf="getWorkerModel(r.value)?.nb_spawn_err > 0" nz-tooltip nzTooltipTitle="{{'worker_model_spawn_error_tooltip' | translate}}" nzTooltipPlacement="top"><i class="exclamation triangle icon large red"></i></span>
-                                    <span *ngIf="getWorkerModel(r.value)?.is_official" nz-tooltip nzTooltipTitle="{{'worker_model_official_tooltip' | translate}}" nzTooltipPlacement="top"><i class="check circle outline icon large green"></i></span>
-                                    <span *ngIf="getWorkerModel(r.value)?.is_deprecated" nz-tooltip nzTooltipTitle="{{'worker_model_deprecated_tooltip' | translate}}}" nzTooltipPlacement="top"><i class="exclamation circle icon large orange"></i></span>
-                                </div>
-                            </div>
-                        </div>
-                        <div *ngIf="r.type === 'model' || r.type === 'service'">
-                            {{ 'requirement_options' | translate }}
-                            <textarea nz-input [(ngModel)]="r.opts" (ngModelChange)="change(r)" rows="3" placeholder="{{'requirement_placeholder_opts_' + r.type | translate}}"></textarea>
-                        </div>
-                    </div>
-                    <ng-template #rValue>
-                        <span>{{r.value}}</span>
-                        <div *ngIf="r.type === 'model' || r.type === 'service'">
-                            {{ 'requirement_options' | translate }}
-                            <span>{{r.opts}}</span>
-                        </div>
-                    </ng-template>
+                    <ng-container *ngIf="suggest && suggestWithWorkerModel && suggestWithOSArch">
+                        <app-requirements-value [requirement]="r"
+                                                (requirementChange)="change(r)"
+                                                [edit]="edit"
+                                                [suggest]="suggest"
+                                                [workerModels]="workerModelsMap"
+                                                [suggestArchOs]="suggestWithOSArch"
+                                                [suggestWorkerModels]="suggestWithWorkerModel"></app-requirements-value>
+                    </ng-container>
                 </td>
                 <td>
                     <div *ngIf="edit" class="center">
diff --git a/ui/src/app/shared/requirements/value/requirements.value.component.ts b/ui/src/app/shared/requirements/value/requirements.value.component.ts
new file mode 100644
index 000000000..159623c95
--- /dev/null
+++ b/ui/src/app/shared/requirements/value/requirements.value.component.ts
@@ -0,0 +1,70 @@
+import {
+    ChangeDetectionStrategy,
+    ChangeDetectorRef,
+    Component,
+    EventEmitter,
+    Input,
+    OnInit,
+    Output
+} from '@angular/core';
+import { Requirement } from 'app/model/requirement.model';
+import { WorkerModel } from 'app/model/worker-model.model';
+
+@Component({
+    selector: 'app-requirements-value',
+    templateUrl: './requirements.value.html',
+    styleUrls: ['./requirements.value.scss'],
+    changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class RequirementsValueComponent implements OnInit {
+
+    @Input() requirement: Requirement
+    @Output() requirementChange = new EventEmitter<Requirement>();
+
+    @Input() edit;
+    @Input() suggest: Array<string> = [];
+    aggregatedSuggestions: Array<string> = [];
+
+    filteredSuggest: Array<string> = [];
+
+    @Input() suggestWorkerModels: Array<string> = [];
+    @Input() workerModels: Map<string, WorkerModel> = new Map();
+    @Input() suggestArchOs: string[] = [];
+
+    constructor(private _cd: ChangeDetectorRef) {
+    }
+
+    ngOnInit(): void {
+        this.initFilter(this.requirement);
+        this._cd.markForCheck();
+    }
+
+    initFilter(r: Requirement): void {
+        this.requirement = r;
+        this.aggregatedSuggestions = new Array<string>();
+        if (r.type === 'os-architecture') {
+            this.aggregatedSuggestions.push(...this.suggestArchOs);
+        } else {
+            if (r.type === 'model') {
+                this.aggregatedSuggestions.push(...this.suggestWorkerModels);
+            }
+            this.aggregatedSuggestions.push(...this.suggest);
+        }
+        this.filteredSuggest = Object.assign([], this.aggregatedSuggestions);
+    }
+
+    change(r: Requirement): void {
+        this.initFilter(r);
+        this.filterSuggestion(r.value)
+        this.requirementChange.next(r);
+        this._cd.markForCheck();
+    }
+
+    filterSuggestion(value: string): void{
+        this.filteredSuggest = this.aggregatedSuggestions.filter(s => s.indexOf(value) !== -1);
+    }
+
+    selectChange(e: any): void {
+        console.log(this.requirement.value, e);
+    }
+}
diff --git a/ui/src/app/shared/requirements/value/requirements.value.html b/ui/src/app/shared/requirements/value/requirements.value.html
new file mode 100644
index 000000000..8d208d59a
--- /dev/null
+++ b/ui/src/app/shared/requirements/value/requirements.value.html
@@ -0,0 +1,47 @@
+<ng-container *ngIf="requirement.type === 'service'">
+    <ng-container *ngIf="edit;else rName">
+        Hostname:
+        <input nz-input [nzAutocomplete]="autoSuggestService" [(ngModel)]="requirement.name" (input)="change(requirement)">
+        <nz-autocomplete [nzDataSource]="filteredSuggest" nzBackfill #autoSuggestService></nz-autocomplete>
+    </ng-container>
+    <ng-template #rName>
+        <span>{{requirement.name}}</span>
+    </ng-template>
+</ng-container>
+<ng-container *ngIf="edit;else rValue">
+    <ng-container *ngIf="requirement.type !== 'model' && requirement.type !== 'os-architecture'">
+        <ng-container *ngIf="requirement.type === 'service'">
+            Docker Image:
+        </ng-container>
+        <input nz-input [nzAutocomplete]="autoSuggestImage" [(ngModel)]="requirement.value" (input)="change(requirement)">
+        <nz-autocomplete [nzDataSource]="filteredSuggest" nzBackfill #autoSuggestImage ></nz-autocomplete>
+    </ng-container>
+    <ng-container *ngIf="requirement.type === 'os-architecture'">
+        <input nz-input [nzAutocomplete]="autoOsArch" name="value-os-architecture" [(ngModel)]="requirement.value" (input)="change(requirement)">
+        <nz-autocomplete [nzDataSource]="filteredSuggest" nzBackfill #autoOsArch></nz-autocomplete>
+    </ng-container>
+    <ng-container *ngIf="requirement.type === 'model' && workerModels">
+        <div class="ui grid">
+            <div class="ui fourteen wide column">
+                <input nz-input name="value-workermodel" [nzAutocomplete]="autoWorkerModel" [(ngModel)]="requirement.value" (input)="change(requirement)">
+                <nz-autocomplete [nzDataSource]="filteredSuggest" nzBackfill #autoWorkerModel></nz-autocomplete>
+            </div>
+            <div class="ui two wide column valign">
+                <span *ngIf="workerModels.get(requirement.value)?.nb_spawn_err > 0" nz-tooltip nzTooltipTitle="{{'worker_model_spawn_error_tooltip' | translate}}" nzTooltipPlacement="top"><i class="exclamation triangle icon large red"></i></span>
+                <span *ngIf="workerModels.get(requirement.value)?.is_official" nz-tooltip nzTooltipTitle="{{'worker_model_official_tooltip' | translate}}" nzTooltipPlacement="top"><i class="check circle outline icon large green"></i></span>
+                <span *ngIf="workerModels.get(requirement.value)?.is_deprecated" nz-tooltip nzTooltipTitle="{{'worker_model_deprecated_tooltip' | translate}}}" nzTooltipPlacement="top"><i class="exclamation circle icon large orange"></i></span>
+            </div>
+        </div>
+    </ng-container>
+    <ng-container *ngIf="requirement.type === 'model' || requirement.type === 'service'">
+        {{ 'requirement_options' | translate }}
+        <textarea nz-input [ngModel]="requirement.opts" (ngModelChange)="change(requirement)" rows="3" placeholder="{{'requirement_placeholder_opts_' + requirement.type | translate}}"></textarea>
+    </ng-container>
+</ng-container>
+<ng-template #rValue>
+    <span>{{requirement.value}}</span>
+    <div *ngIf="requirement.type === 'model' || requirement.type === 'service'">
+        {{ 'requirement_options' | translate }}
+        <span>{{requirement.opts}}</span>
+    </div>
+</ng-template>
diff --git a/ui/src/app/shared/group/form/group.form.scss b/ui/src/app/shared/requirements/value/requirements.value.scss
similarity index 100%
rename from ui/src/app/shared/group/form/group.form.scss
rename to ui/src/app/shared/requirements/value/requirements.value.scss
diff --git a/ui/src/app/shared/shared.module.ts b/ui/src/app/shared/shared.module.ts
index 4fee7d37c..64421cebc 100644
--- a/ui/src/app/shared/shared.module.ts
+++ b/ui/src/app/shared/shared.module.ts
@@ -14,7 +14,6 @@ import { AsCodeEventComponent } from 'app/shared/ascode/events/ascode.event.comp
 import { AsCodeSaveFormComponent } from 'app/shared/ascode/save-form/ascode.save-form.component';
 import { AsCodeSaveModalComponent } from 'app/shared/ascode/save-modal/ascode.save-modal.component';
 import { ConditionsComponent } from 'app/shared/conditions/conditions.component';
-import { GroupFormComponent } from 'app/shared/group/form/group.form.component';
 import { AutoFocusInputComponent } from 'app/shared/input/autofocus/autofocus.input.component';
 import { CallbackPipe } from 'app/shared/pipes/callback.pipe';
 import { SelectFilterComponent } from 'app/shared/select/select.component';
@@ -48,7 +47,6 @@ import { LabelsEditComponent } from './labels/edit/labels.edit.component';
 import { MenuComponent } from './menu/menu.component';
 import { ConfirmModalComponent } from './modal/confirm/confirm.component';
 import { DeleteModalComponent } from './modal/delete/delete.component';
-import { WarningModalComponent } from './modal/warning/warning.component';
 import { ParameterFormComponent } from './parameter/form/parameter.form';
 import { ParameterListComponent } from './parameter/list/parameter.component';
 import { ParameterValueComponent } from './parameter/value/parameter.value.component';
@@ -127,7 +125,7 @@ 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 { BellFill, BookOutline, CaretUpFill, CheckOutline, DeleteOutline, KeyOutline, HistoryOutline, PhoneFill, PlusOutline, PlusSquareOutline, QuestionOutline, RestOutline, SaveOutline, SettingFill, ShareAltOutline, StarOutline, ToolFill, UnlockFill } from '@ant-design/icons-angular/icons'
+import { BellFill, BookOutline, CaretUpFill, CheckOutline, CopyOutline, DeleteOutline, KeyOutline, HistoryOutline, PhoneFill, PlusOutline, PlusSquareOutline, QuestionOutline, RestOutline, SaveOutline, SettingFill, ShareAltOutline, StarOutline, ToolFill, UnlockFill, UploadOutline } from '@ant-design/icons-angular/icons'
 import { NzDropDownModule } from 'ng-zorro-antd/dropdown';
 import { NzTagModule } from 'ng-zorro-antd/tag';
 import { NzPopoverModule } from 'ng-zorro-antd/popover';
@@ -142,13 +140,17 @@ import { NzTableModule } from 'ng-zorro-antd/table';
 import { NzAutocompleteModule } from 'ng-zorro-antd/auto-complete';
 import { NzAlertModule } from 'ng-zorro-antd/alert';
 import { NzCheckboxModule } from 'ng-zorro-antd/checkbox';
+import { NzUploadModule } from 'ng-zorro-antd/upload';
+import { NzSpinModule } from 'ng-zorro-antd/spin';
+import { NzCollapseModule } from 'ng-zorro-antd/collapse';
+import { RequirementsValueComponent } from 'app/shared/requirements/value/requirements.value.component';
 
 const ngZorroConfig: NzConfig = {
     notification: {nzPauseOnHover: true, nzPlacement: "topRight"},
     icon: {nzTheme: "outline"}
 };
 
-const icons: IconDefinition[] = [ BellFill, BookOutline, CaretUpFill, CheckOutline, DeleteOutline, KeyOutline, HistoryOutline, PhoneFill, PlusOutline, PlusSquareOutline, QuestionOutline, RestOutline, SaveOutline, SettingFill, ShareAltOutline, StarOutline, ToolFill, UnlockFill ];
+const icons: IconDefinition[] = [ BellFill, BookOutline, CaretUpFill, CheckOutline, CopyOutline, DeleteOutline, KeyOutline, HistoryOutline, PhoneFill, PlusOutline, PlusSquareOutline, QuestionOutline, RestOutline, SaveOutline, SettingFill, ShareAltOutline, StarOutline, ToolFill, UnlockFill, UploadOutline ];
 
 @NgModule({
     imports: [
@@ -158,6 +160,7 @@ const icons: IconDefinition[] = [ BellFill, BookOutline, CaretUpFill, CheckOutli
         NzButtonModule,
         NzCardModule,
         NzCheckboxModule,
+        NzCollapseModule,
         NzDropDownModule,
         NzFormModule,
         NzGridModule,
@@ -171,11 +174,13 @@ const icons: IconDefinition[] = [ BellFill, BookOutline, CaretUpFill, CheckOutli
         NzPopconfirmModule,
         NzPopoverModule,
         NzSelectModule,
+        NzSpinModule,
         NzSwitchModule,
         NzTabsModule,
         NzTableModule,
         NzTagModule,
         NzToolTipModule,
+        NzUploadModule,
         CommonModule,
         ClipboardModule,
         NgxSemanticModule,
@@ -218,7 +223,6 @@ const icons: IconDefinition[] = [ BellFill, BookOutline, CaretUpFill, CheckOutli
         DurationMsPipe,
         FavoriteCardsComponent,
         ForMapPipe,
-        GroupFormComponent,
         KeysFormComponent,
         KeysListComponent,
         KeysPipe,
@@ -237,6 +241,7 @@ const icons: IconDefinition[] = [ BellFill, BookOutline, CaretUpFill, CheckOutli
         RepoManagerFormComponent,
         RequirementsFormComponent,
         RequirementsListComponent,
+        RequirementsValueComponent,
         SafeHtmlPipe,
         ScrollviewComponent,
         SelectFilterComponent,
@@ -260,7 +265,6 @@ const icons: IconDefinition[] = [ BellFill, BookOutline, CaretUpFill, CheckOutli
         VCSStrategyComponent,
         VulnerabilitiesComponent,
         VulnerabilitiesListComponent,
-        WarningModalComponent,
         WorkflowDeleteNodeComponent,
         WorkflowHookMenuEditComponent,
         WorkflowHookModalComponent,
@@ -335,7 +339,6 @@ const icons: IconDefinition[] = [ BellFill, BookOutline, CaretUpFill, CheckOutli
         FavoriteCardsComponent,
         ForMapPipe,
         FormsModule,
-        GroupFormComponent,
         InfiniteScrollModule,
         KeysFormComponent,
         KeysListComponent,
@@ -353,6 +356,7 @@ const icons: IconDefinition[] = [ BellFill, BookOutline, CaretUpFill, CheckOutli
         NzButtonModule,
         NzCardModule,
         NzCheckboxModule,
+        NzCollapseModule,
         NzDropDownModule,
         NzFormModule,
         NzGridModule,
@@ -366,11 +370,13 @@ const icons: IconDefinition[] = [ BellFill, BookOutline, CaretUpFill, CheckOutli
         NzPopconfirmModule,
         NzPopoverModule,
         NzSelectModule,
+        NzSpinModule,
         NzSwitchModule,
         NzTabsModule,
         NzTagModule,
         NzTableModule,
         NzToolTipModule,
+        NzUploadModule,
         PaginationComponent,
         ParameterFormComponent,
         ParameterListComponent,
@@ -383,6 +389,7 @@ const icons: IconDefinition[] = [ BellFill, BookOutline, CaretUpFill, CheckOutli
         RepoManagerFormComponent,
         RequirementsFormComponent,
         RequirementsListComponent,
+        RequirementsValueComponent,
         SafeHtmlPipe,
         ScrollviewComponent,
         SelectorPipe,
@@ -404,7 +411,6 @@ const icons: IconDefinition[] = [ BellFill, BookOutline, CaretUpFill, CheckOutli
         VCSStrategyComponent,
         VulnerabilitiesComponent,
         VulnerabilitiesListComponent,
-        WarningModalComponent,
         WorkflowDeleteNodeComponent,
         WorkflowNodeAddWizardComponent,
         WorkflowNodeEditModalComponent,
diff --git a/ui/src/app/shared/variable/audit/variable.audit.html b/ui/src/app/shared/variable/audit/variable.audit.html
index 5d621d364..acf521b24 100644
--- a/ui/src/app/shared/variable/audit/variable.audit.html
+++ b/ui/src/app/shared/variable/audit/variable.audit.html
@@ -1,43 +1,17 @@
-<div class="audits" *ngIf="audits">
-    <table class="ui fixed celled table" *ngIf="getDataForCurrentPage().length > 0">
-        <thead>
-            <tr>
-                <th class="eight wide column">{{ 'audit_time_author' | translate}}</th>
-                <th class="eight wide pl50 column">{{ 'audit_change' | translate}}</th>
-            </tr>
-        </thead>
-        <tbody>
-            <tr *ngFor="let a of getDataForCurrentPage()">
-                <td>
-                    {{ a.versionned | amLocal | amDateFormat: 'DD/MM/YYYY HH:mm' }} {{ '(' + a.author + ')' }}
-                </td>
-                <td>
-                    <app-variable-diff [type]="a.type" [variableAfter]="a.variable_after"
-                        [variableBefore]="a.variable_before"></app-variable-diff>
-                </td>
-            </tr>
-        </tbody>
-        <tfoot *ngIf="getNbOfPages() > 1">
-            <tr>
-                <th colspan="2">
-                    <div class="ui right floated pagination menu">
-                        <a class="icon item" (click)="downPage()">
-                            <i class="left chevron icon"></i>
-                        </a>
-                        <a class="item" *ngFor="let page of getNbOfPages() | ngForNumber" (click)="goTopage(page)"
-                            [class.active]="currentPage === page">{{page}}</a>
-                        <a class="icon item" (click)="upPage()">
-                            <i class="right chevron icon"></i>
-                        </a>
-                    </div>
-                </th>
-            </tr>
-        </tfoot>
-    </table>
-    <div class="ui info message" *ngIf="!getDataForCurrentPage()">
-        {{ 'audit_loading' | translate }}
-    </div>
-    <div class="ui info message" *ngIf="getDataForCurrentPage() && getDataForCurrentPage().length === 0">
-        {{ 'audit_no' | translate }}
-    </div>
-</div>
+<nz-table [nzData]="audits" #audit>
+    <thead>
+        <tr>
+            <th>Change date</th>
+            <th>Change</th>
+        </tr>
+    </thead>
+    <tbody>
+        <tr *ngFor="let data of audit.data">
+            <td>{{ data.versionned | amLocal | amDateFormat: 'DD/MM/YYYY HH:mm' }} {{ '(' + data.author + ')' }}</td>
+            <td>
+                <app-variable-diff [type]="data.type" [variableAfter]="data.variable_after"
+                                   [variableBefore]="data.variable_before"></app-variable-diff>
+            </td>
+        </tr>
+    </tbody>
+</nz-table>
diff --git a/ui/src/app/shared/variable/audit/variable.audit.scss b/ui/src/app/shared/variable/audit/variable.audit.scss
index 9e82e6775..e69de29bb 100644
--- a/ui/src/app/shared/variable/audit/variable.audit.scss
+++ b/ui/src/app/shared/variable/audit/variable.audit.scss
@@ -1,14 +0,0 @@
-@import '../../../../common';
-
-.audits {
-  line-height: 1rem;
-}
-
-.table tbody tr td {
-  padding-bottom: 0px;
-  padding-top: 0px;
-}
-
-.ui.table thead th.pl50 {
-  padding-left: 50px;
-}
diff --git a/ui/src/app/shared/variable/diff/variable.diff.scss b/ui/src/app/shared/variable/diff/variable.diff.scss
index a1293506c..ce80d4274 100644
--- a/ui/src/app/shared/variable/diff/variable.diff.scss
+++ b/ui/src/app/shared/variable/diff/variable.diff.scss
@@ -14,9 +14,6 @@
   .add {
     color: green;
   }
-  .key {
-    color: black;
-  }
   .delete {
     color: red;
     text-decoration: line-through;
diff --git a/ui/src/app/shared/variable/list/variable.component.ts b/ui/src/app/shared/variable/list/variable.component.ts
index 3cca7ac27..46fc13e4f 100644
--- a/ui/src/app/shared/variable/list/variable.component.ts
+++ b/ui/src/app/shared/variable/list/variable.component.ts
@@ -4,8 +4,7 @@ import {
     Component,
     EventEmitter,
     Input,
-    Output,
-    ViewChild
+    Output
 } from '@angular/core';
 import { Project } from 'app/model/project.model';
 import { Variable, VariableAudit } from 'app/model/variable.model';
@@ -15,8 +14,9 @@ import { ProjectAuditService } from 'app/service/project/project.audit.service';
 import { VariableService } from 'app/service/variable/variable.service';
 import { Table } from 'app/shared/table/table';
 import { VariableEvent } from 'app/shared/variable/variable.event.model';
-import { SemanticModalComponent } from '@sgu/ng-semantic';
 import { finalize } from 'rxjs/operators';
+import { NzModalService } from 'ng-zorro-antd/modal';
+import { VariableAuditComponent } from 'app/shared/variable/audit/audit.component';
 
 @Component({
     selector: 'app-variable',
@@ -49,16 +49,12 @@ export class VariableComponent extends Table<Variable> {
 
     @Output() event = new EventEmitter<VariableEvent>();
 
-    @ViewChild('auditModal')
-    auditModal: SemanticModalComponent;
-
     public ready = false;
     public variableTypes: string[];
-    public currentVariableAudits: Array<VariableAudit>;
     private _variables: Variable[];
     filter: string;
 
-    constructor(private _variableService: VariableService, private _projAudit: ProjectAuditService,
+    constructor(private _variableService: VariableService, private _projAudit: ProjectAuditService, private _modalService: NzModalService,
         private _envAudit: EnvironmentAuditService, private _appAudit: ApplicationAuditService, public _cd: ChangeDetectorRef) {
         super();
         this.variableTypes = this._variableService.getTypesFromCache();
@@ -97,39 +93,34 @@ export class VariableComponent extends Table<Variable> {
      * Open audit modal
      */
     showAudit(event: any, v: Variable): void {
-        if (this.auditModal) {
-            this.currentVariableAudits = undefined;
-            switch (this.auditContext) {
-                case 'project':
-                    this._projAudit.getVariableAudit(this.project.key, v.name).subscribe(audits => {
-                        this.currentVariableAudits = audits;
-                        this._cd.markForCheck();
-                        setTimeout(() => {
-                            this.auditModal.show({ observeChanges: true });
-                        }, 100);
-                    });
-                    break;
-                case 'environment':
-                    this._envAudit.getVariableAudit(this.project.key, this.environmentName, v.name).subscribe(audits => {
-                        this.currentVariableAudits = audits;
-                        this._cd.markForCheck();
-                        setTimeout(() => {
-                            this.auditModal.show({ observeChanges: true });
-                        }, 100);
-                    });
-                    break;
-                case 'application':
-                    this._appAudit.getVariableAudit(this.project.key, this.applicationName, v.name).subscribe(audits => {
-                        this.currentVariableAudits = audits;
-                        this._cd.markForCheck();
-                        setTimeout(() => {
-                            this.auditModal.show({ observeChanges: true });
-                        }, 100);
-                    });
-                    break;
+        switch (this.auditContext) {
+            case 'project':
+                this._projAudit.getVariableAudit(this.project.key, v.name).subscribe(audits => {
+                    this.openAuditModal(audits);
+                });
+                break;
+            case 'environment':
+                this._envAudit.getVariableAudit(this.project.key, this.environmentName, v.name).subscribe(audits => {
+                    this.openAuditModal(audits);
+                });
+                break;
+            case 'application':
+                this._appAudit.getVariableAudit(this.project.key, this.applicationName, v.name).subscribe(audits => {
+                    this.openAuditModal(audits);
+                })
+                break;
             }
-        }
+    }
 
+    openAuditModal(audits: Array<VariableAudit>): void {
+        this._modalService.create({
+            nzWidth: '900px',
+            nzTitle: 'Variable audit',
+            nzContent: VariableAuditComponent,
+            nzComponentParams: {
+                audits: audits
+            }
+        });
     }
 
 }
diff --git a/ui/src/app/shared/variable/list/variable.html b/ui/src/app/shared/variable/list/variable.html
index e01b2b751..96b36abdf 100644
--- a/ui/src/app/shared/variable/list/variable.html
+++ b/ui/src/app/shared/variable/list/variable.html
@@ -106,8 +106,3 @@
         {{ 'variable_no' | translate }}
     </div>
 </div>
-<sm-modal #auditModal title="{{'audit_title_variable' | translate }}" class="fluid">
-    <modal-content>
-        <app-variable-audit [audits]="currentVariableAudits"></app-variable-audit>
-    </modal-content>
-</sm-modal>
diff --git a/ui/src/app/views/admin/service/show/service.show.html b/ui/src/app/views/admin/service/show/service.show.html
index 2a18dbb17..42eed8f2e 100644
--- a/ui/src/app/views/admin/service/show/service.show.html
+++ b/ui/src/app/views/admin/service/show/service.show.html
@@ -17,7 +17,7 @@
 
             <div *ngIf="service.group">
                 <h4>Group</h4>
-                <p><a sm-item href="#" [routerLink]="['/settings', 'group', service.group.name]">{{service.group.name}}</a></p>
+                <p><a href="#" [routerLink]="['/settings', 'group', service.group.name]">{{service.group.name}}</a></p>
             </div>
 
             <h4>Heartbeat</h4>
@@ -46,4 +46,4 @@
             <div class="ui text active loader">{{ 'common_loading' | translate }}</div>
         </ng-template>
     </app-scrollview>
-</div>
\ No newline at end of file
+</div>
diff --git a/ui/src/app/views/application/show/admin/application.admin.component.ts b/ui/src/app/views/application/show/admin/application.admin.component.ts
index 56ba4c2a5..fd39951c8 100644
--- a/ui/src/app/views/application/show/admin/application.admin.component.ts
+++ b/ui/src/app/views/application/show/admin/application.admin.component.ts
@@ -1,10 +1,9 @@
-import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit, ViewChild } from '@angular/core';
+import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit } from '@angular/core';
 import { Router } from '@angular/router';
 import { TranslateService } from '@ngx-translate/core';
 import { Store } from '@ngxs/store';
 import { Application } from 'app/model/application.model';
 import { Project } from 'app/model/project.model';
-import { WarningModalComponent } from 'app/shared/modal/warning/warning.component';
 import { ToastService } from 'app/shared/toast/ToastService';
 import { DeleteApplication, UpdateApplication } from 'app/store/applications.action';
 import { FetchIntegrationsInProject } from 'app/store/project.action';
@@ -21,8 +20,6 @@ export class ApplicationAdminComponent implements OnInit {
     @Input() application: Application;
     @Input() project: Project;
     @Input() editMode: boolean;
-    @ViewChild('updateWarning')
-    private updateWarningModal: WarningModalComponent;
 
     newName: string;
     fileTooLarge = false;
@@ -34,59 +31,60 @@ export class ApplicationAdminComponent implements OnInit {
         private _router: Router,
         private _store: Store,
         private _cd: ChangeDetectorRef
-    ) { }
+    ) {
+    }
 
     ngOnInit() {
         // Fetch project integration
-        this._store.dispatch(new FetchIntegrationsInProject({ projectKey: this.project.key }));
+        this._store.dispatch(new FetchIntegrationsInProject({projectKey: this.project.key}));
 
         this.newName = this.application.name;
         if (!this.project.permissions.writable) {
             this._router.navigate(['/project', this.project.key, 'application', this.application.name],
-                { queryParams: { tab: 'workflow' } });
+                {queryParams: {tab: 'workflow'}});
         }
     }
 
-    onSubmitApplicationUpdate(skip?: boolean): void {
-        if (!skip && this.application.externalChange) {
-            this.updateWarningModal.show();
-        } else {
-            this.loading = true;
-            let nameUpdated = this.application.name !== this.newName;
-            let app = cloneDeep(this.application);
-            app.name = this.newName;
-            this._store.dispatch(new UpdateApplication({
-                projectKey: this.project.key,
-                applicationName: this.application.name,
-                changes: app
-            })).pipe(finalize(() => {
-                this.loading = false;
-                this._cd.markForCheck();
-            }))
-                .subscribe(() => {
-                    if (this.editMode) {
-                        this._toast.info('', this._translate.instant('application_ascode_updated'));
-                    } else {
-                        this._toast.success('', this._translate.instant('application_update_ok'));
-                        if (nameUpdated) {
-                            this._router.navigate(['/project', this.project.key, 'application', this.newName]);
-                        }
+    onSubmitApplicationUpdate(): void {
+        this.loading = true;
+        let nameUpdated = this.application.name !== this.newName;
+        let app = cloneDeep(this.application);
+        app.name = this.newName;
+        this._store.dispatch(new UpdateApplication({
+            projectKey: this.project.key,
+            applicationName: this.application.name,
+            changes: app
+        })).pipe(finalize(() => {
+            this.loading = false;
+            this._cd.markForCheck();
+        }))
+            .subscribe(() => {
+                if (this.editMode) {
+                    this._toast.info('', this._translate.instant('application_ascode_updated'));
+                } else {
+                    this._toast.success('', this._translate.instant('application_update_ok'));
+                    if (nameUpdated) {
+                        this._router.navigate(['/project', this.project.key, 'application', this.newName]);
                     }
+                }
+
+            });
 
-                });
-        }
     }
 
     deleteApplication(): void {
         this.loading = true;
-        this._store.dispatch(new DeleteApplication({ projectKey: this.project.key, applicationName: this.application.name }))
+        this._store.dispatch(new DeleteApplication({
+            projectKey: this.project.key,
+            applicationName: this.application.name
+        }))
             .pipe(finalize(() => {
                 this.loading = false;
                 this._cd.markForCheck();
             }))
             .subscribe(() => {
                 this._toast.success('', this._translate.instant('application_deleted'));
-                this._router.navigate(['/project', this.project.key], { queryParams: { tab: 'applications' } });
+                this._router.navigate(['/project', this.project.key], {queryParams: {tab: 'applications'}});
             });
     }
 
diff --git a/ui/src/app/views/application/show/admin/application.admin.html b/ui/src/app/views/application/show/admin/application.admin.html
index bef6c0b40..642a46ff1 100644
--- a/ui/src/app/views/application/show/admin/application.admin.html
+++ b/ui/src/app/views/application/show/admin/application.admin.html
@@ -15,7 +15,6 @@
                     <div class="eight wide right aligned field">
                         <button class="ui green button" [class.loading]="loading" name="updateNameButton" (click)="onSubmitApplicationUpdate()" [disabled]="applicationUpdateFrom.invalid">{{ 'btn_rename' | translate }}
                         </button>
-                        <app-warning-modal [title]="_translate.instant('warning_modal_title')" [msg]="_translate.instant('warning_modal_body')" (event)="onSubmitApplicationUpdate(true)" #updateWarning></app-warning-modal>
                     </div>
                 </div>
             </form>
diff --git a/ui/src/app/views/application/show/admin/deployment/application.deployment.component.ts b/ui/src/app/views/application/show/admin/deployment/application.deployment.component.ts
index 4bf1e05a2..39ee7babd 100644
--- a/ui/src/app/views/application/show/admin/deployment/application.deployment.component.ts
+++ b/ui/src/app/views/application/show/admin/deployment/application.deployment.component.ts
@@ -1,4 +1,4 @@
-import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, ViewChild } from '@angular/core';
+import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input } from '@angular/core';
 import { TranslateService } from '@ngx-translate/core';
 import { Store } from '@ngxs/store';
 import {
@@ -8,11 +8,10 @@ import {
 } from 'app/store/applications.action';
 import { cloneDeep } from 'lodash-es';
 import { finalize } from 'rxjs/operators';
-import { Application } from '../../../../../model/application.model';
-import { ProjectIntegration } from '../../../../../model/integration.model';
-import { Project } from '../../../../../model/project.model';
-import { WarningModalComponent } from '../../../../../shared/modal/warning/warning.component';
-import { ToastService } from '../../../../../shared/toast/ToastService';
+import { Application } from 'app/model/application.model';
+import { ProjectIntegration } from 'app/model/integration.model';
+import { Project } from 'app/model/project.model';
+import { ToastService } from 'app/shared/toast/ToastService';
 
 @Component({
     selector: 'app-application-deployment',
@@ -30,15 +29,14 @@ export class ApplicationDeploymentComponent {
             this.filteredIntegrations = cloneDeep(project.integrations.filter(p => p.model.deployment));
         }
     }
+
     get project(): Project {
         return this._project;
     }
+
     @Input() application: Application;
     @Input() editMode: boolean;
 
-    @ViewChild('removeWarning') removeWarningModal: WarningModalComponent;
-    @ViewChild('linkWarning') linkWarningModal: WarningModalComponent;
-
     filteredIntegrations: Array<ProjectIntegration>;
     selectedIntegration: ProjectIntegration;
 
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 4d04813e9..ed462b22b 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
@@ -36,8 +36,6 @@
         <div class="six wide right aligned field">
             <button name="savebtn" class="ui green button" [class.loading]="loadingBtn" type="button" (click)="addIntegration()"
                 [disabled]="!selectedIntegration || loadingBtn">{{ 'btn_add' | translate }}</button>
-            <app-warning-modal [title]="_translate.instant('warning_modal_title')" [msg]="_translate.instant('warning_modal_body')"
-                (event)="addIntegration()" #linkWarning></app-warning-modal>
         </div>
     </div>
     <div class="fields">
diff --git a/ui/src/app/views/application/show/admin/repository/application.repo.component.spec.ts b/ui/src/app/views/application/show/admin/repository/application.repo.component.spec.ts
index e80c7f86f..39415dbaa 100644
--- a/ui/src/app/views/application/show/admin/repository/application.repo.component.spec.ts
+++ b/ui/src/app/views/application/show/admin/repository/application.repo.component.spec.ts
@@ -2,7 +2,7 @@ import { APP_BASE_HREF } from '@angular/common';
 import { HttpRequest } from '@angular/common/http';
 import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
 import { Component, Injector, NO_ERRORS_SCHEMA } from '@angular/core';
-import { fakeAsync, getTestBed, TestBed, tick } from '@angular/core/testing';
+import { fakeAsync, flush, getTestBed, TestBed, tick } from '@angular/core/testing';
 import { RouterTestingModule } from '@angular/router/testing';
 import { TranslateLoader, TranslateModule, TranslateParser, TranslateService } from '@ngx-translate/core';
 import { Store } from '@ngxs/store';
@@ -31,6 +31,7 @@ import { ProjectModule } from 'app/views/project/project.module';
 import { of } from 'rxjs';
 import 'rxjs/add/observable/of';
 import { ApplicationRepositoryComponent } from './application.repo.component';
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
 
 @Component({
     template: ''
@@ -76,6 +77,7 @@ describe('CDS: Application Repo Component', () => {
                 RouterTestingModule.withRoutes([
                     { path: 'project/:key', component: DummyComponent }
                 ]),
+                BrowserAnimationsModule,
                 ProjectModule,
                 ApplicationModule,
                 SharedModule,
@@ -149,22 +151,7 @@ describe('CDS: Application Repo Component', () => {
 
         expect(toast.success).toHaveBeenCalledTimes(1);
 
-        tick(100);
-        fixture.componentInstance.application.vcs_server = repoMan.name;
-        fixture.componentInstance.application.repository_fullname = 'frepo3';
-
-        tick(100);
-
-        // Detach repo
-        compiled.querySelector('.ui.red.button').click();
-        fixture.detectChanges();
-        tick(50);
-        compiled.querySelector('.ui.red.button.active').click();
-        http.expectOne(((req: HttpRequest<any>) => req.url === '/project/key1/repositories_manager/RepoManager/application/app/detach')).flush({
-            name: 'app',
-        });
-        tick(100);
-        expect(toast.success).toHaveBeenCalledTimes(2);
+        flush()
     }));
 });
 
diff --git a/ui/src/app/views/application/show/admin/repository/application.repo.component.ts b/ui/src/app/views/application/show/admin/repository/application.repo.component.ts
index ce5c65c8b..637b91a1c 100644
--- a/ui/src/app/views/application/show/admin/repository/application.repo.component.ts
+++ b/ui/src/app/views/application/show/admin/repository/application.repo.component.ts
@@ -1,4 +1,4 @@
-import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit, ViewChild } from '@angular/core';
+import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit } from '@angular/core';
 import { TranslateService } from '@ngx-translate/core';
 import { Store } from '@ngxs/store';
 import {
@@ -7,12 +7,11 @@ import {
     UpdateApplication
 } from 'app/store/applications.action';
 import { finalize, first } from 'rxjs/operators';
-import { Application } from '../../../../../model/application.model';
-import { Project } from '../../../../../model/project.model';
-import { Repository } from '../../../../../model/repositories.model';
-import { RepoManagerService } from '../../../../../service/repomanager/project.repomanager.service';
-import { WarningModalComponent } from '../../../../../shared/modal/warning/warning.component';
-import { ToastService } from '../../../../../shared/toast/ToastService';
+import { Application } from 'app/model/application.model';
+import { Project } from 'app/model/project.model';
+import { Repository } from 'app/model/repositories.model';
+import { RepoManagerService } from 'app/service/repomanager/project.repomanager.service';
+import { ToastService } from 'app/shared/toast/ToastService';
 
 @Component({
     selector: 'app-application-repo',
@@ -36,9 +35,6 @@ export class ApplicationRepositoryComponent implements OnInit {
     model: string;
     displayVCSStrategy = false;
 
-    @ViewChild('removeWarning') removeWarningModal: WarningModalComponent;
-    @ViewChild('linkWarning') linkWarningModal: WarningModalComponent;
-
     constructor(
         private _repoManagerService: RepoManagerService,
         private _toast: ToastService,
@@ -57,21 +53,19 @@ export class ApplicationRepositoryComponent implements OnInit {
         this.updateListRepo(false);
     }
 
-    removeRepository(skip?: boolean): void {
-        if (!skip && this.application.externalChange) {
-            this.removeWarningModal.show();
-        } else {
-            this.loadingBtn = true;
-            this.store.dispatch(new DeleteVcsRepoOnApplication({
-                projectKey: this.project.key,
-                applicationName: this.application.name,
-                repoManager: this.application.vcs_server
-            })).pipe(finalize(() => {
-                this.loadingBtn = false;
-                this._cd.markForCheck();
-            }))
-                .subscribe(() => this._toast.success('', this._translate.instant('application_repo_detach_ok')));
-        }
+    removeRepository(): void {
+
+        this.loadingBtn = true;
+        this.store.dispatch(new DeleteVcsRepoOnApplication({
+            projectKey: this.project.key,
+            applicationName: this.application.name,
+            repoManager: this.application.vcs_server
+        })).pipe(finalize(() => {
+            this.loadingBtn = false;
+            this._cd.markForCheck();
+        }))
+            .subscribe(() => this._toast.success('', this._translate.instant('application_repo_detach_ok')));
+
     }
 
     filterRepositories(filter: string): void {
@@ -100,25 +94,23 @@ export class ApplicationRepositoryComponent implements OnInit {
         }
     }
 
-    linkRepository(skip?: boolean): void {
-        if (!skip && this.application.externalChange) {
-            this.linkWarningModal.show();
-        } else {
-            this.loadingBtn = true;
-            this.store.dispatch(new ConnectVcsRepoOnApplication({
-                projectKey: this.project.key,
-                applicationName: this.application.name,
-                repoManager: this.selectedRepoManager,
-                repoFullName: this.selectedRepo
-            })).pipe(finalize(() => {
-                this.loadingBtn = false;
-                this._cd.markForCheck();
-            }))
-                .subscribe(() => {
-                    this.displayVCSStrategy = !this.application.vcs_strategy || !this.application.vcs_strategy.connection_type;
-                    this._toast.success('', this._translate.instant('application_repo_attach_ok'));
-                });
-        }
+    linkRepository(): void {
+
+        this.loadingBtn = true;
+        this.store.dispatch(new ConnectVcsRepoOnApplication({
+            projectKey: this.project.key,
+            applicationName: this.application.name,
+            repoManager: this.selectedRepoManager,
+            repoFullName: this.selectedRepo
+        })).pipe(finalize(() => {
+            this.loadingBtn = false;
+            this._cd.markForCheck();
+        }))
+            .subscribe(() => {
+                this.displayVCSStrategy = !this.application.vcs_strategy || !this.application.vcs_strategy.connection_type;
+                this._toast.success('', this._translate.instant('application_repo_attach_ok'));
+            });
+
     }
 
     saveVCSConfiguration(): void {
diff --git a/ui/src/app/views/application/show/admin/repository/application.repo.html b/ui/src/app/views/application/show/admin/repository/application.repo.html
index e0221920d..92f381330 100644
--- a/ui/src/app/views/application/show/admin/repository/application.repo.html
+++ b/ui/src/app/views/application/show/admin/repository/application.repo.html
@@ -1,73 +1,78 @@
 <div class="AppRepository" [hidden]="!project.vcs_servers || project.vcs_servers.length === 0">
-
-    <form class="ui form" (ngSubmit)="linkRepository()" [hidden]="application.vcs_server" *ngIf="!application.from_repository">
-        <div class="fields">
-            <div class="three wide field">
-                <label>{{'repoman_name' | translate}}</label>
-                <sm-select class="fluid search"
-                    name="repomanname"
-                    [disabled]="loadingRepos || application.from_repository"
-                    [(model)]="selectedRepoManager"
-                    [options]="{'fullTextSearch': true}"
-                    (onChange)="updateListRepo(false)">
-                    <option *ngFor="let r of project?.vcs_servers" value="{{r.name}}">{{r.name}}</option>
-                </sm-select>
+    <form nz-form  (ngSubmit)="linkRepository()" nzLayout="inline" [hidden]="application.vcs_server" *ngIf="!application.from_repository">
+        <div nz-row>
+            <div nz-col [nzSpan]="8">
+                <nz-form-item>
+                    <nz-form-label>Repository manager</nz-form-label>
+                    <nz-form-control>
+                        <nz-select nzShowSearch name="repomanname" [(ngModel)]="selectedRepoManager" (ngModelChange)="updateListRepo(false)" [disabled]="loadingRepos || application.from_repository">
+                            <nz-option *ngFor="let r of project?.vcs_servers" [nzValue]="r.name" [nzLabel]="r.name"></nz-option>
+                        </nz-select>
+                    </nz-form-control>
+                </nz-form-item>
             </div>
-            <div class="five wide field">
-                <label>{{'repo_name' | translate}}</label>
-                <sm-select
-                    class="search item"
-                    placeholder="{{'application_repo_choose' | translate}}"
-                    [(model)]="selectedRepo"
-                    [options]="{'fullTextSearch': true}"
-                    (onSearchChange)="filterRepositories($event)"
-                    [disabled]="loadingRepos || application.from_repository">
-                    <option *ngFor="let r of reposFiltered"  value="{{r.fullname}}">{{r.fullname}}</option>
-                </sm-select>
+            <div nz-col [nzSpan]="8">
+                <nz-form-item>
+                    <nz-form-label>Repository</nz-form-label>
+                    <nz-form-control>
+                        <nz-select nzShowSearch name="repository" nzPlaceHolder="Choose a repository"  (nzOnSearch)="filterRepositories($event)"
+                                   [(ngModel)]="selectedRepo" [disabled]="loadingRepos || application.from_repository">
+                            <nz-option *ngFor="let r of reposFiltered" [nzValue]="r.fullname" [nzLabel]="r.fullname"></nz-option>
+                        </nz-select>
+                    </nz-form-control>
+
+                </nz-form-item>
             </div>
-            <div class="four wide field">
-                <button name="resync" class="ui blue button" [class.loading]="loadingRepos" type="button"
-                    (click)="updateListRepo(true)"
-                >{{ 'btn_resync_repo' | translate }}</button>
+            <div nz-col [nzSpan]="4">
+                <nz-form-item>
+                    <button nz-button name="resync" [nzLoading]="loadingRepos" type="button"
+                        (click)="updateListRepo(true)">Resynchronize</button>
+                </nz-form-item>
+
             </div>
-            <div class="four wide right aligned field">
-                <button name="addrepobtn" class="ui green button" [class.loading]="loadingBtn" [disabled]="loadingBtn || application.from_repository" type="submit">{{ 'btn_link' | translate }}</button>
-                <app-warning-modal [title]="_translate.instant('warning_modal_title')" [msg]="_translate.instant('warning_modal_body')" (event)="linkRepository(true)" #linkWarning></app-warning-modal>
+            <div nz-col [nzSpan]="4">
+                <nz-form-item nzJustify="end">
+                    <button nz-button nzType="primary" name="addrepobtn"[nzLoading]="loadingBtn" [disabled]="loadingBtn || application.from_repository" type="submit">Connect</button>
+                </nz-form-item>
             </div>
         </div>
     </form>
     <div [hidden]="!application.vcs_server">
-        <form class="ui form" [hidden]="!application.vcs_server">
-            <div class="fields">
-                <div class="eight wide field">
+        <form nz-form [hidden]="!application.vcs_server">
+            <nz-form-item>
+                <nz-form-label>
                     <span>{{application.vcs_server}}</span>
                     <span> &#10145; </span>
                     <span>{{application.repository_fullname}}</span>
-                </div>
-                <div class="eight wide right aligned field">
-                    <app-delete-button [loading]="loadingBtn" [disabled]="loadingBtn || application.from_repository" (event)="removeRepository()"></app-delete-button>
-                    <app-warning-modal [title]="_translate.instant('warning_modal_title')" [msg]="_translate.instant('warning_modal_body')" (event)="removeRepository(true)" #removeWarning></app-warning-modal>
-                </div>
-            </div>
+                </nz-form-label>
+                <nz-form-control>
+                    <button nz-button nzType="primary" class="unlink" name="deleteBtn" nzDanger [nzLoading]="loadingBtn" [disabled]="loadingBtn || application.from_repository"
+                            nz-popconfirm nzPopconfirmTitle="Are you sure to unlink this repository?" (nzOnConfirm)="removeRepository()"  nzPopconfirmPlacement="topLeft">
+                        Unkink
+                    </button>
+                </nz-form-control>
+            </nz-form-item>
         </form>
         <div *ngIf="application.repository_fullname" class="strategy">
-            <div class="ui orange icon message mt5" *ngIf="!application.vcs_strategy || !application.vcs_strategy.connection_type">
-              <i class="warning sign icon"></i>
-              <div class="content">
-                <div class="header">
-                  {{'application_repo_missing_conf' | translate}}
+            <nz-alert nzType="warning" [nzMessage]="repoTemp" *ngIf="!application.vcs_strategy || !application.vcs_strategy.connection_type || true"></nz-alert>
+            <ng-template #repoTemp>
+                <div nz-row class="alert">
+                    <div nz-col [nzSpan]="2">
+                        <i class="warning sign icon"></i>
+                    </div>
+                    <div nz-col [nzSpan]="22">
+                        <div class="header">Missing configuration</div>
+                        <p>Almost done ! You have to configure at least the connection protocol to your repository just below and save your changes</p>
+                    </div>
                 </div>
-                <p>{{'application_repo_missing_conf_details' | translate}}</p>
-              </div>
-            </div>
-            <h4 class="pointing" (click)="displayVCSStrategy = !displayVCSStrategy">
-                <i class="right caret icon" *ngIf="!displayVCSStrategy"></i>
-                <i class="down caret icon" *ngIf="displayVCSStrategy"></i>
-                {{ 'application_repo_strat_title' | translate }}
-            </h4>
-            <div class="detail" [class.active]="displayVCSStrategy">
-                <app-vcs-strategy [project]="project" [(strategy)]="application.vcs_strategy" (strategyChange)="saveVCSConfiguration()" [appName]="application.name" [loading]="loadingBtn"></app-vcs-strategy>
-            </div>
+            </ng-template>
+            <nz-collapse [nzBordered]="false">
+                <nz-collapse-panel nzHeader="Configuration" [nzActive]="true">
+                    <ng-container>
+                        <app-vcs-strategy [project]="project" [(strategy)]="application.vcs_strategy" (strategyChange)="saveVCSConfiguration()" [appName]="application.name" [loading]="loadingBtn"></app-vcs-strategy>
+                    </ng-container>
+                </nz-collapse-panel>
+            </nz-collapse>
         </div>
     </div>
 </div>
diff --git a/ui/src/app/views/application/show/admin/repository/application.repo.scss b/ui/src/app/views/application/show/admin/repository/application.repo.scss
index 69cdc1890..f941a7a1f 100644
--- a/ui/src/app/views/application/show/admin/repository/application.repo.scss
+++ b/ui/src/app/views/application/show/admin/repository/application.repo.scss
@@ -1,26 +1,17 @@
-.ui.form .fields {
-  margin-bottom: 0;
-  .field {
-    .ui.button {
-      margin-top: 1.7em;
-    }
-  }
-}
-.strategy {
-  min-height: 20px;
 
-  .detail {
-    max-height: 0;
-    transition: all 0.15s ease-out;
-    opacity: 0;
+form div {
+  width: 100%;
+}
+.unlink {
+  float:right;
+}
+nz-alert {
+  margin-bottom: 10px;
+  i {
+    font-size: 2em;
   }
-
-  .detail.active {
-    max-height: 500px;
-    transition: all 0.25s ease-in;
-    opacity: 1;
+  .header {
+    font-size: 1.15em;
+    font-weight: 700;
   }
 }
-.mt5 {
-  margin-top: 5px !important;
-}
diff --git a/ui/src/app/views/application/show/application.component.ts b/ui/src/app/views/application/show/application.component.ts
index dd2dbf20b..ea930ed55 100644
--- a/ui/src/app/views/application/show/application.component.ts
+++ b/ui/src/app/views/application/show/application.component.ts
@@ -10,7 +10,6 @@ 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';
 import { AutoUnsubscribe } from 'app/shared/decorator/autoUnsubscribe';
-import { WarningModalComponent } from 'app/shared/modal/warning/warning.component';
 import { ToastService } from 'app/shared/toast/ToastService';
 import { VariableEvent } from 'app/shared/variable/variable.event.model';
 import * as applicationsActions from 'app/store/applications.action';
@@ -53,8 +52,6 @@ export class ApplicationShowComponent implements OnInit, OnDestroy {
     tabs: Array<Tab>;
     selectedTab: Tab;
 
-    @ViewChild('varWarning')
-    private varWarningModal: WarningModalComponent;
     @ViewChild('updateEditMode')
     asCodeSaveModal: AsCodeSaveModalComponent;
 
@@ -102,10 +99,14 @@ export class ApplicationShowComponent implements OnInit, OnDestroy {
                 this.application = null;
             }
             if (projectKey && this.urlAppName) {
-                this._store.dispatch(new applicationsActions.FetchApplication({ projectKey, applicationName: this.urlAppName }))
+                this._store.dispatch(new applicationsActions.FetchApplication({
+                    projectKey,
+                    applicationName: this.urlAppName
+                }))
                     .subscribe(
-                        () => {},
-                        () => this._router.navigate(['/project', projectKey], { queryParams: { tab: 'applications' } }),
+                        () => {
+                        },
+                        () => this._router.navigate(['/project', projectKey], {queryParams: {tab: 'applications'}}),
                         null
                     );
             }
@@ -137,8 +138,8 @@ export class ApplicationShowComponent implements OnInit, OnDestroy {
                 // Update recent application viewed
                 this._applicationStore.updateRecentApplication(s.currentProjectKey, this.application);
                 this._cd.markForCheck();
-        }, () => {
-                this._router.navigate(['/project', this.project.key], { queryParams: { tab: 'applications' } });
+            }, () => {
+                this._router.navigate(['/project', this.project.key], {queryParams: {tab: 'applications'}});
             });
     }
 
@@ -199,64 +200,61 @@ export class ApplicationShowComponent implements OnInit, OnDestroy {
      *
      * @param event
      */
-    variableEvent(event: VariableEvent, skip?: boolean) {
-        if (!skip && this.application.externalChange) {
-            this.varWarningModal.show(event);
-        } else {
-            event.variable.value = String(event.variable.value);
-            switch (event.type) {
-                case 'add':
-                    this.varFormLoading = true;
-                    this._store.dispatch(new applicationsActions.AddApplicationVariable({
-                        projectKey: this.project.key,
-                        applicationName: this.application.name,
-                        variable: event.variable
-                    })).pipe(finalize(() => {
-                        event.variable.updating = false;
-                        this.varFormLoading = false;
-                        this._cd.markForCheck();
-                    })).subscribe(() => {
+    variableEvent(event: VariableEvent) {
+        event.variable.value = String(event.variable.value);
+        switch (event.type) {
+            case 'add':
+                this.varFormLoading = true;
+                this._store.dispatch(new applicationsActions.AddApplicationVariable({
+                    projectKey: this.project.key,
+                    applicationName: this.application.name,
+                    variable: event.variable
+                })).pipe(finalize(() => {
+                    event.variable.updating = false;
+                    this.varFormLoading = false;
+                    this._cd.markForCheck();
+                })).subscribe(() => {
+                    if (this.editMode) {
+                        this._toast.info('', this._translate.instant('application_ascode_updated'));
+                    } else {
+                        this._toast.success('', this._translate.instant('variable_added'));
+                    }
+
+                });
+                break;
+            case 'update':
+                this._store.dispatch(new applicationsActions.UpdateApplicationVariable({
+                    projectKey: this.project.key,
+                    applicationName: this.application.name,
+                    variableName: event.variable.name,
+                    variable: event.variable
+                })).pipe(finalize(() => {
+                    event.variable.updating = false;
+                    this._cd.markForCheck();
+                }))
+                    .subscribe(() => {
                         if (this.editMode) {
                             this._toast.info('', this._translate.instant('application_ascode_updated'));
                         } else {
-                            this._toast.success('', this._translate.instant('variable_added'));
+                            this._toast.success('', this._translate.instant('variable_updated'));
                         }
-
                     });
-                    break;
-                case 'update':
-                    this._store.dispatch(new applicationsActions.UpdateApplicationVariable({
-                        projectKey: this.project.key,
-                        applicationName: this.application.name,
-                        variableName: event.variable.name,
-                        variable: event.variable
-                    })).pipe(finalize(() => {
-                        event.variable.updating = false;
-                        this._cd.markForCheck();
-                    }))
-                        .subscribe(() => {
-                            if (this.editMode) {
-                                this._toast.info('', this._translate.instant('application_ascode_updated'));
-                            } else {
-                                this._toast.success('', this._translate.instant('variable_updated'));
-                            }
-                        });
-                    break;
-                case 'delete':
-                    this._store.dispatch(new applicationsActions.DeleteApplicationVariable({
-                        projectKey: this.project.key,
-                        applicationName: this.application.name,
-                        variable: event.variable
-                    })).pipe(finalize(() => this._cd.markForCheck()))
-                        .subscribe(() => {
-                            if (this.editMode) {
-                                this._toast.info('', this._translate.instant('application_ascode_updated'));
-                            } else {
-                                this._toast.success('', this._translate.instant('variable_deleted'));
-                            }
-                        });
-                    break;
-            }
+                break;
+            case 'delete':
+                this._store.dispatch(new applicationsActions.DeleteApplicationVariable({
+                    projectKey: this.project.key,
+                    applicationName: this.application.name,
+                    variable: event.variable
+                })).pipe(finalize(() => this._cd.markForCheck()))
+                    .subscribe(() => {
+                        if (this.editMode) {
+                            this._toast.info('', this._translate.instant('application_ascode_updated'));
+                        } else {
+                            this._toast.success('', this._translate.instant('variable_deleted'));
+                        }
+                    });
+                break;
+
         }
     }
 
diff --git a/ui/src/app/views/application/show/application.html b/ui/src/app/views/application/show/application.html
index bae928002..90ad74b98 100644
--- a/ui/src/app/views/application/show/application.html
+++ b/ui/src/app/views/application/show/application.html
@@ -63,9 +63,6 @@
                                 [mode]="readOnly?'ro':'edit'"
                                 (event)="variableEvent($event)" auditContext="application" [project]="project"
                                 [applicationName]="readOnlyApplication.name"></app-variable>
-                            <app-warning-modal [title]="_translate.instant('warning_modal_title')"
-                                [msg]="_translate.instant('warning_modal_body')" (event)="variableEvent($event, true)"
-                                #varWarning></app-warning-modal>
                         </div>
                         <div *ngSwitchCase="'vuln'">
                             <app-vulnerabilities [edit]="project.permissions.writable"></app-vulnerabilities>
diff --git a/ui/src/app/views/environment/show/advanced/environment.advanced.component.ts b/ui/src/app/views/environment/show/advanced/environment.advanced.component.ts
index ad3742f67..c8be910c3 100644
--- a/ui/src/app/views/environment/show/advanced/environment.advanced.component.ts
+++ b/ui/src/app/views/environment/show/advanced/environment.advanced.component.ts
@@ -27,6 +27,8 @@ export class EnvironmentAdvancedComponent implements OnInit {
     cloneName: string;
     public loading = false;
 
+    cloneModalVisible: boolean = false;
+
     constructor(
         private _toast: ToastService,
         public _translate: TranslateService,
@@ -58,7 +60,7 @@ export class EnvironmentAdvancedComponent implements OnInit {
             });
     }
 
-    cloneEnvironment(cloneModal?: any): void {
+    cloneEnvironment(): void {
         this.loading = true;
         this.store.dispatch(new CloneEnvironment({
             projectKey: this.project.key,
@@ -67,7 +69,7 @@ export class EnvironmentAdvancedComponent implements OnInit {
         })).pipe(finalize(() => {
             this.loading = false;
             this.cloneName = '';
-            cloneModal.hide();
+            this.cloneModalVisible = false;
             this._cd.markForCheck();
         })).subscribe(() => {
             this._toast.success('', this._translate.instant('environment_cloned'));
diff --git a/ui/src/app/views/environment/show/advanced/environment.advanced.html b/ui/src/app/views/environment/show/advanced/environment.advanced.html
index 3d3aedb19..154883fee 100644
--- a/ui/src/app/views/environment/show/advanced/environment.advanced.html
+++ b/ui/src/app/views/environment/show/advanced/environment.advanced.html
@@ -12,7 +12,7 @@
                         </div>
                     </div>
                     <div class="eight wide right aligned field">
-                        <button (click)="cloneModal.show()" type="button" name="clonebtn" class="ui blue button"
+                        <button (click)="cloneModalVisible = true" type="button" name="clonebtn" class="ui blue button"
                             [class.loading]="loading" [class.disabled]="loading"><i
                                 class="copy icon"></i>{{'common_clone' | translate}}
                         </button>
@@ -38,17 +38,24 @@
             </div>
         </app-zone-content>
     </app-zone>
-    <sm-modal title="{{'environment_title_clone' | translate}} '{{environment.name}}'" class="small" #cloneModal>
-        <modal-content>
-            <div class="ui fluid action input">
-                <input type="text" placeholder="{{'environment_clone_placeholder' | translate}}"
-                    [(ngModel)]="cloneName">
-                <button class="ui blue right labeled icon button" [class.disabled]="!cloneName"
-                    [class.loading]="loading" (click)="cloneEnvironment(cloneModal)">
-                    <i class="copy icon"></i>
-                    {{'common_clone' | translate}}
-                </button>
-            </div>
-        </modal-content>
-    </sm-modal>
+    <nz-modal nzTitle="{{'environment_title_clone' | translate}} '{{environment.name}}'" [nzVisible]="cloneModalVisible">
+        <div *nzModalContent>
+            <form nz-form>
+                <nz-form-item>
+                    <nz-form-label>Name</nz-form-label>
+                    <nz-form-control>
+                        <input nz-input type="text" name="name" placeholder="{{'environment_clone_placeholder' | translate}}"
+                               [(ngModel)]="cloneName">
+                    </nz-form-control>
+                </nz-form-item>
+            </form>
+        </div>
+        <div *nzModalFooter>
+            <button nz-button (click)="cloneModalVisible = false">Cancel</button>
+            <button nz-button nzType="primary" [disabled]="!cloneName"
+                    [nzLoading]="loading" (click)="cloneEnvironment()">
+                <i nz-icon nzType="copy" nzTheme="outline"></i>Clone
+            </button>
+        </div>
+    </nz-modal>
 </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 f4fc2fc40..8e5ed5cbe 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
@@ -126,7 +126,7 @@ describe('CDS: Pipeline Show', () => {
         let event: ParameterEvent = new ParameterEvent('add', param);
         let store: Store = TestBed.get(Store);
         spyOn(store, 'dispatch').and.callFake(() => of(new Pipeline()));
-        fixture.componentInstance.parameterEvent(event, true);
+        fixture.componentInstance.parameterEvent(event);
         expect(store.dispatch).toHaveBeenCalledWith(new AddPipelineParameter({
             projectKey: 'key1',
             pipelineName: 'pip1',
@@ -136,7 +136,7 @@ describe('CDS: Pipeline Show', () => {
         // Update
 
         event.type = 'update';
-        fixture.componentInstance.parameterEvent(event, true);
+        fixture.componentInstance.parameterEvent(event);
         expect(store.dispatch).toHaveBeenCalledWith(new UpdatePipelineParameter({
             projectKey: 'key1',
             pipelineName: 'pip1',
@@ -146,7 +146,7 @@ describe('CDS: Pipeline Show', () => {
 
         // Delete
         event.type = 'delete';
-        fixture.componentInstance.parameterEvent(event, true);
+        fixture.componentInstance.parameterEvent(event);
         expect(store.dispatch).toHaveBeenCalledWith(new DeletePipelineParameter({
             projectKey: 'key1',
             pipelineName: 'pip1',
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 d0132193f..474e7a2ce 100644
--- a/ui/src/app/views/pipeline/show/pipeline.show.component.ts
+++ b/ui/src/app/views/pipeline/show/pipeline.show.component.ts
@@ -12,11 +12,11 @@ import { KeyService } from 'app/service/keys/keys.service';
 import { PipelineCoreService } from 'app/service/pipeline/pipeline.core.service';
 import { AsCodeSaveModalComponent } from 'app/shared/ascode/save-modal/ascode.save-modal.component';
 import { AutoUnsubscribe } from 'app/shared/decorator/autoUnsubscribe';
-import { WarningModalComponent } from 'app/shared/modal/warning/warning.component';
 import { ParameterEvent } from 'app/shared/parameter/parameter.event.model';
 import { ToastService } from 'app/shared/toast/ToastService';
 import {
-    AddPipelineParameter, CancelPipelineEdition,
+    AddPipelineParameter,
+    CancelPipelineEdition,
     DeletePipelineParameter,
     FetchPipeline,
     UpdatePipelineParameter
@@ -65,8 +65,6 @@ export class PipelineShowComponent implements OnInit, OnDestroy {
     pipName: string;
 
     queryParams: Params;
-    @ViewChild('paramWarning')
-    parameterModalWarning: WarningModalComponent;
     @ViewChild('updateEditMode')
     asCodeSaveModal: AsCodeSaveModalComponent;
 
@@ -119,7 +117,8 @@ export class PipelineShowComponent implements OnInit, OnDestroy {
             });
     }
 
-    ngOnDestroy(): void {} // Should be set to use @AutoUnsubscribe with AOT
+    ngOnDestroy(): void {
+    } // Should be set to use @AutoUnsubscribe with AOT
 
     refreshDatas(key: string, pipName: string) {
         this._store.dispatch(new FetchPipeline({
@@ -208,7 +207,7 @@ export class PipelineShowComponent implements OnInit, OnDestroy {
                 this.initTabs();
                 this._cd.markForCheck();
             }, () => {
-                this._router.navigate(['/project', this.projectKey], { queryParams: { tab: 'pipelines' } });
+                this._router.navigate(['/project', this.projectKey], {queryParams: {tab: 'pipelines'}});
             });
     }
 
@@ -239,7 +238,7 @@ export class PipelineShowComponent implements OnInit, OnDestroy {
             })
         }
         if (this.project?.permissions?.writable) {
-            this.tabs.push( <Tab>{
+            this.tabs.push(<Tab>{
                 title: 'Advanced',
                 icon: 'graduation',
                 key: 'advanced'
@@ -251,59 +250,55 @@ export class PipelineShowComponent implements OnInit, OnDestroy {
         this.selectedTab = tab;
     }
 
-    parameterEvent(event: ParameterEvent, skip?: boolean): void {
-        if (!skip && this.pipeline.externalChange) {
-            this.parameterModalWarning.show(event);
-        } else {
-            if (event.parameter) {
-                event.parameter.value = String(event.parameter.value);
-            }
-            switch (event.type) {
-                case 'add':
-                    this.paramFormLoading = true;
-                    this._store.dispatch(new AddPipelineParameter({
-                        projectKey: this.project.key,
-                        pipelineName: this.pipeline.name,
-                        parameter: event.parameter
-                    })).pipe(finalize(() => {
-                        this.paramFormLoading = false;
-                        this._cd.markForCheck();
-                    })).subscribe(() => {
-                        if (this.editMode) {
-                            this._toast.info('', this._translate.instant('pipeline_ascode_updated'));
-                        } else {
-                            this._toast.success('', this._translate.instant('parameter_added'));
-                        }
-                    });
-                    break;
-                case 'update':
-                    this._store.dispatch(new UpdatePipelineParameter({
-                        projectKey: this.project.key,
-                        pipelineName: this.pipeline.name,
-                        parameterName: event.parameter.previousName || event.parameter.name,
-                        parameter: event.parameter
-                    })).subscribe(() => {
-                        if (this.editMode) {
-                            this._toast.info('', this._translate.instant('pipeline_ascode_updated'));
-                        } else {
-                            this._toast.success('', this._translate.instant('parameter_updated'));
-                        }
-                    });
-                    break;
-                case 'delete':
-                    this._store.dispatch(new DeletePipelineParameter({
-                        projectKey: this.project.key,
-                        pipelineName: this.pipeline.name,
-                        parameter: event.parameter
-                    })).subscribe(() => {
-                        if (this.editMode) {
-                            this._toast.info('', this._translate.instant('pipeline_ascode_updated'));
-                        } else {
-                            this._toast.success('', this._translate.instant('parameter_deleted'));
-                        }
-                    });
-                    break;
-            }
+    parameterEvent(event: ParameterEvent): void {
+        if (event.parameter) {
+            event.parameter.value = String(event.parameter.value);
+        }
+        switch (event.type) {
+            case 'add':
+                this.paramFormLoading = true;
+                this._store.dispatch(new AddPipelineParameter({
+                    projectKey: this.project.key,
+                    pipelineName: this.pipeline.name,
+                    parameter: event.parameter
+                })).pipe(finalize(() => {
+                    this.paramFormLoading = false;
+                    this._cd.markForCheck();
+                })).subscribe(() => {
+                    if (this.editMode) {
+                        this._toast.info('', this._translate.instant('pipeline_ascode_updated'));
+                    } else {
+                        this._toast.success('', this._translate.instant('parameter_added'));
+                    }
+                });
+                break;
+            case 'update':
+                this._store.dispatch(new UpdatePipelineParameter({
+                    projectKey: this.project.key,
+                    pipelineName: this.pipeline.name,
+                    parameterName: event.parameter.previousName || event.parameter.name,
+                    parameter: event.parameter
+                })).subscribe(() => {
+                    if (this.editMode) {
+                        this._toast.info('', this._translate.instant('pipeline_ascode_updated'));
+                    } else {
+                        this._toast.success('', this._translate.instant('parameter_updated'));
+                    }
+                });
+                break;
+            case 'delete':
+                this._store.dispatch(new DeletePipelineParameter({
+                    projectKey: this.project.key,
+                    pipelineName: this.pipeline.name,
+                    parameter: event.parameter
+                })).subscribe(() => {
+                    if (this.editMode) {
+                        this._toast.info('', this._translate.instant('pipeline_ascode_updated'));
+                    } else {
+                        this._toast.success('', this._translate.instant('parameter_deleted'));
+                    }
+                });
+                break;
         }
     }
 
diff --git a/ui/src/app/views/pipeline/show/pipeline.show.html b/ui/src/app/views/pipeline/show/pipeline.show.html
index 074990331..657170630 100644
--- a/ui/src/app/views/pipeline/show/pipeline.show.html
+++ b/ui/src/app/views/pipeline/show/pipeline.show.html
@@ -65,9 +65,6 @@
                             <app-parameter-form [project]="project"
                                 (createParameterEvent)="parameterEvent($event)" [keys]="keys"></app-parameter-form>
                         </ng-container>
-                        <app-warning-modal [title]="_translate.instant('warning_modal_title')"
-                            [msg]="_translate.instant('warning_modal_body')" (event)="parameterEvent($event, true)"
-                            #paramWarning></app-warning-modal>
                     </div>
                     <div *ngSwitchCase="'usage'">
                         <app-usage [project]="project" [applications]="applications" [workflows]="workflows"
diff --git a/ui/src/app/views/pipeline/show/workflow/pipeline.workflow.component.ts b/ui/src/app/views/pipeline/show/workflow/pipeline.workflow.component.ts
index 909ebf238..9a29f7149 100644
--- a/ui/src/app/views/pipeline/show/workflow/pipeline.workflow.component.ts
+++ b/ui/src/app/views/pipeline/show/workflow/pipeline.workflow.component.ts
@@ -100,8 +100,7 @@ export class PipelineWorkflowComponent implements OnInit, OnDestroy {
     }
     @Input() queryParams: {};
 
-    @ViewChild('editStageModal')
-    editStageModal: SemanticModalComponent;
+    stageModalVisible: boolean = false;
 
     pipeline: Pipeline;
     selectedStage: Stage;
@@ -292,7 +291,7 @@ export class PipelineWorkflowComponent implements OnInit, OnDestroy {
                         } else {
                             this._toast.info('', this._translate.instant('pipeline_ascode_updated'));
                         }
-                        this.editStageModal.hide();
+                        this.stageModalVisible = false;
                     });
                 break;
             case 'delete':
@@ -310,7 +309,7 @@ export class PipelineWorkflowComponent implements OnInit, OnDestroy {
                         } else {
                             this._toast.info('', this._translate.instant('pipeline_ascode_updated'));
                         }
-                        this.editStageModal.hide();
+                        this.stageModalVisible = false;
                         this.selectedStage = null;
                         this.selectedJob = null;
                     });
@@ -320,9 +319,7 @@ export class PipelineWorkflowComponent implements OnInit, OnDestroy {
 
     openEditModal(s: Stage): void {
         this.selectedStage = cloneDeep(s);
-        if (this.editStageModal) {
-            this.editStageModal.show({ autofocus: false, closable: false, observeChanges: true });
-        }
+        this.stageModalVisible = true;
     }
 
     selectStage(s: Stage): void {
diff --git a/ui/src/app/views/pipeline/show/workflow/pipeline.workflow.html b/ui/src/app/views/pipeline/show/workflow/pipeline.workflow.html
index 61210afac..52ded9a54 100644
--- a/ui/src/app/views/pipeline/show/workflow/pipeline.workflow.html
+++ b/ui/src/app/views/pipeline/show/workflow/pipeline.workflow.html
@@ -74,22 +74,28 @@
         </app-action>
     </div>
 </div>
-
-<sm-modal title="{{ 'pipeline_stage_edit' | translate }}" class="fluid" #editStageModal>
-    <modal-content>
+<nz-modal [nzVisible]="stageModalVisible" (nzOnCancel)="stageModalVisible = false" [nzWidth]="900">
+    <div *nzModalContent>
         <app-pipeline-stage-form [project]="project" [pipeline]="pipeline" [stage]="selectedStage"
-            [readOnly]="readOnly">
+                                 [readOnly]="readOnly">
         </app-pipeline-stage-form>
-    </modal-content>
-    <modal-actions>
-        <button class="ui grey button" [disabled]="loadingStage"
-            (click)="editStageModal.hide()">{{ 'btn_cancel' | translate }}</button>
-        <button *ngIf="!readOnly" class="ui green button" [disabled]="loadingStage" [class.loading]="loadingStage"
-            (click)="stageEvent('update')">
-            <span *ngIf="!editMode">{{ 'btn_save' | translate }}</span>
-            <span *ngIf="editMode">{{ 'btn_apply' | translate }}</span>
+    </div>
+    <div *nzModalFooter>
+        <button nz-button [disabled]="loadingStage"
+                (click)="stageModalVisible = false">Cancel</button>
+        <button nz-button nzType="primary" *ngIf="!readOnly" [disabled]="loadingStage" [nzLoading]="loadingStage"
+                (click)="stageEvent('update')">
+            <span *ngIf="!editMode">Save</span>
+            <span *ngIf="editMode">Apply</span>
         </button>
-        <app-delete-button (event)="stageEvent('delete')" *ngIf="!selectedStage?.hasChanged && !readOnly">
-        </app-delete-button>
-    </modal-actions>
-</sm-modal>
+
+        <button nz-popconfirm nzDanger *ngIf="!selectedStage?.hasChanged && !readOnly"
+                name="deleteBtn"
+                nzPopconfirmTitle="Are you sure delete this stage?"
+                (nzOnConfirm)="stageEvent('delete')"
+                nzPopconfirmPlacement="topLeft"
+                nz-button>
+            <i nz-icon nzType="delete" nzTheme="outline"></i>Delete
+        </button>
+    </div>
+</nz-modal>
diff --git a/ui/src/app/views/pipeline/show/workflow/stage/form/pipeline.stage.form.html b/ui/src/app/views/pipeline/show/workflow/stage/form/pipeline.stage.form.html
index c5b5ad36f..3e144b02f 100644
--- a/ui/src/app/views/pipeline/show/workflow/stage/form/pipeline.stage.form.html
+++ b/ui/src/app/views/pipeline/show/workflow/stage/form/pipeline.stage.form.html
@@ -1,27 +1,20 @@
-<form class="ui form" *ngIf="project && pipeline && stage">
-    <div class="field">
-        <div class="inline fields">
-            <div class="eight wide field">
-                <label>{{'common_name' | translate}}</label>
-                <input type="text" name="stageName" [(ngModel)]="stage.name" (keydown)="stage.hasChanged = true"
-                    *ngIf="!readOnly">
-                <div class="inline" *ngIf="readOnly">{{stage.name}}
-                </div>
-            </div>
-            <div class="eight wide field">
-                <div class="ui checkbox">
-                    <input type="checkbox" id="enabled_stage_{{stage.id}}" name="enabled" [(ngModel)]="stage.enabled"
-                        (change)="stage.hasChanged = true" [disabled]="readOnly">
-                    <label for="enabled_stage_{{stage.id}}">{{ 'common_enable' | translate }}</label>
-                </div>
-            </div>
-        </div>
-        <div class="field">
-            <h4>Conditions: </h4>
-            <app-conditions *ngIf="!loading" [project]="project" [readonly]="readOnly"
-                [triggerConditions]="triggerConditions" [(conditions)]="stage.conditions">
-            </app-conditions>
-            <div class="ui active centered inline loader" *ngIf="loading"></div>
-        </div>
-    </div>
+<form nz-form *ngIf="project && pipeline && stage" nzLayout="vertical">
+    <nz-form-item>
+        <nz-form-label>Name:</nz-form-label>
+        <nz-form-control>
+            <input nz-input type="text" name="stageName" [(ngModel)]="stage.name" (keydown)="stage.hasChanged = true" *ngIf="!readOnly">
+            <span *ngIf="readOnly">{{stage.name}}</span>
+        </nz-form-control>
+    </nz-form-item>
+    <label nz-checkbox name="enabled" [(ngModel)]="stage.enabled" (ngModelChange)="stage.hasChanged=true" [disabled]="readOnly">Enabled</label>
+    <nz-form-item>
+        <nz-form-label>Conditions:</nz-form-label>
+        <nz-form-control>
+            <nz-spin nzTip="Loading..." [nzSpinning]="loading">
+                <app-conditions [project]="project" [readonly]="readOnly"
+                                [triggerConditions]="triggerConditions" [(conditions)]="stage.conditions">
+                </app-conditions>
+            </nz-spin>
+        </nz-form-control>
+    </nz-form-item>
 </form>
diff --git a/ui/src/app/views/pipeline/show/workflow/stage/form/pipeline.stage.form.scss b/ui/src/app/views/pipeline/show/workflow/stage/form/pipeline.stage.form.scss
index 242f4a4bc..379edbdde 100644
--- a/ui/src/app/views/pipeline/show/workflow/stage/form/pipeline.stage.form.scss
+++ b/ui/src/app/views/pipeline/show/workflow/stage/form/pipeline.stage.form.scss
@@ -1,3 +1,3 @@
-h4 {
+nz-form-item {
   margin-top: 20px;
 }
diff --git a/ui/src/app/views/project/add/project.add.component.ts b/ui/src/app/views/project/add/project.add.component.ts
index a70190918..91674fb7f 100644
--- a/ui/src/app/views/project/add/project.add.component.ts
+++ b/ui/src/app/views/project/add/project.add.component.ts
@@ -1,15 +1,14 @@
-import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ViewChild } from '@angular/core';
+import { ChangeDetectionStrategy, ChangeDetectorRef, Component } from '@angular/core';
 import { Router } from '@angular/router';
 import { TranslateService } from '@ngx-translate/core';
 import { Store } from '@ngxs/store';
 import { AddProject } from 'app/store/project.action';
-import { SemanticModalComponent } from '@sgu/ng-semantic';
 import { finalize, first } from 'rxjs/operators';
-import { Group, GroupPermission } from '../../../model/group.model';
-import { Project } from '../../../model/project.model';
-import { GroupService } from '../../../service/group/group.service';
-import { PermissionService } from '../../../shared/permission/permission.service';
-import { ToastService } from '../../../shared/toast/ToastService';
+import { Group, GroupPermission } from 'app/model/group.model';
+import { Project } from 'app/model/project.model';
+import { GroupService } from 'app/service/group/group.service';
+import { PermissionService } from 'app/shared/permission/permission.service';
+import { ToastService } from 'app/shared/toast/ToastService';
 
 @Component({
     selector: 'app-project-add',
@@ -30,8 +29,7 @@ export class ProjectAddComponent {
 
     groupList: Group[];
 
-    @ViewChild('createGroupModal')
-    modalCreateGroup: SemanticModalComponent;
+    groupModalVisible: boolean = false;
 
     constructor(
         private _toast: ToastService,
@@ -140,7 +138,7 @@ export class ProjectAddComponent {
             .subscribe(() => {
             this._toast.success('', this._translate.instant('group_added'));
             this.loadGroups(this.newGroup.name);
-            this.modalCreateGroup.hide();
+            this.groupModalVisible = false;
         });
     }
 
diff --git a/ui/src/app/views/project/add/project.add.html b/ui/src/app/views/project/add/project.add.html
index 7c09dd4d4..ccf4b6c73 100644
--- a/ui/src/app/views/project/add/project.add.html
+++ b/ui/src/app/views/project/add/project.add.html
@@ -1,70 +1,74 @@
-<div id="AddProject" class="scrollingContent">
-    <form class="ui form" (ngSubmit)="createProject()">
-        <h2>{{ 'project_create' | translate }}</h2>
-        <div class="ui two column centered grid">
-            <div class="column">
-                <div class="field">
-                    <label>{{ 'project_name' | translate }}</label>
-                    <input type="text" name="projectname" [(ngModel)]="project.name" (keyup)="generateKey($event.target.value)">
-                </div>
-                <div class="ui error message" *ngIf="nameError">
-                    {{ 'project_name_error' | translate }}
-                </div>
-                <div class="field">
-                    <label>{{ 'project_key' | translate }}</label>
-                    <input type="text" name="projectkey" [(ngModel)]="project.key">
-                </div>
-                <div class="field">
-                    <label>{{ 'project_description' | translate }}</label>
-                    <textarea type="text" name="projectdescription" [(ngModel)]="project.description"></textarea>
-                </div>
-                <div class="ui error message" *ngIf="keyError">
-                    {{ 'project_key_error' | translate }}
-                </div>
-
-                <div class="field center aligned">
-                    <label>{{ 'project_icon' | translate }} ({{'common_optional' | translate}})</label>
-                    <div>
+<div class="AddProject scrollingContent">
+    <div nz-row>
+        <div nz-col nzSpan="12" nzOffset="6">
+            <h2>Create a new project</h2>
+            <form nz-form (ngSubmit)="createProject()" nzLayout="vertical">
+                <nz-form-item>
+                    <nz-form-label>Project name</nz-form-label>
+                    <nz-form-control>
+                        <input nz-input name="projectname" [(ngModel)]="project.name" (keyup)="generateKey($event.target.value)">
+                    </nz-form-control>
+                </nz-form-item>
+                <nz-alert *ngIf="nameError" nzType="error" nzMessage="project name is mandatory"></nz-alert>
+                <nz-form-item>
+                    <nz-form-label>Project unique key</nz-form-label>
+                    <nz-form-control>
+                        <input nz-input name="projectkey" [(ngModel)]="project.key">
+                    </nz-form-control>
+                </nz-form-item>
+                <nz-alert *ngIf="keyError" nzType="error" nzMessage="project unique key is mandatory and must be in upper case"></nz-alert>
+                <nz-form-item>
+                    <nz-form-label>Description</nz-form-label>
+                    <nz-form-control>
+                        <textarea nz-input name="projectdescription" [(ngModel)]="project.description"></textarea>
+                    </nz-form-control>
+                </nz-form-item>
+                <nz-form-item>
+                    <nz-form-label>Project icon (Optional)</nz-form-label>
+                    <nz-form-control>
                         <div *ngIf="!fileTooLarge && project.icon">
                             <img class="proj-icon" [src]="project.icon" alt="icon"/>
                         </div>
-                        <app-upload-button accept=".png,.jpg,.jpeg" image="true" (event)="fileEvent($event)">
-                        </app-upload-button>
-                    </div>
-                </div>
-                <div class="ui red message field" *ngIf="fileTooLarge"  [textContent]="'common_file_too_large' | translate"></div>
-
+                        <app-upload-button accept=".png,.jpg,.jpeg" image="true" (event)="fileEvent($event)"></app-upload-button>
+                    </nz-form-control>
+                </nz-form-item>
+                <nz-alert *ngIf="fileTooLarge" nzType="error" nzMessage="Your file is too large (max 100Ko)"></nz-alert>
                 <hr>
-
-                <div class="ui message">
-                  {{'project_add_group_help' | translate}}
-                </div>
-                <div class="field">
-                  {{ 'project_permission_form_wizard_title' | translate }} <a class="pointing" (click)="modalCreateGroup.show()">{{ 'project_permission_form_wizard_new' | translate }}</a>
-                  <sm-select class="fluid search"
-                      [options]="{'fullTextSearch': true}"
-                      [model]="group?.id"
-                      (modelChange)="setGroup($event)"
-                      id="PermissionGroup">
-                      <option *ngFor="let g of groupList" [value]="g.id">{{g.name}}</option>
-                  </sm-select>
-                </div>
-
-            </div>
-            <div class="row">
-                <div class="right aligned column">
-                    <button class="ui green button" name="createproject" [class.loading]="loading"><i class="save icon"></i>{{ 'btn_create' | translate }}</button>
-                </div>
-            </div>
+                <nz-alert nzType="info" nzMessage="{{'project_add_group_help' | translate}}"></nz-alert>
+                <nz-form-item>
+                    <nz-form-label>
+                        {{ 'project_permission_form_wizard_title' | translate }} <a class="pointing" (click)="groupModalVisible = true">{{ 'project_permission_form_wizard_new' | translate }}</a>
+                    </nz-form-label>
+                    <nz-form-control>
+                        <nz-select name="group" nzShowSearch [ngModel]="group?.id" (ngModelChange)="setGroup($event)">
+                            <nz-option *ngFor="let g of groupList" [nzValue]="g.id" [nzLabel]="g.name"></nz-option>
+                        </nz-select>
+                    </nz-form-control>
+                </nz-form-item>
+                <nz-form-item nzJustify="end">
+                    <nz-form-control>
+                        <button nz-button nzType="primary" name="createproject" [nzLoading]="loading">
+                            <i nz-icon nzType="save" nzTheme="outline"></i>Create
+                        </button>
+                    </nz-form-control>
+                </nz-form-item>
+            </form>
         </div>
-    </form>
+    </div>
 </div>
-<sm-modal title="{{ 'group_create_title' | translate}}" class="fluid" #createGroupModal>
-    <modal-content>
-        <app-group-form mode="nonform" [group]="newGroup"></app-group-form>
-    </modal-content>
-    <modal-actions>
-        <div class="ui grey button" (click)="modalCreateGroup.hide()">{{ 'btn_cancel' | translate }}</div>
-        <div class="ui green button" name="creategroup" (click)="createGroup()" [class.loading]="loading" [class.disabled]="loading">{{ 'btn_create' | translate }}</div>
-    </modal-actions>
-</sm-modal>
+<nz-modal nzTitle="Create a group" [nzVisible]="groupModalVisible">
+    <div *nzModalContent>
+        <form nz-form>
+            <nz-form-item>
+                <nz-form-label>Groupe name</nz-form-label>
+                <nz-form-control>
+                    <input nz-input type="text" name="groupname" [(ngModel)]="newGroup.name">
+                </nz-form-control>
+            </nz-form-item>
+        </form>
+    </div>
+    <div *nzModalFooter>
+        <button nz-button (click)="groupModalVisible = false">Cancel</button>
+        <button nz-button nzType="primary" name="creategroup" (click)="createGroup()" [nzLoading]="loading" [disabled]="loading">Create</button>
+    </div>
+</nz-modal>
diff --git a/ui/src/app/views/project/add/project.add.scss b/ui/src/app/views/project/add/project.add.scss
index 412e0667a..445ccaaf0 100644
--- a/ui/src/app/views/project/add/project.add.scss
+++ b/ui/src/app/views/project/add/project.add.scss
@@ -1,16 +1,16 @@
-#AddProject {
-  display: flex;
-  flex-direction: column;
-  align-content: stretch;
-  height: 100%;
-  padding-top: 20px;
+.AddProject {
+  margin-top: 20px;
 
   h2 {
     text-align: center;
   }
 
-  .proj-icon {
-    height: 5em;
-    width: 5em;
+  hr {
+    margin-top: 20px;
+    margin-bottom: 20px;
+  }
+
+  nz-alert {
+    margin-bottom: 20px;
   }
 }
diff --git a/ui/src/app/views/project/show/admin/project.admin.component.ts b/ui/src/app/views/project/show/admin/project.admin.component.ts
index 3638f487b..d3d6faf85 100644
--- a/ui/src/app/views/project/show/admin/project.admin.component.ts
+++ b/ui/src/app/views/project/show/admin/project.admin.component.ts
@@ -1,9 +1,8 @@
-import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit, ViewChild } from '@angular/core';
+import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit } from '@angular/core';
 import { Router } from '@angular/router';
 import { TranslateService } from '@ngx-translate/core';
 import { Store } from '@ngxs/store';
 import { Project } from 'app/model/project.model';
-import { WarningModalComponent } from 'app/shared/modal/warning/warning.component';
 import { ToastService } from 'app/shared/toast/ToastService';
 import { DeleteProject, UpdateProject } from 'app/store/project.action';
 import { finalize } from 'rxjs/operators';
@@ -17,8 +16,6 @@ import { finalize } from 'rxjs/operators';
 export class ProjectAdminComponent implements OnInit {
 
     @Input() project: Project;
-    @ViewChild('updateWarning')
-    private warningUpdateModal: WarningModalComponent;
 
     loading = false;
     fileTooLarge = false;
@@ -29,31 +26,28 @@ export class ProjectAdminComponent implements OnInit {
         private _router: Router,
         private _store: Store,
         private _cd: ChangeDetectorRef
-    ) { }
+    ) {
+    }
 
     ngOnInit(): void {
         if (!this.project.permissions.writable) {
-            this._router.navigate(['/project', this.project.key], { queryParams: { tab: 'applications' } });
+            this._router.navigate(['/project', this.project.key], {queryParams: {tab: 'applications'}});
         }
     }
 
-    onSubmitProjectUpdate(skip?: boolean) {
-        if (!skip && this.project.externalChange) {
-            this.warningUpdateModal.show();
-        } else {
-            this.loading = true;
-            this._store.dispatch(new UpdateProject(this.project))
-                .pipe(finalize(() => {
-                    this.loading = false;
-                    this._cd.markForCheck();
-                }))
-                .subscribe(() => this._toast.success('', this._translate.instant('project_update_msg_ok')));
-        }
+    onSubmitProjectUpdate() {
+        this.loading = true;
+        this._store.dispatch(new UpdateProject(this.project))
+            .pipe(finalize(() => {
+                this.loading = false;
+                this._cd.markForCheck();
+            }))
+            .subscribe(() => this._toast.success('', this._translate.instant('project_update_msg_ok')));
     }
 
     deleteProject(): void {
         this.loading = true;
-        this._store.dispatch(new DeleteProject({ projectKey: this.project.key }))
+        this._store.dispatch(new DeleteProject({projectKey: this.project.key}))
             .pipe(finalize(() => {
                 this.loading = false;
                 this._cd.markForCheck();
diff --git a/ui/src/app/views/project/show/admin/project.admin.html b/ui/src/app/views/project/show/admin/project.admin.html
index f6c1bce72..10a86e669 100644
--- a/ui/src/app/views/project/show/admin/project.admin.html
+++ b/ui/src/app/views/project/show/admin/project.admin.html
@@ -20,7 +20,6 @@
                     </div>
                 </div>
             </form>
-            <app-warning-modal [title]="_translate.instant('warning_modal_title')" [msg]="_translate.instant('warning_modal_body')" (event)="onSubmitProjectUpdate(true)" #updateWarning></app-warning-modal>
         </app-zone-content>
     </app-zone>
     <app-zone header="{{ 'project_description' | translate }}">
@@ -40,7 +39,6 @@
                     </div>
                 </div>
             </form>
-            <app-warning-modal [title]="_translate.instant('warning_modal_title')" [msg]="_translate.instant('warning_modal_body')" (event)="onSubmitProjectUpdate(true)" #updateWarning></app-warning-modal>
         </app-zone-content>
     </app-zone>
     <app-zone header="{{ 'project_icon' | translate }}">
diff --git a/ui/src/app/views/project/show/admin/repomanager/list/project.repomanager.list.component.ts b/ui/src/app/views/project/show/admin/repomanager/list/project.repomanager.list.component.ts
index b37c97e01..d75b4408d 100644
--- a/ui/src/app/views/project/show/admin/repomanager/list/project.repomanager.list.component.ts
+++ b/ui/src/app/views/project/show/admin/repomanager/list/project.repomanager.list.component.ts
@@ -5,7 +5,6 @@ import { Project } from 'app/model/project.model';
 import { RepositoriesManager } from 'app/model/repositories.model';
 import { RepoManagerService } from 'app/service/repomanager/project.repomanager.service';
 import { ConfirmModalComponent } from 'app/shared/modal/confirm/confirm.component';
-import { WarningModalComponent } from 'app/shared/modal/warning/warning.component';
 import { Table } from 'app/shared/table/table';
 import { ToastService } from 'app/shared/toast/ToastService';
 import { DisconnectRepositoryManagerInProject } from 'app/store/project.action';
@@ -22,8 +21,6 @@ export class ProjectRepoManagerComponent extends Table<RepositoriesManager> {
     @Input() project: Project;
     @Input() reposmanagers: RepositoriesManager[];
 
-    @ViewChild('deleteRepoWarning')
-    private deleteRepoWarning: WarningModalComponent;
     @ViewChild('confirmDeletionModal')
     confirmDeletionModal: ConfirmModalComponent;
 
@@ -46,29 +43,24 @@ export class ProjectRepoManagerComponent extends Table<RepositoriesManager> {
         return this.reposmanagers;
     }
 
-    clickDeleteButton(repoName: string, skip?: boolean): void {
-        if (!skip && this.project.externalChange) {
-            this.deleteRepoWarning.show(repoName);
-        } else {
-            this.loadingDependencies = true;
-            this.repoNameToDelete = repoName;
-            this.confirmDeletionModal.show();
-            this.repoManagerService.getDependencies(this.project.key, repoName)
-                .pipe(finalize(() => {
-                    this.loadingDependencies = false;
-                    this._cd.markForCheck();
-                }))
-                .subscribe((apps) => {
-                    if (!apps) {
-                        this.confirmationMessage = this._translate.instant('repoman_delete_confirm_message');
-                        return;
-                    }
-                    this.confirmationMessage = this._translate.instant('repoman_delete_dependencies_message', {
-                        apps: apps.map((app) => app.name).join(', ')
-                    });
+    clickDeleteButton(repoName: string): void {
+        this.loadingDependencies = true;
+        this.repoNameToDelete = repoName;
+        this.confirmDeletionModal.show();
+        this.repoManagerService.getDependencies(this.project.key, repoName)
+            .pipe(finalize(() => {
+                this.loadingDependencies = false;
+                this._cd.markForCheck();
+            }))
+            .subscribe((apps) => {
+                if (!apps) {
+                    this.confirmationMessage = this._translate.instant('repoman_delete_confirm_message');
+                    return;
+                }
+                this.confirmationMessage = this._translate.instant('repoman_delete_dependencies_message', {
+                    apps: apps.map((app) => app.name).join(', ')
                 });
-        }
-
+            });
     }
 
     confirmDeletion(confirm: boolean) {
@@ -76,7 +68,10 @@ export class ProjectRepoManagerComponent extends Table<RepositoriesManager> {
             return;
         }
         this.deleteLoading = true;
-        this.store.dispatch(new DisconnectRepositoryManagerInProject({ projectKey: this.project.key, repoManager: this.repoNameToDelete }))
+        this.store.dispatch(new DisconnectRepositoryManagerInProject({
+            projectKey: this.project.key,
+            repoManager: this.repoNameToDelete
+        }))
             .pipe(finalize(() => {
                 this.deleteLoading = false;
                 this._cd.markForCheck();
diff --git a/ui/src/app/views/project/show/admin/repomanager/list/project.repomanager.list.html b/ui/src/app/views/project/show/admin/repomanager/list/project.repomanager.list.html
index 690705f7e..91dd1b9c5 100644
--- a/ui/src/app/views/project/show/admin/repomanager/list/project.repomanager.list.html
+++ b/ui/src/app/views/project/show/admin/repomanager/list/project.repomanager.list.html
@@ -37,7 +37,5 @@
         </tfoot>
     </table>
 </div>
-<app-warning-modal [title]="_translate.instant('warning_modal_title')" [msg]="_translate.instant('warning_modal_body')"
-    (event)="clickDeleteButton($event, true)" #deleteRepoWarning></app-warning-modal>
 <app-confirm-modal [loading]="loadingDependencies" [title]="'repoman_delete_confirm_title' | translate"
     [msg]="confirmationMessage" (event)="confirmDeletion($event)" #confirmDeletionModal></app-confirm-modal>
diff --git a/ui/src/app/views/project/show/permission/permission.component.ts b/ui/src/app/views/project/show/permission/permission.component.ts
index 23e81603f..2b7f3545a 100644
--- a/ui/src/app/views/project/show/permission/permission.component.ts
+++ b/ui/src/app/views/project/show/permission/permission.component.ts
@@ -4,7 +4,6 @@ import { Store } from '@ngxs/store';
 import { GroupPermission } from 'app/model/group.model';
 import { Project } from 'app/model/project.model';
 import { ConfirmModalComponent } from 'app/shared/modal/confirm/confirm.component';
-import { WarningModalComponent } from 'app/shared/modal/warning/warning.component';
 import { PermissionEvent } from 'app/shared/permission/permission.event.model';
 import { ToastService } from 'app/shared/toast/ToastService';
 import { AddGroupInProject, DeleteGroupInProject, UpdateGroupInProject } from 'app/store/project.action';
@@ -17,7 +16,6 @@ import cloneDeep from 'lodash-es/cloneDeep';
     changeDetection: ChangeDetectionStrategy.OnPush
 })
 export class ProjectPermissionsComponent implements OnChanges {
-    @ViewChild('permWarning') permWarningModal: WarningModalComponent;
     @ViewChild('confirmPropagationModal') confirmPropagationModal: ConfirmModalComponent;
 
     groups: Array<GroupPermission> = [];
@@ -28,6 +26,7 @@ export class ProjectPermissionsComponent implements OnChanges {
             this.groups = cloneDeep(data.groups);
         }
     };
+
     get project() {
         return this._project;
     }
@@ -42,7 +41,8 @@ export class ProjectPermissionsComponent implements OnChanges {
         private _toast: ToastService,
         private store: Store,
         private _cd: ChangeDetectorRef
-    ) { }
+    ) {
+    }
 
     ngOnChanges(): void {
         if (this.project && !!this.project.organization) {
@@ -52,32 +52,30 @@ export class ProjectPermissionsComponent implements OnChanges {
         }
     }
 
-    groupEvent(event: PermissionEvent, skip?: boolean): void {
+    groupEvent(event: PermissionEvent): void {
         this.currentPermEvent = event;
-        if (!skip && this.project.externalChange) {
-            this.permWarningModal.show(event);
-        } else {
-            switch (event.type) {
-                case 'add':
-                    this.confirmPropagationModal.show();
-                    break;
-                case 'update':
-                    this.store.dispatch(new UpdateGroupInProject({ projectKey: this.project.key, group: event.gp }))
-                        .pipe(finalize(() => {
-                            this.groups = cloneDeep(this.project.groups);
-                            this._cd.markForCheck();
-                        }))
-                        .subscribe(() => this._toast.success('', this._translate.instant('permission_updated')));
-                    break;
-                case 'delete':
-                    this.store.dispatch(new DeleteGroupInProject({ projectKey: this.project.key, group: event.gp }))
-                        .pipe(finalize(() => {
-                            this.groups = cloneDeep(this.project.groups);
-                            this._cd.markForCheck();
-                        }))
-                        .subscribe(() => this._toast.success('', this._translate.instant('permission_deleted')));
-                    break;
-            }
+
+        switch (event.type) {
+            case 'add':
+                this.confirmPropagationModal.show();
+                break;
+            case 'update':
+                this.store.dispatch(new UpdateGroupInProject({projectKey: this.project.key, group: event.gp}))
+                    .pipe(finalize(() => {
+                        this.groups = cloneDeep(this.project.groups);
+                        this._cd.markForCheck();
+                    }))
+                    .subscribe(() => this._toast.success('', this._translate.instant('permission_updated')));
+                break;
+            case 'delete':
+                this.store.dispatch(new DeleteGroupInProject({projectKey: this.project.key, group: event.gp}))
+                    .pipe(finalize(() => {
+                        this.groups = cloneDeep(this.project.groups);
+                        this._cd.markForCheck();
+                    }))
+                    .subscribe(() => this._toast.success('', this._translate.instant('permission_deleted')));
+                break;
+
         }
     }
 
diff --git a/ui/src/app/views/project/show/permission/permission.html b/ui/src/app/views/project/show/permission/permission.html
index 0d8edfabd..c4ae4424a 100644
--- a/ui/src/app/views/project/show/permission/permission.html
+++ b/ui/src/app/views/project/show/permission/permission.html
@@ -15,8 +15,6 @@
     </app-permission-form>
 </ng-container>
 <div *ngIf="loading" class="ui text active loader">{{'common_loading_environments' | translate}}...</div>
-<app-warning-modal [title]="_translate.instant('warning_modal_title')" [msg]="_translate.instant('warning_modal_body')"
-    (event)="groupEvent($event, true)" #permWarning></app-warning-modal>
 <app-confirm-modal [title]="'project_permission_propagate_title' | translate"
     [msg]="'project_permission_propagate_question' | translate" (event)="confirmPermPropagation($event)"
     #confirmPropagationModal>
diff --git a/ui/src/app/views/project/show/variable/variable.list.component.ts b/ui/src/app/views/project/show/variable/variable.list.component.ts
index c1ecb56dc..61c4a4d7a 100644
--- a/ui/src/app/views/project/show/variable/variable.list.component.ts
+++ b/ui/src/app/views/project/show/variable/variable.list.component.ts
@@ -1,8 +1,7 @@
-import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit, ViewChild } from '@angular/core';
+import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit } from '@angular/core';
 import { TranslateService } from '@ngx-translate/core';
 import { Store } from '@ngxs/store';
 import { Project } from 'app/model/project.model';
-import { WarningModalComponent } from 'app/shared/modal/warning/warning.component';
 import { ToastService } from 'app/shared/toast/ToastService';
 import { VariableEvent } from 'app/shared/variable/variable.event.model';
 import {
@@ -22,8 +21,6 @@ import { finalize } from 'rxjs/operators';
 export class ProjectVariablesComponent implements OnInit {
 
     @Input() project: Project;
-    @ViewChild('varWarning')
-    varWarningModal: WarningModalComponent;
 
     loading = true;
     varFormLoading = false;
@@ -38,7 +35,7 @@ export class ProjectVariablesComponent implements OnInit {
     }
 
     ngOnInit() {
-        this.store.dispatch(new FetchVariablesInProject({ projectKey: this.project.key }))
+        this.store.dispatch(new FetchVariablesInProject({projectKey: this.project.key}))
             .pipe(finalize(() => {
                 this.loading = false;
                 this._cd.markForCheck();
@@ -46,30 +43,29 @@ export class ProjectVariablesComponent implements OnInit {
             .subscribe();
     }
 
-    variableEvent(event: VariableEvent, skip?: boolean): void {
-        if (!skip && this.project.externalChange) {
-            this.varWarningModal.show(event);
-        } else {
-            event.variable.value = String(event.variable.value);
-            switch (event.type) {
-                case 'add':
-                    this.varFormLoading = true;
-                    this.store.dispatch(new AddVariableInProject(event.variable))
-                        .pipe(finalize(() => {
-                            this.varFormLoading = false;
-                            this._cd.markForCheck();
-                        }))
-                        .subscribe(() => this._toast.success('', this._translate.instant('variable_added')));
-                    break;
-                case 'update':
-                    this.store.dispatch(new UpdateVariableInProject({ variableName: event.variable.name, changes: event.variable }))
-                        .subscribe(() => this._toast.success('', this._translate.instant('variable_updated')));
-                    break;
-                case 'delete':
-                    this.store.dispatch(new DeleteVariableInProject(event.variable))
-                        .subscribe(() => this._toast.success('', this._translate.instant('variable_deleted')));
-                    break;
-            }
+    variableEvent(event: VariableEvent): void {
+        event.variable.value = String(event.variable.value);
+        switch (event.type) {
+            case 'add':
+                this.varFormLoading = true;
+                this.store.dispatch(new AddVariableInProject(event.variable))
+                    .pipe(finalize(() => {
+                        this.varFormLoading = false;
+                        this._cd.markForCheck();
+                    }))
+                    .subscribe(() => this._toast.success('', this._translate.instant('variable_added')));
+                break;
+            case 'update':
+                this.store.dispatch(new UpdateVariableInProject({
+                    variableName: event.variable.name,
+                    changes: event.variable
+                }))
+                    .subscribe(() => this._toast.success('', this._translate.instant('variable_updated')));
+                break;
+            case 'delete':
+                this.store.dispatch(new DeleteVariableInProject(event.variable))
+                    .subscribe(() => this._toast.success('', this._translate.instant('variable_deleted')));
+                break;
         }
     }
 }
diff --git a/ui/src/app/views/project/show/variable/variable.list.html b/ui/src/app/views/project/show/variable/variable.list.html
index 43f0bf889..8821999e8 100644
--- a/ui/src/app/views/project/show/variable/variable.list.html
+++ b/ui/src/app/views/project/show/variable/variable.list.html
@@ -11,6 +11,5 @@
       </h3>
       <app-variable [project]="project" [variables]="project.variables" [mode]="project.permissions.writable? 'edit': 'ro'" (event)="variableEvent($event)" auditContext="project"></app-variable>
   </div>
-  <app-warning-modal [title]="'warning_modal_title' | translate" [msg]="'warning_modal_body' | translate" (event)="variableEvent($event, true)" #varWarning></app-warning-modal>
 </div>
 <div *ngIf="loading" class="ui text active loader">Loading variables...</div>
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 faf7995a0..237070426 100644
--- a/ui/src/app/views/settings/action/form/action.form.html
+++ b/ui/src/app/views/settings/action/form/action.form.html
@@ -124,7 +124,7 @@
                                 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>
+                                nz-button><i nz-icon nzType="delete" nzTheme="outline"></i>Delete</button>
             </ng-container>
             <ng-container *ngIf="!action.id">
                 <div nz-col nzSpan="24">
diff --git a/ui/src/app/views/workflow/show/admin/workflow.admin.component.html b/ui/src/app/views/workflow/show/admin/workflow.admin.component.html
index 7c20df738..6f5f288bf 100644
--- a/ui/src/app/views/workflow/show/admin/workflow.admin.component.html
+++ b/ui/src/app/views/workflow/show/admin/workflow.admin.component.html
@@ -212,10 +212,6 @@
                         <span *ngIf="editMode">{{'btn_apply' | translate}}</span>
                     </button>
                 </div>
-                <app-warning-modal [title]="_translate.instant('warning_modal_title')"
-                    [msg]="_translate.instant('warning_modal_body')" (event)="onSubmitWorkflowUpdate(true)"
-                    #updateWarning>
-                </app-warning-modal>
             </form>
         </app-zone-content>
     </app-zone>
diff --git a/ui/src/app/views/workflow/show/admin/workflow.admin.component.ts b/ui/src/app/views/workflow/show/admin/workflow.admin.component.ts
index f244ff89b..b0865d578 100644
--- a/ui/src/app/views/workflow/show/admin/workflow.admin.component.ts
+++ b/ui/src/app/views/workflow/show/admin/workflow.admin.component.ts
@@ -20,13 +20,12 @@ import { ThemeStore } from 'app/service/theme/theme.store';
 import { WorkflowRunService } from 'app/service/workflow/run/workflow.run.service';
 import { WorkflowService } from 'app/service/workflow/workflow.service';
 import { AutoUnsubscribe } from 'app/shared/decorator/autoUnsubscribe';
-import { WarningModalComponent } from 'app/shared/modal/warning/warning.component';
 import { Column, ColumnType } from 'app/shared/table/data-table.component';
 import { ToastService } from 'app/shared/toast/ToastService';
 import { FeatureState } from 'app/store/feature.state';
 import {
-    CleanRetentionDryRun, DeleteIntegrationWorkflow,
-    DeleteWorkflow,
+    CleanRetentionDryRun,
+    DeleteIntegrationWorkflow,
     DeleteWorkflowIcon,
     UpdateIntegrationsWorkflow,
     UpdateWorkflow,
@@ -63,6 +62,7 @@ export class WorkflowAdminComponent implements OnInit, OnDestroy {
             this.filteredIntegrations = cloneDeep(project.integrations.filter(p => p.model.artifact_manager));
         }
     }
+
     get project(): Project {
         return this._project;
     }
@@ -75,6 +75,7 @@ export class WorkflowAdminComponent implements OnInit, OnDestroy {
             this.nbEventIntegrations = this._workflow?.integrations?.filter(i => i.project_integration.model.event).length;
         }
     }
+
     get workflow() {
         return this._workflow;
     }
@@ -104,8 +105,6 @@ export class WorkflowAdminComponent implements OnInit, OnDestroy {
 
     apiConfig: APIConfig;
 
-    @ViewChild('updateWarning')
-    private warningUpdateModal: WarningModalComponent;
     @ViewChild('codemirrorRetentionPolicy') codemirror: CodemirrorComponent;
     themeSubscription: Subscription;
 
@@ -157,7 +156,7 @@ export class WorkflowAdminComponent implements OnInit, OnDestroy {
             }
         });
 
-        this.dragulaSubscription = this._dragularService.drop('bag-tag').subscribe(({ }) => {
+        this.dragulaSubscription = this._dragularService.drop('bag-tag').subscribe(({}) => {
             setTimeout(() => {
                 this.updateTagMetadata();
             });
@@ -212,7 +211,7 @@ export class WorkflowAdminComponent implements OnInit, OnDestroy {
         }
 
         if (!this.project.permissions.writable) {
-            this._router.navigate(['/project', this.project.key], { queryParams: { tab: 'applications' } });
+            this._router.navigate(['/project', this.project.key], {queryParams: {tab: 'applications'}});
         }
         this.oldName = this.workflow.name;
 
@@ -226,17 +225,17 @@ export class WorkflowAdminComponent implements OnInit, OnDestroy {
             });
         this._workflowRunService.getRunNumber(this.project.key, this.workflow)
             .pipe(first(), finalize(() => this._cd.markForCheck())).subscribe(n => {
-                this.originalRunNumber = n.num;
-                this.runnumber = n.num;
-            });
+            this.originalRunNumber = n.num;
+            this.runnumber = n.num;
+        });
 
         this.initDryRunSubscription();
 
         let featRetentionRunsPolicyResult = this.store.selectSnapshot(FeatureState.featureProject(FeatureNames.WorkflowRetentionPolicy,
-            JSON.stringify({ project_key: this.project.key })));
+            JSON.stringify({project_key: this.project.key})));
         this.retentionRunsPolicyEnabled = featRetentionRunsPolicyResult?.enabled;
         let featMaxRunsResult = this.store.selectSnapshot(FeatureState.featureProject(FeatureNames.WorkflowRetentionMaxRuns,
-            JSON.stringify({ project_key: this.project.key })));
+            JSON.stringify({project_key: this.project.key})));
         this.maxRunsEnabled = featMaxRunsResult?.enabled;
 
         this._cd.markForCheck();
@@ -391,45 +390,41 @@ export class WorkflowAdminComponent implements OnInit, OnDestroy {
             });
     }
 
-    onSubmitWorkflowUpdate(skip?: boolean) {
-        if (!skip && this.workflow.externalChange) {
-            this.warningUpdateModal.show();
-        } else {
-            this.loading = true;
-            let actions = [];
-            if (this.runnumber !== this.originalRunNumber) {
-                actions.push(this._workflowRunService.updateRunNumber(this.project.key, this.workflow, this.runnumber));
-            }
-            if (this.selectedTagsPurge) {
-                this._workflow.purge_tags = this.selectedTagsPurge;
-            }
-
-            if (!this._workflow.purge_tags || this._workflow.purge_tags.length === 0) {
-                delete this._workflow.purge_tags;
-            }
+    onSubmitWorkflowUpdate() {
+        this.loading = true;
+        let actions = [];
+        if (this.runnumber !== this.originalRunNumber) {
+            actions.push(this._workflowRunService.updateRunNumber(this.project.key, this.workflow, this.runnumber));
+        }
+        if (this.selectedTagsPurge) {
+            this._workflow.purge_tags = this.selectedTagsPurge;
+        }
 
-            actions.push(this.store.dispatch(new UpdateWorkflow({
-                projectKey: this.project.key,
-                workflowName: this.oldName,
-                changes: this.workflow
-            })));
-
-            forkJoin(...actions)
-                .pipe(finalize(() => {
-                    this.loading = false;
-                    this._cd.markForCheck();
-                }))
-                .subscribe(() => {
-                    if (this.editMode) {
-                        this._toast.info('', this._translate.instant('workflow_ascode_updated'));
-                    } else {
-                        this._toast.success('', this._translate.instant('workflow_updated'));
-                    }
-                    this._router.navigate([
-                        '/project', this.project.key, 'workflow', this.workflow.name
-                    ], { queryParams: { tab: 'advanced' } });
-                });
+        if (!this._workflow.purge_tags || this._workflow.purge_tags.length === 0) {
+            delete this._workflow.purge_tags;
         }
+
+        actions.push(this.store.dispatch(new UpdateWorkflow({
+            projectKey: this.project.key,
+            workflowName: this.oldName,
+            changes: this.workflow
+        })));
+
+        forkJoin(...actions)
+            .pipe(finalize(() => {
+                this.loading = false;
+                this._cd.markForCheck();
+            }))
+            .subscribe(() => {
+                if (this.editMode) {
+                    this._toast.info('', this._translate.instant('workflow_ascode_updated'));
+                } else {
+                    this._toast.success('', this._translate.instant('workflow_updated'));
+                }
+                this._router.navigate([
+                    '/project', this.project.key, 'workflow', this.workflow.name
+                ], {queryParams: {tab: 'advanced'}});
+            });
     }
 
     deleteWorkflow(): void {
diff --git a/ui/src/app/views/workflow/show/workflow.component.ts b/ui/src/app/views/workflow/show/workflow.component.ts
index 1b5cc539b..e1c9d6084 100644
--- a/ui/src/app/views/workflow/show/workflow.component.ts
+++ b/ui/src/app/views/workflow/show/workflow.component.ts
@@ -18,7 +18,6 @@ import { WorkflowCoreService } from 'app/service/workflow/workflow.core.service'
 import { WorkflowStore } from 'app/service/workflow/workflow.store';
 import { AsCodeSaveModalComponent } from 'app/shared/ascode/save-modal/ascode.save-modal.component';
 import { AutoUnsubscribe } from 'app/shared/decorator/autoUnsubscribe';
-import { WarningModalComponent } from 'app/shared/modal/warning/warning.component';
 import { PermissionEvent } from 'app/shared/permission/permission.event.model';
 import { ToastService } from 'app/shared/toast/ToastService';
 import { WorkflowNodeRunParamComponent } from 'app/shared/workflow/node/run/node.run.param.component';
@@ -27,7 +26,7 @@ import { CancelWorkflowEditMode, SelectWorkflowNode } from 'app/store/workflow.a
 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 { finalize, first } from 'rxjs/operators';
 import { Tab } from 'app/shared/tabs/tabs.component';
 
 @Component({
@@ -56,8 +55,6 @@ export class WorkflowShowComponent implements OnInit, OnDestroy, AfterViewInit {
 
     @ViewChild('workflowStartParam')
     runWithParamComponent: WorkflowNodeRunParamComponent;
-    @ViewChild('permWarning')
-    permWarningModal: WarningModalComponent;
     @ViewChild('updateAsCode')
     updateAsCodeModal: AsCodeSaveModalComponent;
 
@@ -83,7 +80,8 @@ export class WorkflowShowComponent implements OnInit, OnDestroy, AfterViewInit {
         private _toast: ToastService,
         private _workflowCoreService: WorkflowCoreService,
         private _cd: ChangeDetectorRef
-    ) { }
+    ) {
+    }
 
     ngAfterViewInit(): void {
         if (this.detailedWorkflow) {
@@ -91,7 +89,8 @@ export class WorkflowShowComponent implements OnInit, OnDestroy, AfterViewInit {
         }
     }
 
-    ngOnDestroy(): void { } // Should be set to use @AutoUnsubscribe with AOT
+    ngOnDestroy(): void {
+    } // Should be set to use @AutoUnsubscribe with AOT
 
     ngOnInit(): void {
         // Update data if route change
@@ -140,7 +139,7 @@ export class WorkflowShowComponent implements OnInit, OnDestroy, AfterViewInit {
 
 
         this.paramsSubs = this.activatedRoute.params.subscribe(() => {
-            this._workflowCoreService.toggleAsCodeEditor({ open: false, save: false });
+            this._workflowCoreService.toggleAsCodeEditor({open: false, save: false});
             this._workflowCoreService.setWorkflowPreview(null);
         });
 
@@ -162,13 +161,13 @@ export class WorkflowShowComponent implements OnInit, OnDestroy, AfterViewInit {
         this.workflowPreviewSubscription = this._workflowCoreService.getWorkflowPreview()
             .subscribe((wfPreview) => {
                 if (wfPreview != null) {
-                    this._workflowCoreService.toggleAsCodeEditor({ open: false, save: false });
+                    this._workflowCoreService.toggleAsCodeEditor({open: false, save: false});
                 }
             });
     }
 
     initTabs() {
-        let graphTab = <Tab> {
+        let graphTab = <Tab>{
             title: 'Workflows',
             key: 'workflows',
             icon: 'share alternate',
@@ -194,7 +193,7 @@ export class WorkflowShowComponent implements OnInit, OnDestroy, AfterViewInit {
         this.tabs = new Array<Tab>();
         this.tabs.push(graphTab, notificationTab, permissionTab)
 
-        if (!this.detailedWorkflow.from_repository)   {
+        if (!this.detailedWorkflow.from_repository) {
             this.tabs.push(<Tab>{
                 title: 'Audit',
                 icon: 'history',
@@ -207,7 +206,7 @@ export class WorkflowShowComponent implements OnInit, OnDestroy, AfterViewInit {
             key: 'usage'
         });
         if (this.detailedWorkflow.permissions.writable) {
-            this.tabs.push( <Tab>{
+            this.tabs.push(<Tab>{
                 title: 'Advanced',
                 icon: 'graduation',
                 key: 'advanced'
@@ -216,7 +215,7 @@ export class WorkflowShowComponent implements OnInit, OnDestroy, AfterViewInit {
     }
 
     savePreview() {
-        this._workflowCoreService.toggleAsCodeEditor({ open: false, save: true });
+        this._workflowCoreService.toggleAsCodeEditor({open: false, save: true});
     }
 
     changeDirection() {
@@ -228,49 +227,45 @@ export class WorkflowShowComponent implements OnInit, OnDestroy, AfterViewInit {
     }
 
     showAsCodeEditor() {
-        this._workflowCoreService.toggleAsCodeEditor({ open: true, save: false });
+        this._workflowCoreService.toggleAsCodeEditor({open: true, save: false});
     }
 
-    groupManagement(event: PermissionEvent, skip?: boolean): void {
-        if (!skip && this.detailedWorkflow.externalChange) {
-            this.permWarningModal.show(event);
-        } else {
-            switch (event.type) {
-                case 'add':
-                    this.permFormLoading = true;
-                    this._store.dispatch(new actionsWorkflow.AddGroupInWorkflow({
-                        projectKey: this.project.key,
-                        workflowName: this.detailedWorkflow.name,
-                        group: event.gp
-                    })).pipe(finalize(() => {
-                        this.permFormLoading = false;
-                        this._cd.markForCheck();
-                    })).subscribe(() => this._toast.success('', this._translate.instant('permission_added')));
-
-                    break;
-                case 'update':
-                    this._store.dispatch(new actionsWorkflow.UpdateGroupInWorkflow({
-                        projectKey: this.project.key,
-                        workflowName: this.detailedWorkflow.name,
-                        group: event.gp
-                    })).pipe(finalize( () => {
-                        this.permFormLoading = false;
-                        this.groups = cloneDeep(this.detailedWorkflow.groups);
-                        this._cd.markForCheck();
-                    })).subscribe(() => this._toast.success('', this._translate.instant('permission_updated')));
-                    break;
-                case 'delete':
-                    this._store.dispatch(new actionsWorkflow.DeleteGroupInWorkflow({
-                        projectKey: this.project.key,
-                        workflowName: this.detailedWorkflow.name,
-                        group: event.gp
-                    })).pipe(finalize( () => {
-                        this.permFormLoading = false;
-                        this.groups = cloneDeep(this.detailedWorkflow.groups);
-                        this._cd.markForCheck();
-                    })).subscribe(() => this._toast.success('', this._translate.instant('permission_deleted')));
-                    break;
-            }
+    groupManagement(event: PermissionEvent): void {
+        switch (event.type) {
+            case 'add':
+                this.permFormLoading = true;
+                this._store.dispatch(new actionsWorkflow.AddGroupInWorkflow({
+                    projectKey: this.project.key,
+                    workflowName: this.detailedWorkflow.name,
+                    group: event.gp
+                })).pipe(finalize(() => {
+                    this.permFormLoading = false;
+                    this._cd.markForCheck();
+                })).subscribe(() => this._toast.success('', this._translate.instant('permission_added')));
+
+                break;
+            case 'update':
+                this._store.dispatch(new actionsWorkflow.UpdateGroupInWorkflow({
+                    projectKey: this.project.key,
+                    workflowName: this.detailedWorkflow.name,
+                    group: event.gp
+                })).pipe(finalize(() => {
+                    this.permFormLoading = false;
+                    this.groups = cloneDeep(this.detailedWorkflow.groups);
+                    this._cd.markForCheck();
+                })).subscribe(() => this._toast.success('', this._translate.instant('permission_updated')));
+                break;
+            case 'delete':
+                this._store.dispatch(new actionsWorkflow.DeleteGroupInWorkflow({
+                    projectKey: this.project.key,
+                    workflowName: this.detailedWorkflow.name,
+                    group: event.gp
+                })).pipe(finalize(() => {
+                    this.permFormLoading = false;
+                    this.groups = cloneDeep(this.detailedWorkflow.groups);
+                    this._cd.markForCheck();
+                })).subscribe(() => this._toast.success('', this._translate.instant('permission_deleted')));
+                break;
         }
     }
 
diff --git a/ui/src/app/views/workflow/show/workflow.html b/ui/src/app/views/workflow/show/workflow.html
index 43ab252d6..d52d8392b 100644
--- a/ui/src/app/views/workflow/show/workflow.html
+++ b/ui/src/app/views/workflow/show/workflow.html
@@ -93,9 +93,6 @@
                         (createGroupPermissionEvent)="groupManagement($event)" [loading]="permFormLoading">
                     </app-workflow-permission-form>
                 </ng-container>
-                <app-warning-modal [title]="_translate.instant('warning_modal_title')"
-                    [msg]="_translate.instant('warning_modal_body')" (event)="groupManagement($event, true)"
-                    #permWarning></app-warning-modal>
             </div>
             <div *ngSwitchCase="'advanced'" class="padded">
                 <app-workflow-admin [project]="project" [workflow]="detailedWorkflow" [editMode]="editMode">
diff --git a/ui/src/assets/i18n/en.json b/ui/src/assets/i18n/en.json
index a3ea4ea33..f080fdf27 100644
--- a/ui/src/assets/i18n/en.json
+++ b/ui/src/assets/i18n/en.json
@@ -78,16 +78,12 @@
   "application_name_error": "Invalid application name. Allowed pattern is: a-zA-Z0-9._-",
   "application_no_usage": "The application is not used",
   "application_rename_title": "Rename the application",
-  "application_repo_missing_conf": "Missing configuration",
-  "application_repo_missing_conf_details": "Almost done ! You have to configure at least the connection protocol to your repository just below and save your changes",
-  "application_repo_choose": "Choose a repository",
   "application_repo_attach_ok": "The repository is linked to the application",
   "application_repo_detach_ok": "The repository is detached from the application",
   "application_repoman_cant": "First, you must connect a repository manager to your project: ",
   "application_repoman_cant_link": "here",
   "application_repo_no": "The application is not linked to a Git repository",
   "application_repo_title": "Link application to a repository: ",
-  "application_repo_strat_title": "Configuration",
   "application_deployment_title": "Link application to a deployment integration: ",
   "application_new_title": "New application",
   "application_update_name_error": "Application name is mandatory and must respect the alphanumeric pattern + the following characters: .-_ ([a-zA-Z0-9._-]*)",
@@ -99,18 +95,13 @@
   "ascode_modal_title": "Save as code",
   "ascode_modal_label_branch": "Select or create a branch",
   "ascode_modal_label_message": "Commit message",
-  "audit_change": "Change",
-  "audit_no": "No audit",
   "audit_time_author": "Change date",
   "audit_title": "Change history",
   "audit_action": "Action",
-  "audit_loading": "Loading audits",
   "audit_username": "User",
   "audit_modification_type": "Modification type",
-  "audit_title_variable": "Variable audit",
   "btn_apply": "Apply",
   "btn_cancel": "Cancel",
-  "btn_continue": "Continue",
   "btn_close": "Close",
   "btn_create": "Create",
   "btn_create_workflow": "Create workflow",
@@ -126,7 +117,6 @@
   "btn_resync_ascode_event": "Resynchronize repository modifications",
   "btn_run": "Run pipeline",
   "btn_run_workflow": "Run workflow",
-  "btn_validate": "Validate",
   "btn_save": "Save",
   "btn_save_application": "Save application",
   "btn_save_pipeline": "Save pipeline",
@@ -368,7 +358,6 @@
   "graph_coverage_y": "Percentage",
   "group_added": "Group added",
   "group_deleted": "Group deleted",
-  "group_create_title": "Create a group",
   "group_name": "Group name",
   "group_list_title": "Groups",
   "group_load_group": "Loading group...",
@@ -484,7 +473,6 @@
   "pipeline_run_test_failed_title": " test failed",
   "pipeline_run_test_skipped_title_s": " tests skipped",
   "pipeline_run_test_skipped_title": " test skipped",
-  "pipeline_stage_edit": "Edit stage",
   "pipeline_stage_moved": "Stage moved",
   "pipeline_stage_no": "The pipeline has no stage",
   "pipeline_wizard_description": "Create or select your first pipeline for your workflow",
@@ -501,9 +489,7 @@
   "project_delete_description": "Once you delete a project, there is no going back. Please be certain.",
   "project_env_list_title": "Environments List: ",
   "project_key": "Project unique key",
-  "project_key_error": "Project unique key is mandatory and must be in upper case",
   "project_name": "Project name",
-  "project_name_error": "project name is mandatory",
   "project_no": "There is no project",
   "project_no_application": "There is no application in this project",
   "project_no_pipeline": "There is no pipeline in this project",
@@ -538,18 +524,15 @@
   "integration_event": "event",
   "integration_deployment": "deployment",
   "integration_storage": "storage",
-  "repo_name": "Repository",
   "repoman_delete_msg_ok": "Repository manager is detached from the project",
   "repoman_delete_confirm_message": "Are you sure to delete this repository manager from your project?",
   "repoman_delete_dependencies_message": "Are you sure to delete this repository manager from your project? It's linked to these applications: {{apps}}",
   "repoman_delete_confirm_title": "Repository manager deletion",
-  "repoman_name": "Repository manager",
   "repoman_verif_msg_ok": "The repository manager is now connected to CDS.",
   "repoman_modal_verif_code_placeholder": "Verification code",
   "repoman_modal_verif_text": "Click on the following link to finalize the link between CDS and the repository manager: ",
   "repoman_modal_verif_title": "Repository manager authorization",
   "requirement_type": "Type",
-  "requirement_value": "Value",
   "requirement_help_binary_0": "Requirement type 'binary': CDS will choose a worker with this binary in his path.",
   "requirement_help_hostname_0": "Requirement type 'hostname': This Job will be take by a worker hosted on this host",
   "requirement_help_os-architecture_0": "Requirement type 'os-architecture': CDS will choose a worker with this OS and architecture",
@@ -567,11 +550,6 @@
   "requirement_help_service_2": "Tutorial - Service Link Requirement Nginx Tutorial",
   "requirement_help_service_3": "Service Link Requirement PostgreSQL",
   "requirement_help_service_4": "You can force memory on service, example: 'CDS_SERVICE_MEMORY=4096'",
-  "requirement_hostname_error": "Hostname is mandatory and must respect the alphanumeric pattern ([a-zA-Z0-9]*)",
-  "requirement_hostname_service": "Hostname :",
-  "requirement_os-architecture_error": "This field is mandatory",
-  "requirement_value_service": "Docker Image :",
-  "requirement_settings_worker_model": "See settings of Worker Model",
   "requirement_options": "Options:",
   "requirement_documentation": "Documentation about requirements",
   "requirement_error_model": "You can't have multiple requirements of type model",
@@ -579,10 +557,7 @@
   "requirement_error_region": "You can't have multiple requirements of type region",
   "requirement_placeholder_name_os-architecture": "os/arch",
   "requirement_placeholder_name_service": "hostnameOfService",
-  "requirement_placeholder_value_model": "Choose a worker model",
-  "requirement_placeholder_opts_model": "If it's a worker model Docker, you can add some option. See Documentation",
   "requirement_placeholder_value_hostname": "yourhostname",
-  "requirement_placeholder_opts_service": "POSTGRES_USER=myuser\nPOSTGRES_PASSWORD=mypassword",
   "settings_tips": "Tips",
   "services": "services",
   "services_list": "CDS Services",
@@ -667,8 +642,6 @@
   "vulnerability_origin": "Origin",
   "vulnerability_showmore": "Show more",
   "vulnerability_updated": "Vulnerability updated",
-  "warning_modal_title": "Warning - concurrent access",
-  "warning_modal_body": "Another use has just updated this resource. You may overwrite his update.",
   "warning_project": "{{username}} has just updated the project.",
   "warning_application": "{{username}} has just updated the application.",
   "warning_pipeline": "{{username}} has just updated the pipeline.",


Test patch:
diff --git a/engine/api/workflow/workflow_run_results_test.go b/engine/api/workflow/workflow_run_results_test.go
index ad2de2f69..3a7b87e34 100644
--- a/engine/api/workflow/workflow_run_results_test.go
+++ b/engine/api/workflow/workflow_run_results_test.go
@@ -125,7 +125,9 @@ func TestCanUploadArtifactAlreadyExist(t *testing.T) {
 		Type:              sdk.WorkflowRunResultTypeArtifact,
 	}
 	artiData := sdk.WorkflowRunResultArtifact{
-		Name:       "myartifact",
+		WorkflowRunResultArtifactCommon: sdk.WorkflowRunResultArtifactCommon{
+			Name: "myartifact",
+		},
 		CDNRefHash: "123",
 		MD5:        "123",
 		Size:       1,
@@ -171,7 +173,9 @@ func TestCanUploadArtifactAlreadyExistInMoreRecentSubNum(t *testing.T) {
 		Type:              sdk.WorkflowRunResultTypeArtifact,
 	}
 	artiData := sdk.WorkflowRunResultArtifact{
-		Name:       "myartifact",
+		WorkflowRunResultArtifactCommon: sdk.WorkflowRunResultArtifactCommon{
+			Name: "myartifact",
+		},
 		CDNRefHash: "123",
 		MD5:        "123",
 		Size:       1,
@@ -230,7 +234,9 @@ func TestCanUploadArtifactAlreadyExistInAPreviousSubNum(t *testing.T) {
 		Type:              sdk.WorkflowRunResultTypeArtifact,
 	}
 	artiData := sdk.WorkflowRunResultArtifact{
-		Name:       "myartifact",
+		WorkflowRunResultArtifactCommon: sdk.WorkflowRunResultArtifactCommon{
+			Name: "myartifact",
+		},
 		CDNRefHash: "123",
 		MD5:        "123",
 		Size:       1,
@@ -275,7 +281,9 @@ func TestCanUploadStaticFile(t *testing.T) {
 		Type:              sdk.WorkflowRunResultTypeStaticFile,
 	}
 	artiData := sdk.WorkflowRunResultStaticFile{
-		Name:      "my title static file",
+		WorkflowRunResultArtifactCommon: sdk.WorkflowRunResultArtifactCommon{
+			Name: "my title static file",
+		},
 		RemoteURL: "https://foo/bar",
 	}
 	bts, err := json.Marshal(artiData)
@@ -309,7 +317,9 @@ func TestCanUploadStaticFileInvalid(t *testing.T) {
 		Type:              sdk.WorkflowRunResultTypeStaticFile,
 	}
 	artiData := sdk.WorkflowRunResultStaticFile{
-		Name:      "my title static file",
+		WorkflowRunResultArtifactCommon: sdk.WorkflowRunResultArtifactCommon{
+			Name: "my title static file",
+		},
 		RemoteURL: "",
 	}
 	bts, err := json.Marshal(artiData)
diff --git a/engine/api/workflow_purge_test.go b/engine/api/workflow_purge_test.go
index eb7d9cc2c..86214be1a 100644
--- a/engine/api/workflow_purge_test.go
+++ b/engine/api/workflow_purge_test.go
@@ -234,8 +234,10 @@ func Test_Purge_DeleteArtifactsFromRepositoryManager(t *testing.T) {
 	require.NoError(t, err)
 
 	data := sdk.WorkflowRunResultArtifactManager{
+		WorkflowRunResultArtifactCommon: sdk.WorkflowRunResultArtifactCommon{
+			Name: "foo",
+		},
 		Path:     "path/to/foo",
-		Name:     "foo",
 		RepoName: "repository",
 	}
 	rawData, _ := json.Marshal(data)
diff --git a/engine/api/workflow_queue_test.go b/engine/api/workflow_queue_test.go
index 6023de266..fa96dec08 100644
--- a/engine/api/workflow_queue_test.go
+++ b/engine/api/workflow_queue_test.go
@@ -1556,10 +1556,12 @@ func Test_workflowRunResultsAdd(t *testing.T) {
 	}
 
 	artiData := sdk.WorkflowRunResultArtifact{
+		WorkflowRunResultArtifactCommon: sdk.WorkflowRunResultArtifactCommon{
+			Name: "myartifact",
+		},
 		Size:       1,
 		MD5:        "AA",
 		CDNRefHash: "AA",
-		Name:       "myartifact",
 		Perm:       0777,
 	}
 	bts, err := json.Marshal(artiData)
diff --git a/engine/api/workflow_run_result_test.go b/engine/api/workflow_run_result_test.go
index ec2993f8b..c93bf39dd 100644
--- a/engine/api/workflow_run_result_test.go
+++ b/engine/api/workflow_run_result_test.go
@@ -38,7 +38,9 @@ func Test_getWorkflowRunAndNodeRunResults(t *testing.T) {
 	require.NoError(t, err)
 
 	artiData := sdk.WorkflowRunResultArtifact{
-		Name:       "myarti",
+		WorkflowRunResultArtifactCommon: sdk.WorkflowRunResultArtifactCommon{
+			Name: "myarti",
+		},
 		CDNRefHash: "123",
 		MD5:        "123",
 		Size:       1,
diff --git a/engine/worker/internal/action/builtin_key_install_test.go b/engine/worker/internal/action/builtin_key_install_test.go
deleted file mode 100644
index f628aa726..000000000
--- a/engine/worker/internal/action/builtin_key_install_test.go
+++ /dev/null
@@ -1,145 +0,0 @@
-package action_test
-
-import (
-	"context"
-	"fmt"
-	"os"
-	"path/filepath"
-	"testing"
-	"time"
-
-	"github.com/sirupsen/logrus"
-	"github.com/spf13/afero"
-	"github.com/stretchr/testify/assert"
-	"github.com/stretchr/testify/require"
-
-	"github.com/ovh/cds/engine/test"
-	"github.com/ovh/cds/engine/worker/internal"
-	"github.com/ovh/cds/engine/worker/internal/action"
-	"github.com/ovh/cds/engine/worker/pkg/workerruntime"
-	"github.com/ovh/cds/sdk"
-)
-
-func TestRunInstallKeyAction_Relative(t *testing.T) {
-	// Init a real worker, not the mocking one
-	var w = new(internal.CurrentWorker)
-	fs := afero.NewOsFs()
-	basedir := "test-" + test.GetTestName(t) + "-" + sdk.RandomString(10) + "-" + fmt.Sprintf("%d", time.Now().Unix())
-	require.NoError(t, fs.MkdirAll(basedir, os.FileMode(0755)))
-	cfg := &workerruntime.WorkerConfig{
-		Name:                "test-worker",
-		HatcheryName:        "test-hatchery",
-		APIEndpoint:         "http://cds-api.local",
-		APIToken:            "xxx-my-token",
-		APIEndpointInsecure: true,
-		Basedir:             basedir,
-	}
-	if err := w.Init(cfg, afero.NewBasePathFs(fs, basedir)); err != nil {
-		t.Fatalf("worker init failed: %v", err)
-	}
-	require.NoError(t, w.BaseDir().Mkdir("keys", os.FileMode(0700)))
-	keyDir, err := w.BaseDir().Open("keys")
-	require.NoError(t, err)
-	w.SetContext(context.Background())
-	ctx := workerruntime.SetKeysDirectory(w.GetContext(), keyDir)
-
-	require.NoError(t, w.BaseDir().Mkdir("workingdir", os.FileMode(0700)))
-	workingdir, err := w.BaseDir().Open("workingdir")
-	require.NoError(t, err)
-	w.SetContext(workerruntime.SetWorkingDirectory(ctx, workingdir))
-
-	w.SetGelfLogger(nil, logrus.New())
-	// End worker init
-
-	keyInstallAction := sdk.Action{
-		Parameters: []sdk.Parameter{
-			{
-				Name:  "key",
-				Type:  sdk.KeyParameter,
-				Value: "proj-mykey",
-			}, {
-				Name:  "file",
-				Type:  sdk.KeyParameter,
-				Value: "my-key",
-			},
-		},
-	}
-	secrets := []sdk.Variable{
-		{
-			ID:    1,
-			Name:  "cds.key.proj-mykey.priv",
-			Value: "test",
-			Type:  string(sdk.KeyTypeSSH),
-		},
-	}
-
-	res, err := action.RunInstallKey(w.GetContext(), w, keyInstallAction, secrets)
-	assert.NoError(t, err)
-	assert.Equal(t, sdk.StatusSuccess, res.Status)
-
-	wkd, err := workerruntime.WorkingDirectory(w.GetContext())
-	require.NoError(t, err)
-
-	absBasedir, _ := filepath.Abs(basedir)
-	assert.FileExists(t, filepath.Join(absBasedir, wkd.Name(), "my-key"))
-
-}
-
-func TestRunInstallKeyAction_Absolute(t *testing.T) {
-	// Init a real worker, not the mocking one
-	var w = new(internal.CurrentWorker)
-	fs := afero.NewOsFs()
-	basedir := "test-" + test.GetTestName(t) + "-" + sdk.RandomString(10) + "-" + fmt.Sprintf("%d", time.Now().Unix())
-	require.NoError(t, fs.MkdirAll(basedir, os.FileMode(0755)))
-	cfg := &workerruntime.WorkerConfig{
-		Name:                "test-worker",
-		HatcheryName:        "test-hatchery",
-		APIEndpoint:         "http://cds-api.local",
-		APIToken:            "xxx-my-token",
-		APIEndpointInsecure: true,
-		Basedir:             basedir,
-	}
-	if err := w.Init(cfg, afero.NewBasePathFs(fs, basedir)); err != nil {
-		t.Fatalf("worker init failed: %v", err)
-	}
-	require.NoError(t, w.BaseDir().Mkdir("keys", os.FileMode(0700)))
-	keyDir, err := w.BaseDir().Open("keys")
-	require.NoError(t, err)
-	w.SetContext(context.Background())
-	ctx := workerruntime.SetKeysDirectory(w.GetContext(), keyDir)
-
-	require.NoError(t, w.BaseDir().Mkdir("workingdir", os.FileMode(0700)))
-	workingdir, err := w.BaseDir().Open("workingdir")
-	require.NoError(t, err)
-	w.SetContext(workerruntime.SetWorkingDirectory(ctx, workingdir))
-
-	w.SetGelfLogger(nil, logrus.New())
-	// End worker init
-
-	keyInstallAction := sdk.Action{
-		Parameters: []sdk.Parameter{
-			{
-				Name:  "key",
-				Type:  sdk.KeyParameter,
-				Value: "proj-mykey",
-			}, {
-				Name:  "file",
-				Type:  sdk.KeyParameter,
-				Value: "/tmp/my-key",
-			},
-		},
-	}
-	secrets := []sdk.Variable{
-		{
-			ID:    1,
-			Name:  "cds.key.proj-mykey.priv",
-			Value: "test",
-			Type:  string(sdk.KeyTypeSSH),
-		},
-	}
-	res, err := action.RunInstallKey(w.GetContext(), w, keyInstallAction, secrets)
-	assert.NoError(t, err)
-	assert.Equal(t, sdk.StatusSuccess, res.Status)
-	assert.FileExists(t, "/tmp/my-key")
-	os.RemoveAll("/tmp/my-key")
-}
diff --git a/engine/worker/internal/handler_run_result_test.go b/engine/worker/internal/handler_run_result_test.go
index 397f5e002..116d19d9f 100644
--- a/engine/worker/internal/handler_run_result_test.go
+++ b/engine/worker/internal/handler_run_result_test.go
@@ -54,7 +54,9 @@ func Test_addRunResultStaticFileHandler(t *testing.T) {
 	).Times(1)
 
 	v := sdk.WorkflowRunResultStaticFile{
-		Name:      "foo",
+		WorkflowRunResultArtifactCommon: sdk.WorkflowRunResultArtifactCommon{
+			Name: "foo",
+		},
 		RemoteURL: "http://locat.local/static/foo.html",
 	}
 	buf, err := json.Marshal(v)
@@ -107,7 +109,9 @@ func Test_addRunResultArtifactManagerHandler(t *testing.T) {
 	).Times(1)
 
 	v := sdk.WorkflowRunResultArtifactManager{
-		Name:     "foo",
+		WorkflowRunResultArtifactCommon: sdk.WorkflowRunResultArtifactCommon{
+			Name: "foo",
+		},
 		RepoName: "my-repo",
 	}
 	buf, err := json.Marshal(v)
diff --git a/engine/worker/internal/start_test.go b/engine/worker/internal/start_test.go
index 8b74322cc..4e994b42e 100644
--- a/engine/worker/internal/start_test.go
+++ b/engine/worker/internal/start_test.go
@@ -86,6 +86,11 @@ func TestStartWorkerWithABookedJob(t *testing.T) {
 			Status: sdk.StatusBuilding,
 		})
 
+	gock.New("http://cds-api.local").Post("/queue/workflows/42/spawn/infos").Times(3).
+		HeaderPresent("Authorization").
+		Reply(200).
+		JSON(nil)
+
 	gock.New("http://cds-api.local").Get("project/proj_key/workflows/workflow_name/runs/0").Times(1).
 		HeaderPresent("Authorization").
 		Reply(200).
@@ -284,14 +289,14 @@ export FOO_FROM_HOOK=BAR`,
 
 	var checkRequest gock.ObserverFunc = func(request *http.Request, mock gock.Mock) {
 		bodyContent, err := io.ReadAll(request.Body)
-		assert.NoError(t, err)
+		require.NoError(t, err)
 		request.Body = io.NopCloser(bytes.NewReader(bodyContent))
 		if mock != nil {
 			switch mock.Request().URLStruct.String() {
 			case "http://cds-api.local/queue/workflows/42/step":
 				var result sdk.StepStatus
 				err := json.Unmarshal(bodyContent, &result)
-				assert.NoError(t, err)
+				require.NoError(t, err)
 
 				switch result.StepOrder {
 				case 0:
@@ -320,15 +325,14 @@ export FOO_FROM_HOOK=BAR`,
 				}
 			case "http://cds-api.local/queue/workflows/42/result":
 				var result sdk.Result
-				err := json.Unmarshal(bodyContent, &result)
-				assert.NoError(t, err)
-				assert.Equal(t, int64(42), result.BuildID)
-				assert.Equal(t, sdk.StatusFail, result.Status)
+				require.NoError(t, json.Unmarshal(bodyContent, &result))
+				require.Equal(t, int64(42), result.BuildID)
+				require.Equal(t, sdk.StatusFail, result.Status)
 				if len(result.NewVariables) > 0 {
-					assert.Equal(t, "cds.build.newvar", result.NewVariables[0].Name)
+					require.Equal(t, "cds.build.newvar", result.NewVariables[0].Name)
 					// assert.Equal(t, "cds.semver", result.NewVariables[0].Name)
 					// assert.Equal(t, "git.describe", result.NewVariables[0].Name)
-					assert.Equal(t, "newval", result.NewVariables[0].Value)
+					require.Equal(t, "newval", result.NewVariables[0].Value)
 				} else {
 					t.Error("missing new variables")
 				}
@@ -363,8 +367,7 @@ export FOO_FROM_HOOK=BAR`,
 	ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
 	defer cancel()
 
-	err = internal.StartWorker(ctx, w, 42)
-	assert.NoError(t, err)
+	require.NoError(t, internal.StartWorker(ctx, w, 42))
 
 	var isDone bool
 	if gock.IsDone() {
@@ -380,7 +383,7 @@ export FOO_FROM_HOOK=BAR`,
 			}
 		}
 	}
-	assert.True(t, isDone)
+	require.True(t, isDone)
 	if gock.HasUnmatchedRequest() {
 		reqs := gock.GetUnmatchedRequests()
 		for _, req := range reqs {
@@ -426,5 +429,4 @@ export FOO_FROM_HOOK=BAR`,
 	assert.Equal(t, 1, strings.Count(logBuffer.String(), "HATCHERY_REGION=local-test"))
 	assert.Equal(t, 1, strings.Count(logBuffer.String(), "BASEDIR="))
 	assert.Equal(t, 1, strings.Count(logBuffer.String(), "FOO_FROM_HOOK=BAR"))
-
 }
diff --git a/sdk/workflow_run_result_test.go b/sdk/workflow_run_result_test.go
new file mode 100644
index 000000000..3eebcc5fe
--- /dev/null
+++ b/sdk/workflow_run_result_test.go
@@ -0,0 +1,81 @@
+package sdk_test
+
+import (
+	"encoding/json"
+	"testing"
+	"time"
+
+	"github.com/ovh/cds/sdk"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestWorkflowRunResults_Unique(t *testing.T) {
+	r1, err := json.Marshal(sdk.WorkflowRunResultArtifact{
+		WorkflowRunResultArtifactCommon: sdk.WorkflowRunResultArtifactCommon{
+			Name: "r1",
+		},
+	})
+	require.NoError(t, err)
+	r2, err := json.Marshal(sdk.WorkflowRunResultArtifactManager{
+		WorkflowRunResultArtifactCommon: sdk.WorkflowRunResultArtifactCommon{
+			Name: "r2",
+		},
+		RepoType: "docker",
+	})
+	require.NoError(t, err)
+	r3, err := json.Marshal(sdk.WorkflowRunResultArtifactManager{
+		WorkflowRunResultArtifactCommon: sdk.WorkflowRunResultArtifactCommon{
+			Name: "r3",
+		},
+		RepoType: "helm",
+	})
+	require.NoError(t, err)
+
+	now := time.Now()
+
+	rs := sdk.WorkflowRunResults{
+		{
+			ID:      "A",
+			Type:    sdk.WorkflowRunResultTypeArtifact,
+			SubNum:  0,
+			DataRaw: r1,
+			Created: now.Add(time.Second),
+		},
+		{
+			ID:      "B",
+			Type:    sdk.WorkflowRunResultTypeArtifact,
+			SubNum:  1,
+			DataRaw: r1,
+			Created: now.Add(2 * time.Second),
+		},
+		{
+			ID:      "C",
+			Type:    sdk.WorkflowRunResultTypeArtifactManager,
+			SubNum:  0,
+			DataRaw: r2,
+			Created: now.Add(3 * time.Second),
+		},
+		{
+			ID:      "D",
+			Type:    sdk.WorkflowRunResultTypeArtifactManager,
+			SubNum:  0,
+			DataRaw: r3,
+			Created: now.Add(4 * time.Second),
+		},
+		{
+			ID:      "E",
+			Type:    sdk.WorkflowRunResultTypeArtifactManager,
+			SubNum:  1,
+			DataRaw: r3,
+			Created: now.Add(5 * time.Second),
+		},
+	}
+
+	res, err := rs.Unique()
+	require.NoError(t, err)
+	require.Len(t, res, 3)
+	require.Equal(t, "B", res[0].ID)
+	require.Equal(t, "C", res[1].ID)
+	require.Equal(t, "E", res[2].ID)
+}