Repository: ovh/cds
Pull request #6266 (2022-09-02 09:48:23)

feat(interpolate): add helper
Signed-off-by: francois  samin <francois.samin@corp.ovh.com>



Patch:
diff --git a/cli/cdsctl/experimental.go b/cli/cdsctl/experimental.go
index a615d152a..fea6a5800 100644
--- a/cli/cdsctl/experimental.go
+++ b/cli/cdsctl/experimental.go
@@ -15,6 +15,7 @@ var experimentalCmd = cli.Command{
 
 func experimentalCommands() []*cobra.Command {
 	return []*cobra.Command{
+		experimentalRbac(),
 		experimentalProject(),
 	}
 }
diff --git a/cli/cdsctl/experimental_rbac.go b/cli/cdsctl/experimental_rbac.go
new file mode 100644
index 000000000..03f44f803
--- /dev/null
+++ b/cli/cdsctl/experimental_rbac.go
@@ -0,0 +1,50 @@
+package main
+
+import (
+	"context"
+	"os"
+
+	"github.com/spf13/cobra"
+
+	"github.com/ovh/cds/cli"
+	"github.com/ovh/cds/sdk/cdsclient"
+)
+
+var experimentalRbacCmd = cli.Command{
+	Name:  "rbac",
+	Short: "CDS Experimental rbac commands",
+}
+
+func experimentalRbac() *cobra.Command {
+	return cli.NewCommand(experimentalRbacCmd, nil, []*cobra.Command{
+		cli.NewCommand(rbacImportCmd, rbacImportFunc, nil, withAllCommandModifiers()...),
+	})
+}
+
+var rbacImportCmd = cli.Command{
+	Name:    "import",
+	Short:   "Import a rbac rule from a yaml file",
+	Example: "cdsctl rbac import file.yml",
+	Ctx:     []cli.Arg{},
+	Args: []cli.Arg{
+		{Name: "filename"},
+	},
+	Flags: []cli.Flag{
+		{Name: "force", Type: cli.FlagBool},
+	},
+}
+
+func rbacImportFunc(v cli.Values) error {
+	f, err := os.Open(v.GetString("filename"))
+	if err != nil {
+		return cli.WrapError(err, "unable to open file %s", v.GetString("filename"))
+	}
+	defer f.Close() // nolint
+
+	var mods []cdsclient.RequestModifier
+	if v.GetBool("force") {
+		mods = append(mods, cdsclient.Force())
+	}
+	_, err = client.RBACImport(context.Background(), f, mods...)
+	return err
+}
diff --git a/contrib/grpcplugins/action/plugin-archive/go.mod b/contrib/grpcplugins/action/plugin-archive/go.mod
index caad49611..3b610ae35 100644
--- a/contrib/grpcplugins/action/plugin-archive/go.mod
+++ b/contrib/grpcplugins/action/plugin-archive/go.mod
@@ -8,7 +8,7 @@ require (
 	github.com/golang/protobuf v1.5.2
 	github.com/mholt/archiver/v3 v3.5.1-0.20210618180617-81fac4ba96e4
 	github.com/ovh/cds v0.0.0-00010101000000-000000000000
-	github.com/stretchr/testify v1.7.0
+	github.com/stretchr/testify v1.8.0
 )
 
 require (
@@ -75,5 +75,5 @@ require (
 	google.golang.org/grpc v1.43.0 // indirect
 	google.golang.org/protobuf v1.27.1 // indirect
 	gopkg.in/warnings.v0 v0.1.2 // indirect
-	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
+	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
diff --git a/contrib/grpcplugins/action/plugin-archive/go.sum b/contrib/grpcplugins/action/plugin-archive/go.sum
index ce283252c..2ffbda87b 100644
--- a/contrib/grpcplugins/action/plugin-archive/go.sum
+++ b/contrib/grpcplugins/action/plugin-archive/go.sum
@@ -291,13 +291,16 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
 github.com/spf13/viper v1.10.1 h1:nuJZuYpG7gTj/XqiUwg8bA0cp1+M2mC3J4g5luUYBKk=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
 github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
 github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
@@ -671,8 +674,9 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/contrib/grpcplugins/action/plugin-artifactory-release-bundle-create/go.mod b/contrib/grpcplugins/action/plugin-artifactory-release-bundle-create/go.mod
index 9c33f11d4..5f5d378e5 100644
--- a/contrib/grpcplugins/action/plugin-artifactory-release-bundle-create/go.mod
+++ b/contrib/grpcplugins/action/plugin-artifactory-release-bundle-create/go.mod
@@ -12,7 +12,7 @@ require (
 	github.com/jfrog/jfrog-client-go v1.5.1
 	github.com/ovh/cds v0.0.0-00010101000000-000000000000
 	github.com/pkg/errors v0.9.1
-	github.com/stretchr/testify v1.7.0
+	github.com/stretchr/testify v1.8.0
 )
 
 require (
@@ -99,5 +99,5 @@ require (
 	gopkg.in/ini.v1 v1.66.4 // indirect
 	gopkg.in/warnings.v0 v0.1.2 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
-	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
+	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
diff --git a/contrib/grpcplugins/action/plugin-artifactory-release-bundle-create/go.sum b/contrib/grpcplugins/action/plugin-artifactory-release-bundle-create/go.sum
index db0df132f..30a958212 100644
--- a/contrib/grpcplugins/action/plugin-artifactory-release-bundle-create/go.sum
+++ b/contrib/grpcplugins/action/plugin-artifactory-release-bundle-create/go.sum
@@ -463,13 +463,16 @@ github.com/spf13/viper v1.10.1 h1:nuJZuYpG7gTj/XqiUwg8bA0cp1+M2mC3J4g5luUYBKk=
 github.com/spf13/viper v1.10.1/go.mod h1:IGlFPqhNAPKRxohIzWpI5QEy4kuI7tcl5WvR+8qy1rU=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
 github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
 github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
@@ -917,8 +920,9 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/contrib/grpcplugins/action/plugin-artifactory-release-bundle-distribute/go.sum b/contrib/grpcplugins/action/plugin-artifactory-release-bundle-distribute/go.sum
index 881b16b5f..23d0fc25c 100644
--- a/contrib/grpcplugins/action/plugin-artifactory-release-bundle-distribute/go.sum
+++ b/contrib/grpcplugins/action/plugin-artifactory-release-bundle-distribute/go.sum
@@ -295,8 +295,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
 github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
 github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
 github.com/ulikunitz/xz v0.5.9 h1:RsKRIA2MO8x56wkkcd3LbtcE/uMszhb6DpRf+3uwa3I=
@@ -669,8 +669,8 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/contrib/grpcplugins/action/plugin-download/go.mod b/contrib/grpcplugins/action/plugin-download/go.mod
index fe751e385..31b2f8444 100644
--- a/contrib/grpcplugins/action/plugin-download/go.mod
+++ b/contrib/grpcplugins/action/plugin-download/go.mod
@@ -7,7 +7,7 @@ go 1.18
 require (
 	github.com/golang/protobuf v1.5.2
 	github.com/ovh/cds v0.0.0-00010101000000-000000000000
-	github.com/stretchr/testify v1.7.0
+	github.com/stretchr/testify v1.8.0
 )
 
 require (
@@ -75,5 +75,5 @@ require (
 	google.golang.org/grpc v1.43.0 // indirect
 	google.golang.org/protobuf v1.27.1 // indirect
 	gopkg.in/warnings.v0 v0.1.2 // indirect
-	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
+	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
diff --git a/contrib/grpcplugins/action/plugin-download/go.sum b/contrib/grpcplugins/action/plugin-download/go.sum
index f70cf5c69..b5e6a9f32 100644
--- a/contrib/grpcplugins/action/plugin-download/go.sum
+++ b/contrib/grpcplugins/action/plugin-download/go.sum
@@ -289,13 +289,16 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
 github.com/spf13/viper v1.10.1 h1:nuJZuYpG7gTj/XqiUwg8bA0cp1+M2mC3J4g5luUYBKk=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
 github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
 github.com/ulikunitz/xz v0.5.9 h1:RsKRIA2MO8x56wkkcd3LbtcE/uMszhb6DpRf+3uwa3I=
@@ -668,8 +671,9 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/contrib/grpcplugins/action/plugin-group-tmpl/go.sum b/contrib/grpcplugins/action/plugin-group-tmpl/go.sum
index f70cf5c69..9132a168c 100644
--- a/contrib/grpcplugins/action/plugin-group-tmpl/go.sum
+++ b/contrib/grpcplugins/action/plugin-group-tmpl/go.sum
@@ -294,8 +294,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
 github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
 github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
 github.com/ulikunitz/xz v0.5.9 h1:RsKRIA2MO8x56wkkcd3LbtcE/uMszhb6DpRf+3uwa3I=
@@ -668,8 +668,8 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/contrib/grpcplugins/action/plugin-kafka-publish/go.mod b/contrib/grpcplugins/action/plugin-kafka-publish/go.mod
index 19ddf53e0..e29b93512 100644
--- a/contrib/grpcplugins/action/plugin-kafka-publish/go.mod
+++ b/contrib/grpcplugins/action/plugin-kafka-publish/go.mod
@@ -13,7 +13,7 @@ require (
 	github.com/golang/protobuf v1.5.2
 	github.com/ovh/cds v0.0.0-00010101000000-000000000000
 	github.com/phayes/permbits v0.0.0-20190612203442-39d7c581d2ee
-	github.com/stretchr/testify v1.7.0
+	github.com/stretchr/testify v1.8.0
 	gopkg.in/urfave/cli.v1 v1.20.0
 )
 
@@ -96,6 +96,7 @@ require (
 	github.com/prometheus/procfs v0.0.8 // indirect
 	github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect
 	github.com/rockbears/log v0.6.0 // indirect
+	github.com/rockbears/yaml v0.1.1-0.20220901090137-13dadb408625 // indirect
 	github.com/rubenv/sql-migrate v0.0.0-20160620083229-6f4757563362 // indirect
 	github.com/sergi/go-diff v1.1.0 // indirect
 	github.com/sguiheux/go-coverage v0.0.0-20190710153556-287b082a7197 // indirect
@@ -128,5 +129,5 @@ require (
 	gopkg.in/square/go-jose.v2 v2.3.1 // indirect
 	gopkg.in/warnings.v0 v0.1.2 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
-	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
+	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
diff --git a/contrib/grpcplugins/action/plugin-kafka-publish/go.sum b/contrib/grpcplugins/action/plugin-kafka-publish/go.sum
index 7c77de5a4..2055160fe 100644
--- a/contrib/grpcplugins/action/plugin-kafka-publish/go.sum
+++ b/contrib/grpcplugins/action/plugin-kafka-publish/go.sum
@@ -507,6 +507,8 @@ github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5X
 github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
 github.com/rockbears/log v0.6.0 h1:Wzpu7nbrZFFJy14ku41jI2/UEh3d0CJwvIED9/FQZKQ=
 github.com/rockbears/log v0.6.0/go.mod h1:z46IOEOh914gJvg256Vm3F4s8K7D3ePaEAzuMYcfk98=
+github.com/rockbears/yaml v0.1.1-0.20220901090137-13dadb408625 h1:HQAWw/D9RItCYSoWFs8E7IrGKrX9ivqAlCq47DM3IVU=
+github.com/rockbears/yaml v0.1.1-0.20220901090137-13dadb408625/go.mod h1:8cDJx2PWQJMtfGgsRCvHVbIB61SV3dvy8o6EGv2cIpg=
 github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
 github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
@@ -547,13 +549,16 @@ github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5q
 github.com/spf13/viper v1.10.1 h1:nuJZuYpG7gTj/XqiUwg8bA0cp1+M2mC3J4g5luUYBKk=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
 github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
 github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
@@ -1066,8 +1071,9 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/contrib/grpcplugins/action/plugin-marathon/go.mod b/contrib/grpcplugins/action/plugin-marathon/go.mod
index 2d6121739..7a0798ea7 100644
--- a/contrib/grpcplugins/action/plugin-marathon/go.mod
+++ b/contrib/grpcplugins/action/plugin-marathon/go.mod
@@ -9,7 +9,7 @@ require (
 	github.com/golang/protobuf v1.5.2
 	github.com/ovh/cds v0.0.0-00010101000000-000000000000
 	github.com/ovh/cds/sdk/interpolate v0.0.0-20191126072910-b8d81d038865
-	github.com/stretchr/testify v1.7.0
+	github.com/stretchr/testify v1.8.0
 	github.com/xeipuuv/gojsonschema v1.2.0
 )
 
@@ -76,6 +76,7 @@ require (
 	github.com/prometheus/common v0.9.1 // indirect
 	github.com/prometheus/procfs v0.0.8 // indirect
 	github.com/rockbears/log v0.6.0 // indirect
+	github.com/rockbears/yaml v0.1.1-0.20220901090137-13dadb408625 // indirect
 	github.com/sergi/go-diff v1.1.0 // indirect
 	github.com/sguiheux/go-coverage v0.0.0-20190710153556-287b082a7197 // indirect
 	github.com/sirupsen/logrus v1.8.1 // indirect
@@ -106,5 +107,5 @@ require (
 	gopkg.in/AlecAivazis/survey.v1 v1.7.1 // indirect
 	gopkg.in/warnings.v0 v0.1.2 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
-	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
+	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
diff --git a/contrib/grpcplugins/action/plugin-marathon/go.sum b/contrib/grpcplugins/action/plugin-marathon/go.sum
index cc3d83336..8315df88b 100644
--- a/contrib/grpcplugins/action/plugin-marathon/go.sum
+++ b/contrib/grpcplugins/action/plugin-marathon/go.sum
@@ -454,6 +454,8 @@ github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40T
 github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
 github.com/rockbears/log v0.6.0 h1:Wzpu7nbrZFFJy14ku41jI2/UEh3d0CJwvIED9/FQZKQ=
 github.com/rockbears/log v0.6.0/go.mod h1:z46IOEOh914gJvg256Vm3F4s8K7D3ePaEAzuMYcfk98=
+github.com/rockbears/yaml v0.1.1-0.20220901090137-13dadb408625 h1:HQAWw/D9RItCYSoWFs8E7IrGKrX9ivqAlCq47DM3IVU=
+github.com/rockbears/yaml v0.1.1-0.20220901090137-13dadb408625/go.mod h1:8cDJx2PWQJMtfGgsRCvHVbIB61SV3dvy8o6EGv2cIpg=
 github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
 github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
@@ -492,13 +494,16 @@ github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5q
 github.com/spf13/viper v1.10.1 h1:nuJZuYpG7gTj/XqiUwg8bA0cp1+M2mC3J4g5luUYBKk=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
 github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
 github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
@@ -993,8 +998,9 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/contrib/grpcplugins/action/plugin-npm-audit-parser/go.sum b/contrib/grpcplugins/action/plugin-npm-audit-parser/go.sum
index f70cf5c69..9132a168c 100644
--- a/contrib/grpcplugins/action/plugin-npm-audit-parser/go.sum
+++ b/contrib/grpcplugins/action/plugin-npm-audit-parser/go.sum
@@ -294,8 +294,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
 github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
 github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
 github.com/ulikunitz/xz v0.5.9 h1:RsKRIA2MO8x56wkkcd3LbtcE/uMszhb6DpRf+3uwa3I=
@@ -668,8 +668,8 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/contrib/grpcplugins/action/plugin-ssh-cmd/go.sum b/contrib/grpcplugins/action/plugin-ssh-cmd/go.sum
index f70cf5c69..9132a168c 100644
--- a/contrib/grpcplugins/action/plugin-ssh-cmd/go.sum
+++ b/contrib/grpcplugins/action/plugin-ssh-cmd/go.sum
@@ -294,8 +294,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
 github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
 github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
 github.com/ulikunitz/xz v0.5.9 h1:RsKRIA2MO8x56wkkcd3LbtcE/uMszhb6DpRf+3uwa3I=
@@ -668,8 +668,8 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/contrib/grpcplugins/action/plugin-tmpl/go.sum b/contrib/grpcplugins/action/plugin-tmpl/go.sum
index f70cf5c69..9132a168c 100644
--- a/contrib/grpcplugins/action/plugin-tmpl/go.sum
+++ b/contrib/grpcplugins/action/plugin-tmpl/go.sum
@@ -294,8 +294,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
 github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
 github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
 github.com/ulikunitz/xz v0.5.9 h1:RsKRIA2MO8x56wkkcd3LbtcE/uMszhb6DpRf+3uwa3I=
@@ -668,8 +668,8 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/contrib/grpcplugins/action/plugin-venom/go.mod b/contrib/grpcplugins/action/plugin-venom/go.mod
index bb8e6ecf3..f785d0304 100644
--- a/contrib/grpcplugins/action/plugin-venom/go.mod
+++ b/contrib/grpcplugins/action/plugin-venom/go.mod
@@ -8,7 +8,7 @@ require (
 	github.com/golang/protobuf v1.5.2
 	github.com/ovh/cds v0.0.0-00010101000000-000000000000
 	github.com/ovh/venom v0.26.0
-	github.com/stretchr/testify v1.7.0
+	github.com/stretchr/testify v1.8.0
 	gopkg.in/yaml.v2 v2.4.0
 )
 
@@ -108,5 +108,5 @@ require (
 	gopkg.in/ini.v1 v1.66.4 // indirect
 	gopkg.in/testfixtures.v2 v2.6.0 // indirect
 	gopkg.in/warnings.v0 v0.1.2 // indirect
-	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
+	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
diff --git a/contrib/grpcplugins/action/plugin-venom/go.sum b/contrib/grpcplugins/action/plugin-venom/go.sum
index 480bb4733..96e598a77 100644
--- a/contrib/grpcplugins/action/plugin-venom/go.sum
+++ b/contrib/grpcplugins/action/plugin-venom/go.sum
@@ -365,13 +365,16 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
 github.com/spf13/viper v1.10.1 h1:nuJZuYpG7gTj/XqiUwg8bA0cp1+M2mC3J4g5luUYBKk=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
 github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
 github.com/ulikunitz/xz v0.5.9 h1:RsKRIA2MO8x56wkkcd3LbtcE/uMszhb6DpRf+3uwa3I=
@@ -761,8 +764,9 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/contrib/integrations/artifactory/artifactory.go b/contrib/integrations/artifactory/artifactory.go
index 82842c9e8..102ad882e 100644
--- a/contrib/integrations/artifactory/artifactory.go
+++ b/contrib/integrations/artifactory/artifactory.go
@@ -17,8 +17,8 @@ import (
 	"github.com/jfrog/jfrog-client-go/config"
 	"github.com/jfrog/jfrog-client-go/distribution"
 	authdistrib "github.com/jfrog/jfrog-client-go/distribution/auth"
-	"github.com/jfrog/jfrog-client-go/utils/log"
 	"github.com/pkg/errors"
+	"github.com/rockbears/log"
 
 	"github.com/ovh/cds/engine/api/integration/artifact_manager"
 	"github.com/ovh/cds/sdk"
@@ -236,7 +236,7 @@ type BuildInfoRequest struct {
 
 func PrepareBuildInfo(ctx context.Context, artiClient artifact_manager.ArtifactManager, r BuildInfoRequest) (*buildinfo.BuildInfo, error) {
 	buildInfoName := fmt.Sprintf("%s/%s/%s", r.BuildInfoPrefix, r.ProjectKey, r.WorkflowName)
-	log.Debug(ctx, "PrepareBuildInfo %q", buildInfoName)
+	log.Debug(ctx, "PrepareBuildInfo %q maturity:%q", buildInfoName, r.LowMaturitySuffix)
 
 	buildInfoRequest := &buildinfo.BuildInfo{
 		Properties: map[string]string{},
diff --git a/engine/api/api_routes.go b/engine/api/api_routes.go
index 4afb64315..a4e1ae39b 100644
--- a/engine/api/api_routes.go
+++ b/engine/api/api_routes.go
@@ -321,6 +321,7 @@ func (api *API) InitRouter() {
 	r.Handle("/queue/workflows/{permJobID}/coverage", Scope(sdk.AuthConsumerScopeRunExecution), r.POSTEXECUTE(api.postWorkflowJobCoverageResultsHandler, MaintenanceAware()))
 	r.Handle("/queue/workflows/{permJobID}/run/results", Scope(sdk.AuthConsumerScopeRunExecution), r.POSTEXECUTE(api.postWorkflowRunResultsHandler))
 	r.Handle("/queue/workflows/{permJobID}/run/results/check", Scope(sdk.AuthConsumerScopeRunExecution), r.POSTEXECUTE(api.workflowRunResultCheckUploadHandler))
+	r.Handle("/queue/workflows/{permJobID}/run/results/promote", Scope(sdk.AuthConsumerScopeRunExecution), r.POSTEXECUTE(api.workflowRunResultPromoteHandler))
 	r.Handle("/queue/workflows/{permJobID}/run/results/release", Scope(sdk.AuthConsumerScopeRunExecution), r.POSTEXECUTE(api.workflowRunResultReleaseHandler))
 	r.Handle("/queue/workflows/{permJobID}/test", Scope(sdk.AuthConsumerScopeRunExecution), r.POSTEXECUTE(api.postWorkflowJobTestsResultsHandler, MaintenanceAware()))
 	r.Handle("/queue/workflows/{permJobID}/tag", Scope(sdk.AuthConsumerScopeRunExecution), r.POSTEXECUTE(api.postWorkflowJobTagsHandler, MaintenanceAware()))
@@ -432,6 +433,8 @@ func (api *API) InitRouter() {
 	r.Handle("/template/{groupName}/{templateSlug}/instance/{instanceID}", Scope(sdk.AuthConsumerScopeTemplate), r.DELETE(api.deleteTemplateInstanceHandler))
 	r.Handle("/template/{groupName}/{templateSlug}/usage", Scope(sdk.AuthConsumerScopeTemplate), r.GET(api.getTemplateUsageHandler))
 
+	r.Handle("/v2/rbac/import", nil, r.POSTv2(api.postImportRbacHandler))
+
 	r.Handle("/v2/repository/analyze", Scope(sdk.AuthConsumerScopeHooks), r.POSTv2(api.postRepositoryAnalysisHandler))
 	r.Handle("/v2/project/repositories", Scope(sdk.AuthConsumerScopeHooks), r.GETv2(api.getAllRepositoriesHandler))
 	r.Handle("/v2/project/repositories/{repositoryIdentifier}/hook", Scope(sdk.AuthConsumerScopeHooks), r.GETv2(api.getRepositoryHookHandler))
diff --git a/engine/api/integration/artifact_manager/artifactory/client.go b/engine/api/integration/artifact_manager/artifactory/client.go
index 9eb217f1a..2b04aef3e 100644
--- a/engine/api/integration/artifact_manager/artifactory/client.go
+++ b/engine/api/integration/artifact_manager/artifactory/client.go
@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"net/http"
 	"net/url"
+	"path"
 	"strconv"
 	"strings"
 
@@ -27,6 +28,11 @@ func (c *Client) GetFileInfo(repoName string, filePath string) (sdk.FileInfo, er
 		return fi, sdk.NewErrorFrom(sdk.ErrUnknownError, "unable to get repository %s: %v", repoName, err)
 	}
 
+	// To get FileInfo for a docker image, we have to check the manifest file
+	if repoDetails.PackageType == "docker" && !strings.HasSuffix(filePath, "manifest.json") {
+		filePath = path.Join(filePath, "manifest.json")
+	}
+
 	fileInfoURL := fmt.Sprintf("%sapi/storage/%s/%s", c.Asm.GetConfig().GetServiceDetails().GetUrl(), repoName, filePath)
 	httpDetails := c.Asm.GetConfig().GetServiceDetails().CreateHttpClientDetails()
 	re, body, _, err := c.Asm.Client().SendGet(fileInfoURL, true, &httpDetails)
diff --git a/engine/api/purge/purge.go b/engine/api/purge/purge.go
index e46f93773..36e12ea3d 100644
--- a/engine/api/purge/purge.go
+++ b/engine/api/purge/purge.go
@@ -331,7 +331,7 @@ func DeleteArtifactsFromRepositoryManager(ctx context.Context, db gorp.SqlExecut
 	)
 
 	// Load the token from secrets
-	secrets, err := workflow.LoadDecryptSecrets(ctx, db, wr, nil)
+	secrets, err := workflow.LoadDecryptSecrets(ctx, db, wr, wr.RootRun())
 	if err != nil {
 		return err
 	}
diff --git a/engine/api/rbac/dao_rbac.go b/engine/api/rbac/dao_rbac.go
index a2f8e60ab..4b94d4a9c 100644
--- a/engine/api/rbac/dao_rbac.go
+++ b/engine/api/rbac/dao_rbac.go
@@ -12,14 +12,14 @@ import (
 	"github.com/ovh/cds/sdk"
 )
 
-func LoadRbacByName(ctx context.Context, db gorp.SqlExecutor, name string, opts ...LoadOptionFunc) (sdk.RBAC, error) {
+func LoadRBACByName(ctx context.Context, db gorp.SqlExecutor, name string, opts ...LoadOptionFunc) (*sdk.RBAC, error) {
 	query := `SELECT * FROM rbac WHERE name = $1`
 	return get(ctx, db, gorpmapping.NewQuery(query).Args(name), opts...)
 }
 
 // Insert a RBAC permission in database
 func Insert(ctx context.Context, db gorpmapper.SqlExecutorWithTx, rb *sdk.RBAC) error {
-	if err := sdk.IsValidRbac(rb); err != nil {
+	if err := sdk.IsValidRBAC(rb); err != nil {
 		return err
 	}
 	if rb.ID == "" {
@@ -39,7 +39,7 @@ func Insert(ctx context.Context, db gorpmapper.SqlExecutorWithTx, rb *sdk.RBAC)
 			RbacID:     dbRb.ID,
 			RBACGlobal: rb.Globals[i],
 		}
-		if err := insertRbacGlobal(ctx, db, &dbRbGlobal); err != nil {
+		if err := insertRBACGlobal(ctx, db, &dbRbGlobal); err != nil {
 			return err
 		}
 	}
@@ -48,7 +48,7 @@ func Insert(ctx context.Context, db gorpmapper.SqlExecutorWithTx, rb *sdk.RBAC)
 			RbacID:      dbRb.ID,
 			RBACProject: rb.Projects[i],
 		}
-		if err := insertRbacProject(ctx, db, &dbRbProject); err != nil {
+		if err := insertRBACProject(ctx, db, &dbRbProject); err != nil {
 			return err
 		}
 	}
@@ -71,30 +71,30 @@ func Delete(_ context.Context, db gorpmapper.SqlExecutorWithTx, rb sdk.RBAC) err
 	return nil
 }
 
-func get(ctx context.Context, db gorp.SqlExecutor, q gorpmapping.Query, opts ...LoadOptionFunc) (sdk.RBAC, error) {
+func get(ctx context.Context, db gorp.SqlExecutor, q gorpmapping.Query, opts ...LoadOptionFunc) (*sdk.RBAC, error) {
 	var r sdk.RBAC
 	var rbacDB rbac
 	found, err := gorpmapping.Get(ctx, db, q, &rbacDB)
 	if err != nil {
-		return r, err
+		return nil, err
 	}
 	if !found {
-		return r, sdk.WithStack(sdk.ErrNotFound)
+		return nil, sdk.WithStack(sdk.ErrNotFound)
 	}
 
 	isValid, err := gorpmapping.CheckSignature(rbacDB, rbacDB.Signature)
 	if err != nil {
-		return r, sdk.WrapError(err, "error when checking signature for rbac %s", rbacDB.ID)
+		return nil, sdk.WrapError(err, "error when checking signature for rbac %s", rbacDB.ID)
 	}
 	if !isValid {
 		log.Error(ctx, "rbac.get> rbac %s (%s) data corrupted", rbacDB.Name, rbacDB.ID)
-		return r, sdk.WithStack(sdk.ErrNotFound)
+		return nil, sdk.WithStack(sdk.ErrNotFound)
 	}
 	for _, f := range opts {
 		if err := f(ctx, db, &rbacDB); err != nil {
-			return r, err
+			return nil, err
 		}
 	}
 	r = rbacDB.RBAC
-	return r, nil
+	return &r, nil
 }
diff --git a/engine/api/rbac/dao_rbac_global.go b/engine/api/rbac/dao_rbac_global.go
index 85cccd4eb..72ffb2a69 100644
--- a/engine/api/rbac/dao_rbac_global.go
+++ b/engine/api/rbac/dao_rbac_global.go
@@ -2,34 +2,35 @@ package rbac
 
 import (
 	"context"
-
-	"github.com/go-gorp/gorp"
+	"github.com/lib/pq"
+	"github.com/ovh/cds/sdk/telemetry"
 	"github.com/rockbears/log"
 
+	"github.com/go-gorp/gorp"
 	"github.com/ovh/cds/engine/api/database/gorpmapping"
 	"github.com/ovh/cds/engine/gorpmapper"
 	"github.com/ovh/cds/sdk"
 )
 
-func insertRbacGlobal(ctx context.Context, db gorpmapper.SqlExecutorWithTx, rg *rbacGlobal) error {
+func insertRBACGlobal(ctx context.Context, db gorpmapper.SqlExecutorWithTx, rg *rbacGlobal) error {
 	if err := gorpmapping.InsertAndSign(ctx, db, rg); err != nil {
 		return err
 	}
 
 	for _, userID := range rg.RBACUsersIDs {
-		if err := insertRbacGlobalUser(ctx, db, rg.ID, userID); err != nil {
+		if err := insertRBACGlobalUser(ctx, db, rg.ID, userID); err != nil {
 			return err
 		}
 	}
 	for _, groupID := range rg.RBACGroupsIDs {
-		if err := insertRbacGlobalGroup(ctx, db, rg.ID, groupID); err != nil {
+		if err := insertRBACGlobalGroup(ctx, db, rg.ID, groupID); err != nil {
 			return err
 		}
 	}
 	return nil
 }
 
-func insertRbacGlobalUser(ctx context.Context, db gorpmapper.SqlExecutorWithTx, rbacGlobalID int64, userID string) error {
+func insertRBACGlobalUser(ctx context.Context, db gorpmapper.SqlExecutorWithTx, rbacGlobalID int64, userID string) error {
 	rgu := rbacGlobalUser{
 		RbacGlobalID:     rbacGlobalID,
 		RbacGlobalUserID: userID,
@@ -40,7 +41,7 @@ func insertRbacGlobalUser(ctx context.Context, db gorpmapper.SqlExecutorWithTx,
 	return nil
 }
 
-func insertRbacGlobalGroup(ctx context.Context, db gorpmapper.SqlExecutorWithTx, rbacGlobalID int64, groupID int64) error {
+func insertRBACGlobalGroup(ctx context.Context, db gorpmapper.SqlExecutorWithTx, rbacGlobalID int64, groupID int64) error {
 	rgu := rbacGlobalGroup{
 		RbacGlobalID:      rbacGlobalID,
 		RbacGlobalGroupID: groupID,
@@ -51,44 +52,59 @@ func insertRbacGlobalGroup(ctx context.Context, db gorpmapper.SqlExecutorWithTx,
 	return nil
 }
 
-func getAllRBACGlobalUsers(ctx context.Context, db gorp.SqlExecutor, rbacGlobal *rbacGlobal) error {
-	q := gorpmapping.NewQuery("SELECT * FROM  rbac_global_users WHERE rbac_global_id = $1").Args(rbacGlobal.ID)
-	var rbacUserIDS []rbacGlobalUser
-	if err := gorpmapping.GetAll(ctx, db, q, &rbacUserIDS); err != nil {
-		return err
+func HasGlobalRole(ctx context.Context, db gorp.SqlExecutor, role string, userID string) (bool, error) {
+	ctx, next := telemetry.Span(ctx, "rbac.HasGlobalRole")
+	defer next()
+
+	// Get rbac_global_groups
+	rbacGlobalGroups, err := loadRBACGlobalGroupsByUserID(ctx, db, userID)
+	if err != nil {
+		return false, err
 	}
-	rbacGlobal.RBACGlobal.RBACUsersIDs = make([]string, 0, len(rbacUserIDS))
-	for _, rbacUsers := range rbacUserIDS {
-		isValid, err := gorpmapping.CheckSignature(rbacUsers, rbacUsers.Signature)
-		if err != nil {
-			return sdk.WrapError(err, "error when checking signature for rbac_global_users %d", rbacUsers.ID)
-		}
-		if !isValid {
-			log.Error(ctx, "rbac.getAllRBACGlobalUsers> rbac_global_users %d data corrupted", rbacUsers.ID)
-			continue
-		}
-		rbacGlobal.RBACGlobal.RBACUsersIDs = append(rbacGlobal.RBACGlobal.RBACUsersIDs, rbacUsers.RbacGlobalUserID)
+	// Get rbac_global_users
+	rbacGlobalUsers, err := loadRBACGlobalUsersByUserID(ctx, db, userID)
+	if err != nil {
+		return false, err
 	}
-	return nil
+
+	rbacGlobalIDs := make(sdk.Int64Slice, 0)
+	for _, rgg := range rbacGlobalGroups {
+		rbacGlobalIDs = append(rbacGlobalIDs, rgg.RbacGlobalID)
+	}
+	for _, rgu := range rbacGlobalUsers {
+		rbacGlobalIDs = append(rbacGlobalIDs, rgu.RbacGlobalID)
+	}
+	rbacGlobalIDs.Unique()
+
+	rgs, err := loadRBACGlobalsByRoleAndIDs(ctx, db, role, rbacGlobalIDs)
+	if err != nil {
+		return false, err
+	}
+
+	return len(rgs) > 0, nil
 }
 
-func getAllRBACGlobalGroups(ctx context.Context, db gorp.SqlExecutor, rbacGlobal *rbacGlobal) error {
-	q := gorpmapping.NewQuery("SELECT * FROM rbac_global_groups WHERE rbac_global_id = $1").Args(rbacGlobal.ID)
-	var rbacGroupIDs []rbacGlobalGroup
-	if err := gorpmapping.GetAll(ctx, db, q, &rbacGroupIDs); err != nil {
-		return err
+func loadRBACGlobalsByRoleAndIDs(ctx context.Context, db gorp.SqlExecutor, role string, rbacGlobalIDs []int64) ([]rbacGlobal, error) {
+	q := gorpmapping.NewQuery(`SELECT * from rbac_global WHERE role = $1 AND id = ANY($2)`).Args(role, pq.Int64Array(rbacGlobalIDs))
+	return getAllRBACGlobals(ctx, db, q)
+}
+
+func getAllRBACGlobals(ctx context.Context, db gorp.SqlExecutor, q gorpmapping.Query) ([]rbacGlobal, error) {
+	var rbacGlobals []rbacGlobal
+	if err := gorpmapping.GetAll(ctx, db, q, &rbacGlobals); err != nil {
+		return nil, err
 	}
-	rbacGlobal.RBACGlobal.RBACGroupsIDs = make([]int64, 0, len(rbacGroupIDs))
-	for _, rbacGroups := range rbacGroupIDs {
-		isValid, err := gorpmapping.CheckSignature(rbacGroups, rbacGroups.Signature)
+
+	for _, rg := range rbacGlobals {
+		isValid, err := gorpmapping.CheckSignature(rg, rg.Signature)
 		if err != nil {
-			return sdk.WrapError(err, "error when checking signature for rbac_global_groups %d", rbacGroups.ID)
+			return nil, sdk.WrapError(err, "error when checking signature for rbac_global %d", rg.ID)
 		}
 		if !isValid {
-			log.Error(ctx, "rbac.getAllRBACGlobalGroups> rbac_global_groups %d data corrupted", rbacGroups.ID)
+			log.Error(ctx, "rbac.getAllRBACGlobals> rbac_global %d data corrupted", rg.ID)
 			continue
 		}
-		rbacGlobal.RBACGlobal.RBACGroupsIDs = append(rbacGlobal.RBACGlobal.RBACGroupsIDs, rbacGroups.RbacGlobalGroupID)
+		rbacGlobals = append(rbacGlobals, rg)
 	}
-	return nil
+	return rbacGlobals, nil
 }
diff --git a/engine/api/rbac/dao_rbac_global_group.go b/engine/api/rbac/dao_rbac_global_group.go
new file mode 100644
index 000000000..2a7e6dead
--- /dev/null
+++ b/engine/api/rbac/dao_rbac_global_group.go
@@ -0,0 +1,61 @@
+package rbac
+
+import (
+	"context"
+
+	"github.com/go-gorp/gorp"
+	"github.com/lib/pq"
+	"github.com/rockbears/log"
+
+	"github.com/ovh/cds/engine/api/database/gorpmapping"
+	"github.com/ovh/cds/engine/api/group"
+	"github.com/ovh/cds/sdk"
+)
+
+func loadRBACGlobalGroupsByUserID(ctx context.Context, db gorp.SqlExecutor, userID string) ([]rbacGlobalGroup, error) {
+	groups, err := group.LoadAllByUserID(ctx, db, userID)
+	if err != nil {
+		return nil, err
+	}
+	groupIDs := make([]int64, 0, len(groups))
+	for _, g := range groups {
+		groupIDs = append(groupIDs, g.ID)
+	}
+	return loadRBACGlobalGroupsByGroupIDs(ctx, db, groupIDs)
+}
+
+func loadRBACGlobalGroupsByGroupIDs(ctx context.Context, db gorp.SqlExecutor, groupIDs []int64) ([]rbacGlobalGroup, error) {
+	q := gorpmapping.NewQuery("SELECT * FROM rbac_global_groups WHERE group_id = ANY ($1)").Args(pq.Int64Array(groupIDs))
+	return getAllRBACGlobalGroups(ctx, db, q)
+}
+
+func getAllRBACGlobalGroups(ctx context.Context, db gorp.SqlExecutor, q gorpmapping.Query) ([]rbacGlobalGroup, error) {
+	var rbacGroups []rbacGlobalGroup
+	if err := gorpmapping.GetAll(ctx, db, q, &rbacGroups); err != nil {
+		return nil, err
+	}
+	for _, rg := range rbacGroups {
+		isValid, err := gorpmapping.CheckSignature(rg, rg.Signature)
+		if err != nil {
+			return nil, sdk.WrapError(err, "error when checking signature for rbac_global_groups %d", rg.ID)
+		}
+		if !isValid {
+			log.Error(ctx, "rbac.getAllRBACGlobalGroups> rbac_global_groups %d data corrupted", rg.ID)
+			continue
+		}
+	}
+	return rbacGroups, nil
+}
+
+func loadRBACGlobalGroups(ctx context.Context, db gorp.SqlExecutor, rbacGlobal *rbacGlobal) error {
+	q := gorpmapping.NewQuery("SELECT * FROM rbac_global_groups WHERE rbac_global_id = $1").Args(rbacGlobal.ID)
+	rbacGlobalGroups, err := getAllRBACGlobalGroups(ctx, db, q)
+	if err != nil {
+		return err
+	}
+	rbacGlobal.RBACGroupsIDs = make([]int64, 0, len(rbacGlobalGroups))
+	for _, rgg := range rbacGlobalGroups {
+		rbacGlobal.RBACGroupsIDs = append(rbacGlobal.RBACGroupsIDs, rgg.RbacGlobalGroupID)
+	}
+	return nil
+}
diff --git a/engine/api/rbac/dao_rbac_global_user.go b/engine/api/rbac/dao_rbac_global_user.go
new file mode 100644
index 000000000..ef0790336
--- /dev/null
+++ b/engine/api/rbac/dao_rbac_global_user.go
@@ -0,0 +1,50 @@
+package rbac
+
+import (
+	"context"
+
+	"github.com/go-gorp/gorp"
+	"github.com/rockbears/log"
+
+	"github.com/ovh/cds/engine/api/database/gorpmapping"
+	"github.com/ovh/cds/sdk"
+)
+
+func loadRBACGlobalUsersByUserID(ctx context.Context, db gorp.SqlExecutor, userID string) ([]rbacGlobalUser, error) {
+	q := gorpmapping.NewQuery("SELECT * FROM rbac_global_users WHERE user_id = $1").Args(userID)
+	return getAllRBACGlobalUsers(ctx, db, q)
+}
+
+func loadRBACGlobalUsers(ctx context.Context, db gorp.SqlExecutor, rbacGlobal *rbacGlobal) error {
+	q := gorpmapping.NewQuery("SELECT * FROM rbac_global_users WHERE rbac_global_id = $1").Args(rbacGlobal.ID)
+	rbacUserIDS, err := getAllRBACGlobalUsers(ctx, db, q)
+	if err != nil {
+		return err
+	}
+	rbacGlobal.RBACUsersIDs = make([]string, 0, len(rbacUserIDS))
+	for _, rbacUsers := range rbacUserIDS {
+		rbacGlobal.RBACUsersIDs = append(rbacGlobal.RBACUsersIDs, rbacUsers.RbacGlobalUserID)
+	}
+	return nil
+}
+
+func getAllRBACGlobalUsers(ctx context.Context, db gorp.SqlExecutor, q gorpmapping.Query) ([]rbacGlobalUser, error) {
+	var rbacGlobalUsers []rbacGlobalUser
+	if err := gorpmapping.GetAll(ctx, db, q, &rbacGlobalUsers); err != nil {
+		return nil, err
+	}
+
+	usersFiltered := make([]rbacGlobalUser, 0, len(rbacGlobalUsers))
+	for _, rbacUsers := range rbacGlobalUsers {
+		isValid, err := gorpmapping.CheckSignature(rbacUsers, rbacUsers.Signature)
+		if err != nil {
+			return nil, sdk.WrapError(err, "error when checking signature for rbac_global_users %d", rbacUsers.ID)
+		}
+		if !isValid {
+			log.Error(ctx, "rbac.getAllRBACGlobalUsers> rbac_global_users %d data corrupted", rbacUsers.ID)
+			continue
+		}
+		usersFiltered = append(usersFiltered, rbacUsers)
+	}
+	return usersFiltered, nil
+}
diff --git a/engine/api/rbac/dao_rbac_project.go b/engine/api/rbac/dao_rbac_project.go
index 063f281fb..55ae6defb 100644
--- a/engine/api/rbac/dao_rbac_project.go
+++ b/engine/api/rbac/dao_rbac_project.go
@@ -12,30 +12,30 @@ import (
 	"github.com/ovh/cds/sdk"
 )
 
-func insertRbacProject(ctx context.Context, db gorpmapper.SqlExecutorWithTx, rbacProject *rbacProject) error {
+func insertRBACProject(ctx context.Context, db gorpmapper.SqlExecutorWithTx, rbacProject *rbacProject) error {
 	if err := gorpmapping.InsertAndSign(ctx, db, rbacProject); err != nil {
 		return err
 	}
 
 	for _, projectKey := range rbacProject.RBACProjectKeys {
-		if err := insertRbacProjectKey(ctx, db, rbacProject.ID, projectKey); err != nil {
+		if err := insertRBACProjectKey(ctx, db, rbacProject.ID, projectKey); err != nil {
 			return err
 		}
 	}
 	for _, rbUserID := range rbacProject.RBACUsersIDs {
-		if err := insertRbacProjectUser(ctx, db, rbacProject.ID, rbUserID); err != nil {
+		if err := insertRBACProjectUser(ctx, db, rbacProject.ID, rbUserID); err != nil {
 			return err
 		}
 	}
 	for _, rbGroupID := range rbacProject.RBACGroupsIDs {
-		if err := insertRbacProjectGroup(ctx, db, rbacProject.ID, rbGroupID); err != nil {
+		if err := insertRBACProjectGroup(ctx, db, rbacProject.ID, rbGroupID); err != nil {
 			return err
 		}
 	}
 	return nil
 }
 
-func insertRbacProjectKey(ctx context.Context, db gorpmapper.SqlExecutorWithTx, rbacParentID int64, projectKey string) error {
+func insertRBACProjectKey(ctx context.Context, db gorpmapper.SqlExecutorWithTx, rbacParentID int64, projectKey string) error {
 	rpk := rbacProjectKey{
 		RbacProjectID: rbacParentID,
 		ProjectKey:    projectKey,
@@ -46,7 +46,7 @@ func insertRbacProjectKey(ctx context.Context, db gorpmapper.SqlExecutorWithTx,
 	return nil
 }
 
-func insertRbacProjectUser(ctx context.Context, db gorpmapper.SqlExecutorWithTx, rbacProjectID int64, userID string) error {
+func insertRBACProjectUser(ctx context.Context, db gorpmapper.SqlExecutorWithTx, rbacProjectID int64, userID string) error {
 	rgu := rbacProjectUser{
 		RbacProjectID:     rbacProjectID,
 		RbacProjectUserID: userID,
@@ -57,7 +57,7 @@ func insertRbacProjectUser(ctx context.Context, db gorpmapper.SqlExecutorWithTx,
 	return nil
 }
 
-func insertRbacProjectGroup(ctx context.Context, db gorpmapper.SqlExecutorWithTx, rbacProjectID int64, groupID int64) error {
+func insertRBACProjectGroup(ctx context.Context, db gorpmapper.SqlExecutorWithTx, rbacProjectID int64, groupID int64) error {
 	rgu := rbacProjectGroup{
 		RbacProjectID:      rbacProjectID,
 		RbacProjectGroupID: groupID,
@@ -68,7 +68,7 @@ func insertRbacProjectGroup(ctx context.Context, db gorpmapper.SqlExecutorWithTx
 	return nil
 }
 
-func getAllRbacProjects(ctx context.Context, db gorp.SqlExecutor, q gorpmapping.Query) ([]rbacProject, error) {
+func getAllRBACProjects(ctx context.Context, db gorp.SqlExecutor, q gorpmapping.Query) ([]rbacProject, error) {
 	var rbacProjects []rbacProject
 	if err := gorpmapping.GetAll(ctx, db, q, &rbacProjects); err != nil {
 		return nil, err
@@ -81,7 +81,7 @@ func getAllRbacProjects(ctx context.Context, db gorp.SqlExecutor, q gorpmapping.
 			return nil, sdk.WrapError(err, "error when checking signature for rbac_project %d", projectDatas.ID)
 		}
 		if !isValid {
-			log.Error(ctx, "rbac.getAllRbacProjects> rbac_project %d data corrupted", projectDatas.ID)
+			log.Error(ctx, "rbac.getAllRBACProjects> rbac_project %d data corrupted", projectDatas.ID)
 			continue
 		}
 		projectsFiltered = append(projectsFiltered, projectDatas)
@@ -89,7 +89,7 @@ func getAllRbacProjects(ctx context.Context, db gorp.SqlExecutor, q gorpmapping.
 	return projectsFiltered, nil
 }
 
-func loadRbacProjectsByRoleAndIDs(ctx context.Context, db gorp.SqlExecutor, role string, rbacProjectIDs []int64) ([]rbacProject, error) {
+func loadRBACProjectsByRoleAndIDs(ctx context.Context, db gorp.SqlExecutor, role string, rbacProjectIDs []int64) ([]rbacProject, error) {
 	q := gorpmapping.NewQuery(`SELECT * from rbac_project WHERE role = $1 AND id = ANY($2)`).Args(role, pq.Int64Array(rbacProjectIDs))
-	return getAllRbacProjects(ctx, db, q)
+	return getAllRBACProjects(ctx, db, q)
 }
diff --git a/engine/api/rbac/dao_rbac_project_group.go b/engine/api/rbac/dao_rbac_project_group.go
index a125032f2..d5cd22e9f 100644
--- a/engine/api/rbac/dao_rbac_project_group.go
+++ b/engine/api/rbac/dao_rbac_project_group.go
@@ -12,7 +12,7 @@ import (
 	"github.com/ovh/cds/sdk"
 )
 
-func loadRbacProjectGroupsByUserID(ctx context.Context, db gorp.SqlExecutor, userID string) ([]rbacProjectGroup, error) {
+func loadRBACProjectGroupsByUserID(ctx context.Context, db gorp.SqlExecutor, userID string) ([]rbacProjectGroup, error) {
 	groups, err := group.LoadAllByUserID(ctx, db, userID)
 	if err != nil {
 		return nil, err
@@ -21,10 +21,10 @@ func loadRbacProjectGroupsByUserID(ctx context.Context, db gorp.SqlExecutor, use
 	for _, g := range groups {
 		groupIDs = append(groupIDs, g.ID)
 	}
-	return loadRbacProjectGroupsByGroupIDs(ctx, db, groupIDs)
+	return loadRBACProjectGroupsByGroupIDs(ctx, db, groupIDs)
 }
 
-func loadRbacProjectGroupsByGroupIDs(ctx context.Context, db gorp.SqlExecutor, groupIDs []int64) ([]rbacProjectGroup, error) {
+func loadRBACProjectGroupsByGroupIDs(ctx context.Context, db gorp.SqlExecutor, groupIDs []int64) ([]rbacProjectGroup, error) {
 	q := gorpmapping.NewQuery("SELECT * FROM rbac_project_groups WHERE group_id = ANY ($1)").Args(pq.Int64Array(groupIDs))
 	return getAllRBACProjectGroups(ctx, db, q)
 }
diff --git a/engine/api/rbac/dao_rbac_project_key.go b/engine/api/rbac/dao_rbac_project_key.go
index e891d240c..39c4a0508 100644
--- a/engine/api/rbac/dao_rbac_project_key.go
+++ b/engine/api/rbac/dao_rbac_project_key.go
@@ -46,7 +46,7 @@ func loadRBACProjectKeys(ctx context.Context, db gorp.SqlExecutor, rbacProject *
 	return nil
 }
 
-func loadRRBACProjectKeys(ctx context.Context, db gorp.SqlExecutor, rbacProjectIDs []int64) ([]rbacProjectKey, error) {
+func loadAllRBACProjectKeys(ctx context.Context, db gorp.SqlExecutor, rbacProjectIDs []int64) ([]rbacProjectKey, error) {
 	query := gorpmapping.NewQuery(`SELECT * FROM rbac_project_keys WHERE rbac_project_id = ANY($1)`).Args(pq.Int64Array(rbacProjectIDs))
 	return getAllRBACProjectKeys(ctx, db, query)
 }
@@ -63,32 +63,28 @@ func HasRoleOnProjectAndUserID(ctx context.Context, db gorp.SqlExecutor, role st
 
 func LoadProjectKeysByRoleAndUserID(ctx context.Context, db gorp.SqlExecutor, role string, userID string) ([]string, error) {
 	// Get rbac_project_groups
-	rbacProjectGroups, err := loadRbacProjectGroupsByUserID(ctx, db, userID)
+	rbacProjectGroups, err := loadRBACProjectGroupsByUserID(ctx, db, userID)
 	if err != nil {
 		return nil, err
 	}
 	// Get rbac_project_users
-	rbacProjectUsers, err := loadRbacProjectUsersByUserID(ctx, db, userID)
+	rbacProjectUsers, err := loadRBACProjectUsersByUserID(ctx, db, userID)
 	if err != nil {
 		return nil, err
 	}
 
 	// Deduplicate rbac_project.id
-	mapRbacProjectID := make(map[int64]struct{})
-	rbacProjectIDs := make([]int64, 0)
+	rbacProjectIDs := make(sdk.Int64Slice, 0)
 	for _, rpg := range rbacProjectGroups {
-		mapRbacProjectID[rpg.RbacProjectID] = struct{}{}
 		rbacProjectIDs = append(rbacProjectIDs, rpg.RbacProjectID)
 	}
 	for _, rpu := range rbacProjectUsers {
-		if _, has := mapRbacProjectID[rpu.RbacProjectID]; !has {
-			mapRbacProjectID[rpu.RbacProjectID] = struct{}{}
-			rbacProjectIDs = append(rbacProjectIDs, rpu.RbacProjectID)
-		}
+		rbacProjectIDs = append(rbacProjectIDs, rpu.RbacProjectID)
 	}
+	rbacProjectIDs.Unique()
 
 	// Get rbac_project
-	rbacProjects, err := loadRbacProjectsByRoleAndIDs(ctx, db, role, rbacProjectIDs)
+	rbacProjects, err := loadRBACProjectsByRoleAndIDs(ctx, db, role, rbacProjectIDs)
 	if err != nil {
 		return nil, err
 	}
@@ -98,7 +94,7 @@ func LoadProjectKeysByRoleAndUserID(ctx context.Context, db gorp.SqlExecutor, ro
 	for _, rp := range rbacProjects {
 		rbacProjectIDs = append(rbacProjectIDs, rp.ID)
 	}
-	rbacProjectKeys, err := loadRRBACProjectKeys(ctx, db, rbacProjectIDs)
+	rbacProjectKeys, err := loadAllRBACProjectKeys(ctx, db, rbacProjectIDs)
 	if err != nil {
 		return nil, err
 	}
diff --git a/engine/api/rbac/dao_rbac_project_user.go b/engine/api/rbac/dao_rbac_project_user.go
index 0ab5890c2..42574471c 100644
--- a/engine/api/rbac/dao_rbac_project_user.go
+++ b/engine/api/rbac/dao_rbac_project_user.go
@@ -10,7 +10,7 @@ import (
 	"github.com/ovh/cds/sdk"
 )
 
-func loadRbacProjectUsersByUserID(ctx context.Context, db gorp.SqlExecutor, userID string) ([]rbacProjectUser, error) {
+func loadRBACProjectUsersByUserID(ctx context.Context, db gorp.SqlExecutor, userID string) ([]rbacProjectUser, error) {
 	q := gorpmapping.NewQuery("SELECT * FROM rbac_project_users WHERE user_id = $1").Args(userID)
 	return getAllRBACProjectUsers(ctx, db, q)
 }
diff --git a/engine/api/rbac/loader.go b/engine/api/rbac/loader.go
index aa0de1ba1..079ddfd9a 100644
--- a/engine/api/rbac/loader.go
+++ b/engine/api/rbac/loader.go
@@ -16,25 +16,25 @@ type LoadOptionFunc func(context.Context, gorp.SqlExecutor, *rbac) error
 // LoadOptions provides all options on rbac loads functions
 var LoadOptions = struct {
 	Default         LoadOptionFunc
-	LoadRbacGlobal  LoadOptionFunc
-	LoadRbacProject LoadOptionFunc
+	LoadRBACGlobal  LoadOptionFunc
+	LoadRBACProject LoadOptionFunc
 }{
 	Default:         loadDefault,
-	LoadRbacGlobal:  loadRbacGlobal,
-	LoadRbacProject: loadRbacProject,
+	LoadRBACGlobal:  loadRBACGlobal,
+	LoadRBACProject: loadRBACProject,
 }
 
 func loadDefault(ctx context.Context, db gorp.SqlExecutor, rbac *rbac) error {
-	if err := loadRbacGlobal(ctx, db, rbac); err != nil {
+	if err := loadRBACGlobal(ctx, db, rbac); err != nil {
 		return err
 	}
-	if err := loadRbacProject(ctx, db, rbac); err != nil {
+	if err := loadRBACProject(ctx, db, rbac); err != nil {
 		return err
 	}
 	return nil
 }
 
-func loadRbacProject(ctx context.Context, db gorp.SqlExecutor, rbac *rbac) error {
+func loadRBACProject(ctx context.Context, db gorp.SqlExecutor, rbac *rbac) error {
 	query := "SELECT * FROM rbac_project WHERE rbac_id = $1"
 	var rbacProjects []rbacProject
 	if err := gorpmapping.GetAll(ctx, db, gorpmapping.NewQuery(query).Args(rbac.ID), &rbacProjects); err != nil {
@@ -67,7 +67,7 @@ func loadRbacProject(ctx context.Context, db gorp.SqlExecutor, rbac *rbac) error
 	return nil
 }
 
-func loadRbacGlobal(ctx context.Context, db gorp.SqlExecutor, rbac *rbac) error {
+func loadRBACGlobal(ctx context.Context, db gorp.SqlExecutor, rbac *rbac) error {
 	query := "SELECT * FROM rbac_global WHERE rbac_id = $1"
 	var rbacGbl []rbacGlobal
 	if err := gorpmapping.GetAll(ctx, db, gorpmapping.NewQuery(query).Args(rbac.ID), &rbacGbl); err != nil {
@@ -81,13 +81,13 @@ func loadRbacGlobal(ctx context.Context, db gorp.SqlExecutor, rbac *rbac) error
 			return sdk.WrapError(err, "error when checking signature for rbac_global %d", rg.ID)
 		}
 		if !isValid {
-			log.Error(ctx, "rbac.loadRbacGlobal> rbac_global %d data corrupted", rg.ID)
+			log.Error(ctx, "rbac.loadRBACGlobal> rbac_global %d data corrupted", rg.ID)
 			continue
 		}
-		if err := getAllRBACGlobalUsers(ctx, db, rg); err != nil {
+		if err := loadRBACGlobalUsers(ctx, db, rg); err != nil {
 			return err
 		}
-		if err := getAllRBACGlobalGroups(ctx, db, rg); err != nil {
+		if err := loadRBACGlobalGroups(ctx, db, rg); err != nil {
 			return err
 		}
 		rbac.Globals = append(rbac.Globals, rg.RBACGlobal)
diff --git a/engine/api/rbac/rbac.go b/engine/api/rbac/rbac.go
index 9b9089c8e..edb433de5 100644
--- a/engine/api/rbac/rbac.go
+++ b/engine/api/rbac/rbac.go
@@ -11,32 +11,34 @@ import (
 
 func FillWithIDs(ctx context.Context, db gorp.SqlExecutor, r *sdk.RBAC) error {
 	// Check existing permission
-	rbacDB, err := LoadRbacByName(ctx, db, r.Name)
+	rbacDB, err := LoadRBACByName(ctx, db, r.Name)
 	if err != nil {
 		if !sdk.ErrorIs(err, sdk.ErrNotFound) {
 			return err
 		}
 	}
-	r.ID = rbacDB.ID
+	if rbacDB != nil {
+		r.ID = rbacDB.ID
+	}
 
 	userCache := make(map[string]string)
 	groupCache := make(map[string]int64)
 	for gID := range r.Globals {
 		rbacGbl := &r.Globals[gID]
-		if err := fillRbacGlobalWithID(ctx, db, rbacGbl, userCache, groupCache); err != nil {
+		if err := fillRBACGlobalWithID(ctx, db, rbacGbl, userCache, groupCache); err != nil {
 			return err
 		}
 	}
 	for pID := range r.Projects {
 		rbacPrj := &r.Projects[pID]
-		if err := fillRbacProjectWithID(ctx, db, rbacPrj, userCache, groupCache); err != nil {
+		if err := fillRBACProjectWithID(ctx, db, rbacPrj, userCache, groupCache); err != nil {
 			return err
 		}
 	}
 	return nil
 }
 
-func fillRbacProjectWithID(ctx context.Context, db gorp.SqlExecutor, rbacPrj *sdk.RBACProject, userCache map[string]string, groupCache map[string]int64) error {
+func fillRBACProjectWithID(ctx context.Context, db gorp.SqlExecutor, rbacPrj *sdk.RBACProject, userCache map[string]string, groupCache map[string]int64) error {
 	rbacPrj.RBACUsersIDs = make([]string, 0, len(rbacPrj.RBACUsersName))
 	for _, userName := range rbacPrj.RBACUsersName {
 		userID := userCache[userName]
@@ -66,7 +68,7 @@ func fillRbacProjectWithID(ctx context.Context, db gorp.SqlExecutor, rbacPrj *sd
 	return nil
 }
 
-func fillRbacGlobalWithID(ctx context.Context, db gorp.SqlExecutor, rbacGbl *sdk.RBACGlobal, userCache map[string]string, groupCache map[string]int64) error {
+func fillRBACGlobalWithID(ctx context.Context, db gorp.SqlExecutor, rbacGbl *sdk.RBACGlobal, userCache map[string]string, groupCache map[string]int64) error {
 	rbacGbl.RBACUsersIDs = make([]string, 0, len(rbacGbl.RBACUsersName))
 	for _, rbacUserName := range rbacGbl.RBACUsersName {
 		userID := userCache[rbacUserName]
diff --git a/engine/api/rbac/rule_global.go b/engine/api/rbac/rule_global.go
new file mode 100644
index 000000000..380187a25
--- /dev/null
+++ b/engine/api/rbac/rule_global.go
@@ -0,0 +1,36 @@
+package rbac
+
+import (
+	"context"
+
+	"github.com/go-gorp/gorp"
+	"github.com/rockbears/log"
+
+	"github.com/ovh/cds/engine/cache"
+	"github.com/ovh/cds/sdk"
+)
+
+func hasGlobalRole(ctx context.Context, auth *sdk.AuthConsumer, _ cache.Store, db gorp.SqlExecutor, role string) error {
+	if auth == nil {
+		return sdk.WithStack(sdk.ErrForbidden)
+	}
+
+	hasRole, err := HasGlobalRole(ctx, db, role, auth.AuthentifiedUser.ID)
+	if err != nil {
+		return err
+	}
+
+	log.RegisterField(LogFieldRole)
+	ctx = context.WithValue(ctx, LogFieldRole, role)
+	log.Info(ctx, "hasRole:%t", hasRole)
+
+	if !hasRole {
+		return sdk.WithStack(sdk.ErrForbidden)
+	}
+	return nil
+}
+
+// PermissionManage return nil if the current AuthConsumer have the RoleManage on current project KEY
+func PermissionManage(ctx context.Context, auth *sdk.AuthConsumer, store cache.Store, db gorp.SqlExecutor, _ map[string]string) error {
+	return hasGlobalRole(ctx, auth, store, db, sdk.RoleManagePermission)
+}
diff --git a/engine/api/v2_rbac.go b/engine/api/v2_rbac.go
new file mode 100644
index 000000000..9e7a3c74b
--- /dev/null
+++ b/engine/api/v2_rbac.go
@@ -0,0 +1,56 @@
+package api
+
+import (
+	"context"
+	"net/http"
+
+	"github.com/ovh/cds/sdk"
+
+	"github.com/ovh/cds/engine/api/rbac"
+	"github.com/ovh/cds/engine/service"
+)
+
+func (api *API) postImportRbacHandler() ([]service.RbacChecker, service.Handler) {
+	return service.RBAC(rbac.PermissionManage),
+		func(ctx context.Context, w http.ResponseWriter, req *http.Request) error {
+			force := service.FormBool(req, "force")
+
+			var rbacRule sdk.RBAC
+			if err := service.UnmarshalRequest(ctx, req, &rbacRule); err != nil {
+				return err
+			}
+
+			existingRule, err := rbac.LoadRBACByName(ctx, api.mustDB(), rbacRule.Name)
+			if err != nil && !sdk.ErrorIs(err, sdk.ErrNotFound) {
+				return err
+			}
+
+			if err := rbac.FillWithIDs(ctx, api.mustDB(), &rbacRule); err != nil {
+				return err
+			}
+
+			tx, err := api.mustDB().Begin()
+			if err != nil {
+				return sdk.WithStack(err)
+			}
+			defer tx.Rollback() // nolint
+
+			if existingRule != nil && !force {
+				return sdk.NewErrorFrom(sdk.ErrForbidden, "unable to override existing permission")
+			}
+			if existingRule != nil {
+				if err := rbac.Delete(ctx, tx, *existingRule); err != nil {
+					return err
+				}
+			}
+
+			if err := rbac.Insert(ctx, tx, &rbacRule); err != nil {
+				return err
+			}
+
+			if err := tx.Commit(); err != nil {
+				return sdk.WithStack(err)
+			}
+			return service.WriteMarshal(w, req, nil, http.StatusCreated)
+		}
+}
diff --git a/engine/api/workflow/workflow_run_results.go b/engine/api/workflow/workflow_run_results.go
index a6de2000f..f168a6a42 100644
--- a/engine/api/workflow/workflow_run_results.go
+++ b/engine/api/workflow/workflow_run_results.go
@@ -9,6 +9,7 @@ import (
 	"time"
 
 	"github.com/go-gorp/gorp"
+	"github.com/lib/pq"
 	"github.com/rockbears/log"
 
 	art "github.com/ovh/cds/contrib/integrations/artifactory"
@@ -175,7 +176,7 @@ func AddResult(ctx context.Context, db *gorp.DbMap, store cache.Store, wr *sdk.W
 	return sdk.WithStack(store.Delete(cacheKey))
 }
 
-// Check validity of the request + complete runResuklt with md5,size,type
+// Check validity of the request + complete runResult with md5,size,type
 func verifyAddResultArtifactManager(ctx context.Context, db gorp.SqlExecutor, store cache.Store, wr *sdk.WorkflowRun, newRunResult *sdk.WorkflowRunResult) (string, error) {
 	artNewResult, err := newRunResult.GetArtifactManager()
 	if err != nil {
@@ -369,6 +370,11 @@ func getAll(ctx context.Context, db gorp.SqlExecutor, query gorpmapping.Query) (
 	return results, nil
 }
 
+func LoadRunResultsByRunIDFilterByIDs(ctx context.Context, db gorp.SqlExecutor, runID int64, resultIDs ...string) (sdk.WorkflowRunResults, error) {
+	query := gorpmapping.NewQuery("SELECT * FROM workflow_run_result WHERE workflow_run_id = $1 AND id = ANY($2) ORDER BY sub_num DESC").Args(runID, pq.StringArray(resultIDs))
+	return getAll(ctx, db, query)
+}
+
 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)
@@ -413,8 +419,19 @@ func ResyncWorkflowRunResultsRoutine(ctx context.Context, DBFunc func() *gorp.Db
 					continue
 				}
 				for _, id := range ids {
-					if err := SyncRunResultArtifactManagerByRunID(ctx, DBFunc(), id); err != nil {
+					tx, err := DBFunc().Begin()
+					if err != nil {
+						log.ErrorWithStackTrace(ctx, err)
+						continue
+					}
+					if err := SyncRunResultArtifactManagerByRunID(ctx, tx, id); err != nil {
+						log.ErrorWithStackTrace(ctx, err)
+						tx.Rollback()
+						continue
+					}
+					if err := tx.Commit(); err != nil {
 						log.ErrorWithStackTrace(ctx, err)
+						tx.Rollback()
 						continue
 					}
 				}
@@ -432,14 +449,17 @@ func FindOldestWorkflowRunsWithResultToSync(ctx context.Context, dbmap *gorp.DbM
 	return results, nil
 }
 
-func SyncRunResultArtifactManagerByRunID(ctx context.Context, dbmap *gorp.DbMap, id int64) error {
-	log.Info(ctx, "sync run result for workflow run id %d", id)
-	db, err := dbmap.Begin()
-	if err != nil {
-		return sdk.WithStack(err)
+func UpdateRunResult(ctx context.Context, db gorp.SqlExecutor, result *sdk.WorkflowRunResult) error {
+	dbResult := dbRunResult(*result)
+	log.Debug(ctx, "updating run result %s: %v", dbResult.ID, result)
+	if err := gorpmapping.Update(db, &dbResult); err != nil {
+		return err
 	}
-	defer db.Rollback() // nolint
+	return nil
+}
 
+func SyncRunResultArtifactManagerByRunID(ctx context.Context, db gorp.SqlExecutor, id int64) error {
+	log.Info(ctx, "sync run result for workflow run id %d", id)
 	wr, err := LoadAndLockRunByID(ctx, db, id, LoadRunOptions{})
 	if err != nil {
 		return err
@@ -459,12 +479,12 @@ func SyncRunResultArtifactManagerByRunID(ctx context.Context, dbmap *gorp.DbMap,
 		result := allRunResults[i]
 		// If the result is not an artifact manager, we do nothing but we consider it as synchronized
 		if result.Type != sdk.WorkflowRunResultTypeArtifactManager {
-			dbResult := dbRunResult(result)
-			dbResult.DataSync = new(sdk.WorkflowRunResultSync)
-			dbResult.DataSync.Link = ""
-			dbResult.DataSync.Sync = true
-			log.Debug(ctx, "updating run result %s", dbResult.ID)
-			if err := gorpmapping.Update(db, &dbResult); err != nil {
+			if result.DataSync == nil {
+				result.DataSync = new(sdk.WorkflowRunResultSync)
+			}
+			result.DataSync.Link = ""
+			result.DataSync.Sync = true
+			if err := UpdateRunResult(ctx, db, &result); err != nil {
 				return err
 			}
 		} else {
@@ -474,9 +494,6 @@ func SyncRunResultArtifactManagerByRunID(ctx context.Context, dbmap *gorp.DbMap,
 
 	// Nothing more to do with artifact manager
 	if len(runResults) == 0 {
-		if err := db.Commit(); err != nil {
-			return err
-		}
 		return nil
 	}
 
@@ -495,30 +512,27 @@ func SyncRunResultArtifactManagerByRunID(ctx context.Context, dbmap *gorp.DbMap,
 		for i := range runResults {
 			result := runResults[i]
 			// If the result is not an artifact manager, we do nothing but we consider it as synchronized
-			dbResult := dbRunResult(result)
-			dbResult.DataSync = new(sdk.WorkflowRunResultSync)
-			dbResult.DataSync.Sync = false
-			dbResult.DataSync.Error = err.Error()
-			log.Debug(ctx, "updating run result %s", dbResult.ID)
-			if err := gorpmapping.Update(db, &dbResult); err != nil {
+			if result.DataSync == nil {
+				result.DataSync = new(sdk.WorkflowRunResultSync)
+			}
+			result.DataSync.Sync = false
+			result.DataSync.Error = err.Error()
+			if err := UpdateRunResult(ctx, db, &result); err != nil {
 				return err
 			}
 		}
-		if err := db.Commit(); err != nil {
-			return err
-		}
 		return nil
 	}
 
 	log.Info(ctx, "artifact manager %q found for workflow run", artifactManagerInteg.ProjectIntegration.Name)
 
 	var (
-		rtName                = artifactManagerInteg.ProjectIntegration.Config[sdk.ArtifactoryConfigPlatform].Value
-		rtURL                 = artifactManagerInteg.ProjectIntegration.Config[sdk.ArtifactoryConfigURL].Value
-		buildInfoPrefix       = artifactManagerInteg.ProjectIntegration.Config[sdk.ArtifactoryConfigBuildInfoPrefix].Value
-		tokenName             = artifactManagerInteg.ProjectIntegration.Config[sdk.ArtifactoryConfigTokenName].Value
-		lowMaturitySuffix     = artifactManagerInteg.ProjectIntegration.Config[sdk.ArtifactoryConfigPromotionLowMaturity].Value
-		artifactoryProjectKey = artifactManagerInteg.ProjectIntegration.Config[sdk.ArtifactoryConfigProjectKey].Value
+		rtName                      = artifactManagerInteg.ProjectIntegration.Config[sdk.ArtifactoryConfigPlatform].Value
+		rtURL                       = artifactManagerInteg.ProjectIntegration.Config[sdk.ArtifactoryConfigURL].Value
+		buildInfoPrefix             = artifactManagerInteg.ProjectIntegration.Config[sdk.ArtifactoryConfigBuildInfoPrefix].Value
+		tokenName                   = artifactManagerInteg.ProjectIntegration.Config[sdk.ArtifactoryConfigTokenName].Value
+		lowMaturitySuffixFromConfig = artifactManagerInteg.ProjectIntegration.Config[sdk.ArtifactoryConfigPromotionLowMaturity].Value
+		artifactoryProjectKey       = artifactManagerInteg.ProjectIntegration.Config[sdk.ArtifactoryConfigProjectKey].Value
 	)
 
 	// Load the token from secrets
@@ -539,18 +553,15 @@ func SyncRunResultArtifactManagerByRunID(ctx context.Context, dbmap *gorp.DbMap,
 		log.ErrorWithStackTrace(ctx, err)
 		for i := range runResults {
 			result := runResults[i]
-			dbResult := dbRunResult(result)
-			dbResult.DataSync = new(sdk.WorkflowRunResultSync)
-			dbResult.DataSync.Sync = false
-			dbResult.DataSync.Error = err.Error()
-			log.Debug(ctx, "updating run result %s", dbResult.ID)
-			if err := gorpmapping.Update(db, &dbResult); err != nil {
+			if result.DataSync == nil {
+				result.DataSync = new(sdk.WorkflowRunResultSync)
+			}
+			result.DataSync.Sync = false
+			result.DataSync.Error = err.Error()
+			if err := UpdateRunResult(ctx, db, &result); err != nil {
 				return err
 			}
 		}
-		if err := db.Commit(); err != nil {
-			return err
-		}
 		return nil
 	}
 
@@ -591,6 +602,27 @@ func SyncRunResultArtifactManagerByRunID(ctx context.Context, dbmap *gorp.DbMap,
 	nodeRunURL := parameters["cds.ui.pipeline.run"][0]
 	runURL := nodeRunURL[0:strings.Index(nodeRunURL, "/node/")]
 
+	var lowMaturitySuffix string
+	for i := range runResults {
+		log.Debug(ctx, "checking for earlier maturity in %+v", runResults[i].DataSync)
+		if runResults[i].DataSync != nil {
+			p := runResults[i].DataSync.LatestPromotionOrRelease()
+			if p != nil {
+				if lowMaturitySuffix != "" {
+					if p.FromMaturity != lowMaturitySuffix {
+						return sdk.NewErrorFrom(sdk.ErrWrongRequest, "several maturities (%q, %q) detected among your artifacts", p.ToMaturity, lowMaturitySuffix)
+					}
+				}
+				log.Debug(ctx, "low maturity is %+v", p.FromMaturity)
+				lowMaturitySuffix = p.FromMaturity
+				// we don't break the loop to let us to check all the maturities
+			}
+		}
+	}
+	if lowMaturitySuffix == "" {
+		lowMaturitySuffix = lowMaturitySuffixFromConfig
+	}
+
 	buildInfoRequest, err := art.PrepareBuildInfo(ctx, artifactClient, art.BuildInfoRequest{
 		BuildInfoPrefix:   buildInfoPrefix,
 		ProjectKey:        wr.Workflow.ProjectKey,
@@ -630,15 +662,15 @@ func SyncRunResultArtifactManagerByRunID(ctx context.Context, dbmap *gorp.DbMap,
 	}
 
 	for _, result := range runResults {
-		dbResult := dbRunResult(result)
-		dbResult.DataSync = new(sdk.WorkflowRunResultSync)
-		dbResult.DataSync.Link = buildInfoRequest.Name + "/" + buildInfoRequest.Number
-		dbResult.DataSync.Sync = true
-		log.Debug(ctx, "updating run result %s", dbResult.ID)
-		if err := gorpmapping.Update(db, &dbResult); err != nil {
+		if result.DataSync == nil {
+			result.DataSync = new(sdk.WorkflowRunResultSync)
+		}
+		result.DataSync.Link = buildInfoRequest.Name + "/" + buildInfoRequest.Number
+		result.DataSync.Sync = true
+		if err := UpdateRunResult(ctx, db, &result); err != nil {
 			return err
 		}
 	}
 
-	return sdk.WithStack(db.Commit())
+	return nil
 }
diff --git a/engine/api/workflow_queue.go b/engine/api/workflow_queue.go
index ee1940927..6a3f26d7f 100644
--- a/engine/api/workflow_queue.go
+++ b/engine/api/workflow_queue.go
@@ -1121,6 +1121,67 @@ func (api *API) workflowRunResultCheckUploadHandler() service.Handler {
 	}
 }
 
+func (api *API) workflowRunResultPromoteHandler() service.Handler {
+	return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
+		if !isWorker(ctx) {
+			return sdk.WrapError(sdk.ErrForbidden, "only workers can call this route")
+		}
+
+		jobID, err := requestVarInt(r, "permJobID")
+		if err != nil {
+			return err
+		}
+
+		var promotionRequest sdk.WorkflowRunResultPromotionRequest
+		if err := service.UnmarshalBody(r, &promotionRequest); err != nil {
+			return sdk.WithStack(err)
+		}
+
+		tx, err := api.mustDBWithCtx(ctx).Begin()
+		if err != nil {
+			return sdk.WithStack(err)
+		}
+		defer tx.Rollback()
+
+		wr, err := workflow.LoadRunByJobID(ctx, tx, jobID, workflow.LoadRunOptions{DisableDetailledNodeRun: true})
+		if err != nil {
+			return err
+		}
+
+		runResults, err := workflow.LoadRunResultsByRunIDFilterByIDs(ctx, tx, wr.ID, promotionRequest.IDs...)
+		if err != nil {
+			return err
+		}
+
+		if len(runResults) == 0 {
+			return sdk.NewErrorFrom(sdk.ErrWrongRequest, "unable to find any run results among %v", promotionRequest.IDs)
+		}
+
+		promotionRequest.WorkflowRunResultPromotion.Date = time.Now()
+		for i := range runResults {
+			r := &runResults[i]
+			for _, id := range promotionRequest.IDs {
+				if id == r.ID {
+					if r.DataSync == nil {
+						r.DataSync = new(sdk.WorkflowRunResultSync)
+					}
+					log.Debug(ctx, "adding promotion data: %+v", promotionRequest)
+					r.DataSync.Promotions = append(r.DataSync.Promotions, promotionRequest.WorkflowRunResultPromotion)
+				}
+			}
+			if err := workflow.UpdateRunResult(ctx, tx, r); err != nil {
+				return err
+			}
+		}
+
+		if err := workflow.SyncRunResultArtifactManagerByRunID(ctx, tx, wr.ID); err != nil {
+			return err
+		}
+
+		return sdk.WithStack(tx.Commit())
+	}
+}
+
 func (api *API) workflowRunResultReleaseHandler() service.Handler {
 	return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
 		if !isWorker(ctx) {
@@ -1132,16 +1193,53 @@ func (api *API) workflowRunResultReleaseHandler() service.Handler {
 			return err
 		}
 
-		wr, err := workflow.LoadRunByJobID(ctx, api.mustDBWithCtx(ctx), jobID, workflow.LoadRunOptions{DisableDetailledNodeRun: true})
+		var releaseRequest sdk.WorkflowRunResultPromotionRequest
+		if err := service.UnmarshalBody(r, &releaseRequest); err != nil {
+			return sdk.WithStack(err)
+		}
+
+		tx, err := api.mustDBWithCtx(ctx).Begin()
+		if err != nil {
+			return sdk.WithStack(err)
+		}
+		defer tx.Rollback()
+
+		wr, err := workflow.LoadRunByJobID(ctx, tx, jobID, workflow.LoadRunOptions{DisableDetailledNodeRun: true})
 		if err != nil {
 			return err
 		}
 
-		if err := workflow.SyncRunResultArtifactManagerByRunID(ctx, api.mustDBWithCtx(ctx), wr.ID); err != nil {
+		runResults, err := workflow.LoadRunResultsByRunIDFilterByIDs(ctx, tx, wr.ID, releaseRequest.IDs...)
+		if err != nil {
 			return err
 		}
 
-		return nil
+		if len(runResults) == 0 {
+			return sdk.NewErrorFrom(sdk.ErrWrongRequest, "unable to find any run results among %v", releaseRequest.IDs)
+		}
+
+		releaseRequest.WorkflowRunResultPromotion.Date = time.Now()
+		for i := range runResults {
+			r := &runResults[i]
+			for _, id := range releaseRequest.IDs {
+				if id == r.ID {
+					if r.DataSync == nil {
+						r.DataSync = new(sdk.WorkflowRunResultSync)
+					}
+					log.Debug(ctx, "adding release data: %+v", releaseRequest)
+					r.DataSync.Releases = append(r.DataSync.Releases, releaseRequest.WorkflowRunResultPromotion)
+				}
+			}
+			if err := workflow.UpdateRunResult(ctx, tx, r); err != nil {
+				return err
+			}
+		}
+
+		if err := workflow.SyncRunResultArtifactManagerByRunID(ctx, tx, wr.ID); err != nil {
+			return err
+		}
+
+		return sdk.WithStack(tx.Commit())
 	}
 }
 
diff --git a/engine/worker/internal/action/builtin_promote.go b/engine/worker/internal/action/builtin_promote.go
index ad0d2577b..7da4e97e3 100644
--- a/engine/worker/internal/action/builtin_promote.go
+++ b/engine/worker/internal/action/builtin_promote.go
@@ -7,6 +7,7 @@ import (
 	"strings"
 
 	"github.com/golang/protobuf/ptypes/empty"
+	"github.com/rockbears/log"
 
 	"github.com/ovh/cds/engine/worker/pkg/workerruntime"
 	"github.com/ovh/cds/sdk"
@@ -14,6 +15,24 @@ import (
 )
 
 func RunPromote(ctx context.Context, wk workerruntime.Runtime, a sdk.Action, _ []sdk.Variable) (sdk.Result, error) {
+	jobID, err := workerruntime.JobID(ctx)
+	if err != nil {
+		return sdk.Result{Status: sdk.StatusFail}, err
+	}
+
+	promotedRunResultIDs, err := RunReleaseActionPrepare(ctx, wk, a)
+	if err != nil {
+		return sdk.Result{Status: sdk.StatusFail}, err
+	}
+
+	log.Info(ctx, "RunPromote> preparing run result %+v for promotion", promotedRunResultIDs)
+	if err := wk.Client().QueueWorkflowRunResultsPromote(ctx,
+		jobID, promotedRunResultIDs,
+		sdk.ParameterValue(a.Parameters, "srcMaturity"), sdk.ParameterValue(a.Parameters, "destMaturity"),
+	); err != nil {
+		return sdk.Result{Status: sdk.StatusFail}, err
+	}
+
 	pfName := sdk.ParameterFind(wk.Parameters(), "cds.integration.artifact_manager")
 	if pfName == nil {
 		return sdk.Result{}, errors.New("unable to retrieve artifact manager integration... Aborting")
diff --git a/engine/worker/internal/action/builtin_release.go b/engine/worker/internal/action/builtin_release.go
index 0d8c777a6..1ea84b5cf 100644
--- a/engine/worker/internal/action/builtin_release.go
+++ b/engine/worker/internal/action/builtin_release.go
@@ -2,11 +2,13 @@ package action
 
 import (
 	"context"
-	"errors"
 	"fmt"
+	"regexp"
+	"strconv"
 	"strings"
 
 	"github.com/golang/protobuf/ptypes/empty"
+	"github.com/pkg/errors"
 	"github.com/rockbears/log"
 
 	"github.com/ovh/cds/engine/worker/pkg/workerruntime"
@@ -14,14 +16,72 @@ import (
 	"github.com/ovh/cds/sdk/grpcplugin/integrationplugin"
 )
 
+func RunReleaseActionPrepare(ctx context.Context, wk workerruntime.Runtime, a sdk.Action) ([]string, error) {
+	artifactList := sdk.ParameterValue(a.Parameters, "artifacts")
+	artSplitted := strings.Split(artifactList, ",")
+	artRegs := make([]*regexp.Regexp, 0, len(artSplitted))
+	for _, arti := range artSplitted {
+		r, err := regexp.Compile(arti)
+		if err != nil {
+			return nil, sdk.Errorf("unable to compile regexp in artifact list: %v", err)
+		}
+		artRegs = append(artRegs, r)
+	}
+
+	projectKey := sdk.ParameterValue(wk.Parameters(), "cds.project")
+	wName := sdk.ParameterValue(wk.Parameters(), "cds.workflow")
+	runNumberString := sdk.ParameterValue(wk.Parameters(), "cds.run.number")
+	runNumber, err := strconv.ParseInt(runNumberString, 10, 64)
+	if err != nil {
+		newError := sdk.NewError(sdk.ErrWrongRequest, fmt.Errorf("cannot parse '%s' as run number: %s", runNumberString, err))
+		return nil, newError
+	}
+	runResult, err := wk.Client().WorkflowRunResultsList(ctx, projectKey, wName, runNumber)
+	if err != nil {
+		return nil, sdk.Errorf("unable to get run result list: %v", err)
+	}
+
+	promotedRunResultIDs := make([]string, 0)
+	for _, r := range runResult {
+		// static-file type does not need to be released
+		if r.Type == sdk.WorkflowRunResultTypeStaticFile {
+			continue
+		}
+		rData, err := r.GetArtifactManager()
+		if err != nil {
+			return nil, sdk.Errorf("unable to read artifacts data: %v", err)
+		}
+		skip := true
+		for _, reg := range artRegs {
+			if reg.MatchString(rData.Name) {
+				skip = false
+				break
+			}
+		}
+		if !skip {
+			promotedRunResultIDs = append(promotedRunResultIDs, r.ID)
+		}
+	}
+
+	return promotedRunResultIDs, nil
+}
+
 func RunRelease(ctx context.Context, wk workerruntime.Runtime, a sdk.Action, _ []sdk.Variable) (sdk.Result, error) {
 	jobID, err := workerruntime.JobID(ctx)
 	if err != nil {
 		return sdk.Result{Status: sdk.StatusFail}, err
 	}
 
-	log.Debug(ctx, "RunRelease> preparing run result for release ")
-	if err := wk.Client().QueueWorkflowRunResultsRelease(ctx, jobID); err != nil {
+	promotedRunResultIDs, err := RunReleaseActionPrepare(ctx, wk, a)
+	if err != nil {
+		return sdk.Result{Status: sdk.StatusFail}, err
+	}
+
+	log.Info(ctx, "RunRelease> preparing run result %+v for release", promotedRunResultIDs)
+	if err := wk.Client().QueueWorkflowRunResultsRelease(ctx,
+		jobID, promotedRunResultIDs,
+		sdk.ParameterValue(a.Parameters, "srcMaturity"), sdk.ParameterValue(a.Parameters, "destMaturity"),
+	); err != nil {
 		return sdk.Result{Status: sdk.StatusFail}, err
 	}
 
diff --git a/engine/worker/internal/handler_cache.go b/engine/worker/internal/handler_cache.go
index ced93fe85..ef20a6f1c 100644
--- a/engine/worker/internal/handler_cache.go
+++ b/engine/worker/internal/handler_cache.go
@@ -122,7 +122,7 @@ func cachePushHandler(ctx context.Context, wk *CurrentWorker) http.HandlerFunc {
 			writeError(w, r, err)
 			return
 		}
-		duration, err := wk.client.CDNItemUpload(ctx, wk.cdnHttpAddr, sig, wk.BaseDir(), tarF.Name())
+		duration, err := wk.client.CDNItemUpload(ctx, wk.cfg.CDNEndpoint, sig, wk.BaseDir(), tarF.Name())
 		if err != nil {
 			log.Error(ctx, "%v", err)
 			writeError(w, r, err)
@@ -177,7 +177,7 @@ func cachePullHandler(ctx context.Context, wk *CurrentWorker) http.HandlerFunc {
 			writeError(w, req, err)
 			return
 		}
-		if err := wk.client.CDNItemDownload(ctx, wk.cdnHttpAddr, items.Items[0].APIRefHash, sdk.CDNTypeItemWorkerCache, items.Items[0].MD5, f); err != nil {
+		if err := wk.client.CDNItemDownload(ctx, wk.cfg.CDNEndpoint, items.Items[0].APIRefHash, sdk.CDNTypeItemWorkerCache, items.Items[0].MD5, f); err != nil {
 			_ = f.Close()
 			err = sdk.Error{
 				Message: "worker cache pull > Cannot pull cache: " + err.Error(),
diff --git a/engine/worker/internal/take.go b/engine/worker/internal/take.go
index 1832bdab8..48e2de937 100644
--- a/engine/worker/internal/take.go
+++ b/engine/worker/internal/take.go
@@ -46,8 +46,6 @@ func (w *CurrentWorker) Take(ctx context.Context, job sdk.WorkflowNodeJobRun) er
 	// Reset build variables
 	w.currentJob.newVariables = nil
 
-	w.cdnHttpAddr = info.CDNHttpAddr
-
 	secretKey := make([]byte, 32)
 	if _, err := base64.StdEncoding.Decode(secretKey, []byte(info.SigningKey)); err != nil {
 		return sdk.WithStack(err)
@@ -58,11 +56,11 @@ func (w *CurrentWorker) Take(ctx context.Context, job sdk.WorkflowNodeJobRun) er
 	}
 	w.currentJob.signer = signer
 
-	log.Info(ctx, "Setup step logger %s", info.GelfServiceAddr)
+	log.Info(ctx, "Setup step logger %s", w.cfg.GelfServiceAddr)
 	throttlePolicy := hook.NewDefaultThrottlePolicy()
 
 	var graylogCfg = &hook.Config{
-		Addr:     info.GelfServiceAddr,
+		Addr:     w.cfg.GelfServiceAddr,
 		Protocol: "tcp",
 		ThrottlePolicy: &hook.ThrottlePolicyConfig{
 			Amount: 100,
@@ -71,8 +69,8 @@ func (w *CurrentWorker) Take(ctx context.Context, job sdk.WorkflowNodeJobRun) er
 		},
 	}
 
-	if info.GelfServiceAddrEnableTLS {
-		tcpCDNUrl := info.GelfServiceAddr
+	if w.cfg.GelfServiceAddrEnableTLS {
+		tcpCDNUrl := w.cfg.GelfServiceAddr
 		// Check if the url has a scheme
 		// We have to remove if to retrieve the hostname
 		if i := strings.Index(tcpCDNUrl, "://"); i > -1 {
diff --git a/engine/worker/internal/types.go b/engine/worker/internal/types.go
index 57dbdfeeb..455dec285 100644
--- a/engine/worker/internal/types.go
+++ b/engine/worker/internal/types.go
@@ -42,7 +42,6 @@ type CurrentWorker struct {
 	basedir     afero.Fs
 	manualExit  bool
 	gelfLogger  *logger
-	cdnHttpAddr string // FIXME get CDN information from worker config
 	stepLogLine int64
 	httpPort    int32
 	currentJob  struct {
@@ -194,7 +193,7 @@ func (wk *CurrentWorker) SendLog(ctx context.Context, level workerruntime.Level,
 }
 
 func (wk *CurrentWorker) CDNHttpURL() string {
-	return wk.cdnHttpAddr
+	return wk.cfg.CDNEndpoint
 }
 
 func (wk *CurrentWorker) prepareLog(ctx context.Context, level workerruntime.Level, s string) (cdslog.Message, string, error) {
diff --git a/go.mod b/go.mod
index 9c0400243..118f8716b 100644
--- a/go.mod
+++ b/go.mod
@@ -67,6 +67,7 @@ require (
 	github.com/pkg/browser v0.0.0-20170505125900-c90ca0c84f15
 	github.com/pkg/errors v0.9.1
 	github.com/rockbears/log v0.6.0
+	github.com/rockbears/yaml v0.1.1-0.20220901090137-13dadb408625
 	github.com/rubenv/sql-migrate v0.0.0-20160620083229-6f4757563362
 	github.com/sguiheux/go-coverage v0.0.0-20190710153556-287b082a7197
 	github.com/shirou/gopsutil v0.0.0-20170406131756-e49a95f3d5f8
@@ -76,7 +77,7 @@ require (
 	github.com/spf13/cobra v1.1.1
 	github.com/spf13/viper v1.10.1
 	github.com/streadway/amqp v0.0.0-20180528204448-e5adc2ada8b8
-	github.com/stretchr/testify v1.7.0
+	github.com/stretchr/testify v1.8.0
 	github.com/studio-b12/gowebdav v0.0.0-20200303150724-9380631c29a1
 	github.com/tevino/abool v0.0.0-20170917061928-9b9efcf221b5
 	github.com/urfave/cli v1.20.0
@@ -252,7 +253,7 @@ require (
 	gopkg.in/inf.v0 v0.9.1 // indirect
 	gopkg.in/ini.v1 v1.66.4 // indirect
 	gopkg.in/warnings.v0 v0.1.2 // indirect
-	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
+	gopkg.in/yaml.v3 v3.0.1 // indirect
 	gotest.tools v2.1.0+incompatible // indirect
 	k8s.io/klog/v2 v2.8.0 // indirect
 	k8s.io/utils v0.0.0-20201110183641-67b214c5f920 // indirect
diff --git a/go.sum b/go.sum
index af1394abe..755e76f2c 100644
--- a/go.sum
+++ b/go.sum
@@ -756,6 +756,8 @@ github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5X
 github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
 github.com/rockbears/log v0.6.0 h1:Wzpu7nbrZFFJy14ku41jI2/UEh3d0CJwvIED9/FQZKQ=
 github.com/rockbears/log v0.6.0/go.mod h1:z46IOEOh914gJvg256Vm3F4s8K7D3ePaEAzuMYcfk98=
+github.com/rockbears/yaml v0.1.1-0.20220901090137-13dadb408625 h1:HQAWw/D9RItCYSoWFs8E7IrGKrX9ivqAlCq47DM3IVU=
+github.com/rockbears/yaml v0.1.1-0.20220901090137-13dadb408625/go.mod h1:8cDJx2PWQJMtfGgsRCvHVbIB61SV3dvy8o6EGv2cIpg=
 github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
 github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
@@ -816,16 +818,19 @@ github.com/streadway/amqp v0.0.0-20180528204448-e5adc2ada8b8 h1:l6epF6yBwuejBfhG
 github.com/streadway/amqp v0.0.0-20180528204448-e5adc2ada8b8/go.mod h1:1WNBiOZtZQLpVAyu0iTduoJL9hEsMloAK5XWrtW0xdY=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
 github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
+github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.3.1-0.20190311161405-34c6fa2dc709/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/studio-b12/gowebdav v0.0.0-20200303150724-9380631c29a1 h1:TPyHV/OgChqNcnYqCoCvIFjR9TU60gFXXBKnhOBzVEI=
 github.com/studio-b12/gowebdav v0.0.0-20200303150724-9380631c29a1/go.mod h1:gCcfDlA1Y7GqOaeEKw5l9dOGx1VLdc/HuQSlQAaZ30s=
 github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
@@ -1418,8 +1423,9 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
 gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gotest.tools v2.1.0+incompatible h1:5USw7CrJBYKqjg9R7QlA6jzqZKEAtvW82aNmsxxGPxw=
 gotest.tools v2.1.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
 honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/sdk/cdsclient/client_queue.go b/sdk/cdsclient/client_queue.go
index 5a4223d91..8a62100d8 100644
--- a/sdk/cdsclient/client_queue.go
+++ b/sdk/cdsclient/client_queue.go
@@ -271,8 +271,28 @@ func (c *client) QueueJobSetVersion(ctx context.Context, jobID int64, version sd
 	return err
 }
 
-func (c *client) QueueWorkflowRunResultsRelease(ctx context.Context, permJobID int64) error {
+func (c *client) QueueWorkflowRunResultsRelease(ctx context.Context, permJobID int64, runResultIDs []string, from, to string) error {
+	req := sdk.WorkflowRunResultPromotionRequest{
+		IDs: runResultIDs,
+		WorkflowRunResultPromotion: sdk.WorkflowRunResultPromotion{
+			FromMaturity: from,
+			ToMaturity:   to,
+		},
+	}
 	uri := fmt.Sprintf("/queue/workflows/%d/run/results/release", permJobID)
-	_, err := c.PostJSON(ctx, uri, nil, nil)
+	_, err := c.PostJSON(ctx, uri, req, nil)
+	return err
+}
+
+func (c *client) QueueWorkflowRunResultsPromote(ctx context.Context, permJobID int64, runResultIDs []string, from, to string) error {
+	req := sdk.WorkflowRunResultPromotionRequest{
+		IDs: runResultIDs,
+		WorkflowRunResultPromotion: sdk.WorkflowRunResultPromotion{
+			FromMaturity: from,
+			ToMaturity:   to,
+		},
+	}
+	uri := fmt.Sprintf("/queue/workflows/%d/run/results/promote", permJobID)
+	_, err := c.PostJSON(ctx, uri, req, nil)
 	return err
 }
diff --git a/sdk/cdsclient/client_rbac.go b/sdk/cdsclient/client_rbac.go
new file mode 100644
index 000000000..627d1ecbb
--- /dev/null
+++ b/sdk/cdsclient/client_rbac.go
@@ -0,0 +1,27 @@
+package cdsclient
+
+import (
+	"context"
+	"io"
+
+	"github.com/rockbears/yaml"
+
+	"github.com/ovh/cds/sdk"
+)
+
+func (c *client) RBACImport(ctx context.Context, content io.Reader, mods ...RequestModifier) (sdk.RBAC, error) {
+	var rbacRule sdk.RBAC
+
+	body, err := io.ReadAll(content)
+	if err != nil {
+		return rbacRule, err
+	}
+
+	if err := yaml.Unmarshal(body, &rbacRule); err != nil {
+		return rbacRule, err
+	}
+
+	path := "/v2/rbac/import"
+	_, err = c.PostJSON(ctx, path, &rbacRule, &rbacRule, mods...)
+	return rbacRule, err
+}
diff --git a/sdk/cdsclient/interface.go b/sdk/cdsclient/interface.go
index 7e239c8f8..3291ee86b 100644
--- a/sdk/cdsclient/interface.go
+++ b/sdk/cdsclient/interface.go
@@ -231,6 +231,10 @@ type ProjectClient interface {
 	ProjectRepositoryAnalysisGet(ctx context.Context, projectKey string, vcsIdentifier string, repositoryIdentifier string, analysisID string) (sdk.ProjectRepositoryAnalysis, error)
 }
 
+type RBACClient interface {
+	RBACImport(ctx context.Context, content io.Reader, mods ...RequestModifier) (sdk.RBAC, error)
+}
+
 // ProjectKeysClient exposes project keys related functions
 type ProjectKeysClient interface {
 	ProjectKeysList(projectKey string) ([]sdk.ProjectKey, error)
@@ -270,7 +274,8 @@ type QueueClient interface {
 	QueueWorkerCacheLink(ctx context.Context, jobID int64, tag string) (sdk.CDNItemLinks, error)
 	QueueWorkflowRunResultsAdd(ctx context.Context, jobID int64, addRequest sdk.WorkflowRunResult) error
 	QueueWorkflowRunResultCheck(ctx context.Context, jobID int64, runResultCheck sdk.WorkflowRunResultCheck) (int, error)
-	QueueWorkflowRunResultsRelease(ctx context.Context, permJobID int64) error
+	QueueWorkflowRunResultsRelease(ctx context.Context, permJobID int64, runResultIDs []string, from, to string) error
+	QueueWorkflowRunResultsPromote(ctx context.Context, permJobID int64, runResultIDs []string, from, to string) error
 }
 
 // UserClient exposes users functions
@@ -405,6 +410,7 @@ type Interface interface {
 	PipelineClient
 	IntegrationClient
 	ProjectClient
+	RBACClient
 	QueueClient
 	Navbar() ([]sdk.NavbarProjectData, error)
 	Requirements() ([]sdk.Requirement, error)
diff --git a/sdk/cdsclient/mock_cdsclient/interface_mock.go b/sdk/cdsclient/mock_cdsclient/interface_mock.go
index d4ca5c618..ae4b5fd71 100644
--- a/sdk/cdsclient/mock_cdsclient/interface_mock.go
+++ b/sdk/cdsclient/mock_cdsclient/interface_mock.go
@@ -2894,6 +2894,49 @@ func (mr *MockProjectClientMockRecorder) VariableListEncrypt(projectKey interfac
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VariableListEncrypt", reflect.TypeOf((*MockProjectClient)(nil).VariableListEncrypt), projectKey)
 }
 
+// MockRbacClient is a mock of RBACClient interface.
+type MockRbacClient struct {
+	ctrl     *gomock.Controller
+	recorder *MockRbacClientMockRecorder
+}
+
+// MockRbacClientMockRecorder is the mock recorder for MockRbacClient.
+type MockRbacClientMockRecorder struct {
+	mock *MockRbacClient
+}
+
+// NewMockRbacClient creates a new mock instance.
+func NewMockRbacClient(ctrl *gomock.Controller) *MockRbacClient {
+	mock := &MockRbacClient{ctrl: ctrl}
+	mock.recorder = &MockRbacClientMockRecorder{mock}
+	return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use.
+func (m *MockRbacClient) EXPECT() *MockRbacClientMockRecorder {
+	return m.recorder
+}
+
+// RbacImport mocks base method.
+func (m *MockRbacClient) RBACImport(ctx context.Context, content io.Reader, mods ...cdsclient.RequestModifier) (sdk.RBAC, error) {
+	m.ctrl.T.Helper()
+	varargs := []interface{}{ctx, content}
+	for _, a := range mods {
+		varargs = append(varargs, a)
+	}
+	ret := m.ctrl.Call(m, "RBACImport", varargs...)
+	ret0, _ := ret[0].(sdk.RBAC)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// RbacImport indicates an expected call of RbacImport.
+func (mr *MockRbacClientMockRecorder) RbacImport(ctx, content interface{}, mods ...interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	varargs := append([]interface{}{ctx, content}, mods...)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RBACImport", reflect.TypeOf((*MockRbacClient)(nil).RBACImport), varargs...)
+}
+
 // MockProjectKeysClient is a mock of ProjectKeysClient interface.
 type MockProjectKeysClient struct {
 	ctrl     *gomock.Controller
@@ -3390,18 +3433,32 @@ func (mr *MockQueueClientMockRecorder) QueueWorkflowRunResultsAdd(ctx, jobID, ad
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueueWorkflowRunResultsAdd", reflect.TypeOf((*MockQueueClient)(nil).QueueWorkflowRunResultsAdd), ctx, jobID, addRequest)
 }
 
+// QueueWorkflowRunResultsPromote mocks base method.
+func (m *MockQueueClient) QueueWorkflowRunResultsPromote(ctx context.Context, permJobID int64, runResultIDs []string, from, to string) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "QueueWorkflowRunResultsPromote", ctx, permJobID, runResultIDs, from, to)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// QueueWorkflowRunResultsPromote indicates an expected call of QueueWorkflowRunResultsPromote.
+func (mr *MockQueueClientMockRecorder) QueueWorkflowRunResultsPromote(ctx, permJobID, runResultIDs, from, to interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueueWorkflowRunResultsPromote", reflect.TypeOf((*MockQueueClient)(nil).QueueWorkflowRunResultsPromote), ctx, permJobID, runResultIDs, from, to)
+}
+
 // QueueWorkflowRunResultsRelease mocks base method.
-func (m *MockQueueClient) QueueWorkflowRunResultsRelease(ctx context.Context, permJobID int64) error {
+func (m *MockQueueClient) QueueWorkflowRunResultsRelease(ctx context.Context, permJobID int64, runResultIDs []string, from, to string) error {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "QueueWorkflowRunResultsRelease", ctx, permJobID)
+	ret := m.ctrl.Call(m, "QueueWorkflowRunResultsRelease", ctx, permJobID, runResultIDs, from, to)
 	ret0, _ := ret[0].(error)
 	return ret0
 }
 
 // QueueWorkflowRunResultsRelease indicates an expected call of QueueWorkflowRunResultsRelease.
-func (mr *MockQueueClientMockRecorder) QueueWorkflowRunResultsRelease(ctx, permJobID interface{}) *gomock.Call {
+func (mr *MockQueueClientMockRecorder) QueueWorkflowRunResultsRelease(ctx, permJobID, runResultIDs, from, to interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueueWorkflowRunResultsRelease", reflect.TypeOf((*MockQueueClient)(nil).QueueWorkflowRunResultsRelease), ctx, permJobID)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueueWorkflowRunResultsRelease", reflect.TypeOf((*MockQueueClient)(nil).QueueWorkflowRunResultsRelease), ctx, permJobID, runResultIDs, from, to)
 }
 
 // MockUserClient is a mock of UserClient interface.
@@ -7466,18 +7523,52 @@ func (mr *MockInterfaceMockRecorder) QueueWorkflowRunResultsAdd(ctx, jobID, addR
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueueWorkflowRunResultsAdd", reflect.TypeOf((*MockInterface)(nil).QueueWorkflowRunResultsAdd), ctx, jobID, addRequest)
 }
 
+// QueueWorkflowRunResultsPromote mocks base method.
+func (m *MockInterface) QueueWorkflowRunResultsPromote(ctx context.Context, permJobID int64, runResultIDs []string, from, to string) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "QueueWorkflowRunResultsPromote", ctx, permJobID, runResultIDs, from, to)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// QueueWorkflowRunResultsPromote indicates an expected call of QueueWorkflowRunResultsPromote.
+func (mr *MockInterfaceMockRecorder) QueueWorkflowRunResultsPromote(ctx, permJobID, runResultIDs, from, to interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueueWorkflowRunResultsPromote", reflect.TypeOf((*MockInterface)(nil).QueueWorkflowRunResultsPromote), ctx, permJobID, runResultIDs, from, to)
+}
+
 // QueueWorkflowRunResultsRelease mocks base method.
-func (m *MockInterface) QueueWorkflowRunResultsRelease(ctx context.Context, permJobID int64) error {
+func (m *MockInterface) QueueWorkflowRunResultsRelease(ctx context.Context, permJobID int64, runResultIDs []string, from, to string) error {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "QueueWorkflowRunResultsRelease", ctx, permJobID)
+	ret := m.ctrl.Call(m, "QueueWorkflowRunResultsRelease", ctx, permJobID, runResultIDs, from, to)
 	ret0, _ := ret[0].(error)
 	return ret0
 }
 
 // QueueWorkflowRunResultsRelease indicates an expected call of QueueWorkflowRunResultsRelease.
-func (mr *MockInterfaceMockRecorder) QueueWorkflowRunResultsRelease(ctx, permJobID interface{}) *gomock.Call {
+func (mr *MockInterfaceMockRecorder) QueueWorkflowRunResultsRelease(ctx, permJobID, runResultIDs, from, to interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueueWorkflowRunResultsRelease", reflect.TypeOf((*MockInterface)(nil).QueueWorkflowRunResultsRelease), ctx, permJobID)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueueWorkflowRunResultsRelease", reflect.TypeOf((*MockInterface)(nil).QueueWorkflowRunResultsRelease), ctx, permJobID, runResultIDs, from, to)
+}
+
+// RbacImport mocks base method.
+func (m *MockInterface) RBACImport(ctx context.Context, content io.Reader, mods ...cdsclient.RequestModifier) (sdk.RBAC, error) {
+	m.ctrl.T.Helper()
+	varargs := []interface{}{ctx, content}
+	for _, a := range mods {
+		varargs = append(varargs, a)
+	}
+	ret := m.ctrl.Call(m, "RBACImport", varargs...)
+	ret0, _ := ret[0].(sdk.RBAC)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// RbacImport indicates an expected call of RbacImport.
+func (mr *MockInterfaceMockRecorder) RbacImport(ctx, content interface{}, mods ...interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	varargs := append([]interface{}{ctx, content}, mods...)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RBACImport", reflect.TypeOf((*MockInterface)(nil).RBACImport), varargs...)
 }
 
 // RepositoriesList mocks base method.
@@ -9583,18 +9674,32 @@ func (mr *MockWorkerInterfaceMockRecorder) QueueWorkflowRunResultsAdd(ctx, jobID
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueueWorkflowRunResultsAdd", reflect.TypeOf((*MockWorkerInterface)(nil).QueueWorkflowRunResultsAdd), ctx, jobID, addRequest)
 }
 
+// QueueWorkflowRunResultsPromote mocks base method.
+func (m *MockWorkerInterface) QueueWorkflowRunResultsPromote(ctx context.Context, permJobID int64, runResultIDs []string, from, to string) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "QueueWorkflowRunResultsPromote", ctx, permJobID, runResultIDs, from, to)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// QueueWorkflowRunResultsPromote indicates an expected call of QueueWorkflowRunResultsPromote.
+func (mr *MockWorkerInterfaceMockRecorder) QueueWorkflowRunResultsPromote(ctx, permJobID, runResultIDs, from, to interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueueWorkflowRunResultsPromote", reflect.TypeOf((*MockWorkerInterface)(nil).QueueWorkflowRunResultsPromote), ctx, permJobID, runResultIDs, from, to)
+}
+
 // QueueWorkflowRunResultsRelease mocks base method.
-func (m *MockWorkerInterface) QueueWorkflowRunResultsRelease(ctx context.Context, permJobID int64) error {
+func (m *MockWorkerInterface) QueueWorkflowRunResultsRelease(ctx context.Context, permJobID int64, runResultIDs []string, from, to string) error {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "QueueWorkflowRunResultsRelease", ctx, permJobID)
+	ret := m.ctrl.Call(m, "QueueWorkflowRunResultsRelease", ctx, permJobID, runResultIDs, from, to)
 	ret0, _ := ret[0].(error)
 	return ret0
 }
 
 // QueueWorkflowRunResultsRelease indicates an expected call of QueueWorkflowRunResultsRelease.
-func (mr *MockWorkerInterfaceMockRecorder) QueueWorkflowRunResultsRelease(ctx, permJobID interface{}) *gomock.Call {
+func (mr *MockWorkerInterfaceMockRecorder) QueueWorkflowRunResultsRelease(ctx, permJobID, runResultIDs, from, to interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueueWorkflowRunResultsRelease", reflect.TypeOf((*MockWorkerInterface)(nil).QueueWorkflowRunResultsRelease), ctx, permJobID)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueueWorkflowRunResultsRelease", reflect.TypeOf((*MockWorkerInterface)(nil).QueueWorkflowRunResultsRelease), ctx, permJobID, runResultIDs, from, to)
 }
 
 // Requirements mocks base method.
diff --git a/sdk/interpolate/interpolate_helper.go b/sdk/interpolate/interpolate_helper.go
index 46572c9ef..97c6ef337 100644
--- a/sdk/interpolate/interpolate_helper.go
+++ b/sdk/interpolate/interpolate_helper.go
@@ -11,6 +11,7 @@ import (
 	"net/url"
 	"path"
 	"reflect"
+	"strconv"
 	"strings"
 	"text/template"
 
@@ -66,6 +67,7 @@ func init() {
 		"b64enc":       base64encode,
 		"b64dec":       base64decode,
 		"escape":       escape,
+		"stringQuote":  stringQuote,
 		"add": func(i ...interface{}) int64 {
 			var a int64 = 0
 			for _, b := range i {
@@ -375,6 +377,13 @@ func escape(s string) string {
 	return s1
 }
 
+func stringQuote(s string) string {
+	x := strconv.Quote(s)
+	x = strings.TrimPrefix(x, `"`)
+	x = strings.TrimSuffix(x, `"`)
+	return x
+}
+
 func ternary(v, v2, a interface{}) interface{} {
 	if cast.ToBool(a) {
 		return v
diff --git a/sdk/log/fields.go b/sdk/log/fields.go
index b55acb3b2..c16687c53 100644
--- a/sdk/log/fields.go
+++ b/sdk/log/fields.go
@@ -66,6 +66,7 @@ func init() {
 		LatencyNum,
 		Method,
 		RbackCheckerName,
+		Repository,
 		Route,
 		RequestID,
 		RequestURI,
diff --git a/sdk/rbac.go b/sdk/rbac.go
index 4e2bd49d8..8a5c517bd 100644
--- a/sdk/rbac.go
+++ b/sdk/rbac.go
@@ -19,17 +19,17 @@ type RBAC struct {
 	Projects     []RBACProject `json:"projects" db:"-"`
 }
 
-func IsValidRbac(rbac *RBAC) error {
+func IsValidRBAC(rbac *RBAC) error {
 	if rbac.Name == "" {
 		return WrapError(ErrInvalidData, "missing permission name")
 	}
 	for _, g := range rbac.Globals {
-		if err := isValidRbacGlobal(rbac.Name, g); err != nil {
+		if err := isValidRBACGlobal(rbac.Name, g); err != nil {
 			return err
 		}
 	}
 	for _, p := range rbac.Projects {
-		if err := isValidRbacProject(rbac.Name, p); err != nil {
+		if err := isValidRBACProject(rbac.Name, p); err != nil {
 			return err
 		}
 	}
diff --git a/sdk/rbac_global.go b/sdk/rbac_global.go
index 40dad46ab..db0e0c07d 100644
--- a/sdk/rbac_global.go
+++ b/sdk/rbac_global.go
@@ -12,7 +12,7 @@ type RBACGlobal struct {
 	RBACGroupsIDs  []int64  `json:"-" db:"-"`
 }
 
-func isValidRbacGlobal(rbacName string, rg RBACGlobal) error {
+func isValidRBACGlobal(rbacName string, rg RBACGlobal) error {
 	if len(rg.RBACGroupsIDs) == 0 && len(rg.RBACUsersIDs) == 0 {
 		return NewErrorFrom(ErrInvalidData, "rbac %s: missing groups or users on global permission", rbacName)
 	}
diff --git a/sdk/rbac_project.go b/sdk/rbac_project.go
index 5cb731fe9..7fb93d765 100644
--- a/sdk/rbac_project.go
+++ b/sdk/rbac_project.go
@@ -15,7 +15,7 @@ type RBACProject struct {
 	RBACGroupsIDs []int64  `json:"-" db:"-"`
 }
 
-func isValidRbacProject(rbacName string, rbacProject RBACProject) error {
+func isValidRBACProject(rbacName string, rbacProject RBACProject) error {
 	// Check empty group and users
 	if len(rbacProject.RBACGroupsIDs) == 0 && len(rbacProject.RBACUsersIDs) == 0 {
 		return NewErrorFrom(ErrInvalidData, "rbac %s: missing groups or users on project permission", rbacName)
diff --git a/sdk/workflow_run_result.go b/sdk/workflow_run_result.go
index 3907bc45c..ba5713b5f 100644
--- a/sdk/workflow_run_result.go
+++ b/sdk/workflow_run_result.go
@@ -53,9 +53,38 @@ type WorkflowRunResult struct {
 }
 
 type WorkflowRunResultSync struct {
-	Sync  bool   `json:"sync"`
-	Link  string `json:"link"`
-	Error string `json:"error"`
+	Sync       bool                         `json:"sync"`
+	Link       string                       `json:"link"`
+	Error      string                       `json:"error"`
+	Promotions []WorkflowRunResultPromotion `json:"promotions"`
+	Releases   []WorkflowRunResultPromotion `json:"releases"`
+}
+
+type WorkflowRunResultPromotionRequest struct {
+	WorkflowRunResultPromotion
+	IDs []string
+}
+
+type WorkflowRunResultPromotion struct {
+	FromMaturity string    `json:"from_maturity"`
+	ToMaturity   string    `json:"to_maturity"`
+	Date         time.Time `json:"date"`
+}
+
+func (s *WorkflowRunResultSync) LatestPromotionOrRelease() *WorkflowRunResultPromotion {
+	sort.Slice(s.Promotions, func(i, j int) bool {
+		return s.Promotions[i].Date.Before(s.Promotions[j].Date)
+	})
+	sort.Slice(s.Releases, func(i, j int) bool {
+		return s.Releases[i].Date.Before(s.Releases[j].Date)
+	})
+	if len(s.Releases) > 0 {
+		return &s.Releases[len(s.Releases)-1]
+	}
+	if len(s.Promotions) > 0 {
+		return &s.Promotions[len(s.Promotions)-1]
+	}
+	return nil
 }
 
 // Value returns driver.Value from WorkflowRunResultSync
diff --git a/ui/package-lock.json b/ui/package-lock.json
index 029f85f6a..b74717aeb 100644
--- a/ui/package-lock.json
+++ b/ui/package-lock.json
@@ -25,7 +25,6 @@
         "@ngxs/devtools-plugin": "3.7.3",
         "@ngxs/logger-plugin": "3.7.3",
         "@ngxs/store": "3.7.3",
-        "@richardlt/ng2-semantic-ui": "0.10.0-alpha.6-fork-6",
         "@sgu/auto-complete": "13.0.0-rc",
         "@swimlane/ngx-charts": "16.0.0",
         "@types/dagre-d3": "0.6.2",
@@ -5227,32 +5226,6 @@
         "@octokit/openapi-types": "^11.2.0"
       }
     },
-    "node_modules/@richardlt/ng2-semantic-ui": {
-      "version": "0.10.0-alpha.6-fork-6",
-      "resolved": "https://registry.npmjs.org/@richardlt/ng2-semantic-ui/-/ng2-semantic-ui-0.10.0-alpha.6-fork-6.tgz",
-      "integrity": "sha512-D1UK0kFP+NvhfzA6szVrhWSQICzkgPqYozFFimErCVXxmShUjDu2vsU9O3I9h2h7iw6gYBZrb+XSj+sGXxiUpg==",
-      "dependencies": {
-        "bowser": "^1.7.2",
-        "date-fns": "2.0.0-alpha.1",
-        "element-closest": "^2.0.2",
-        "extend": "^3.0.1",
-        "is-ua-webview": "^1.0.0",
-        "popper.js": "^1.14.0",
-        "tslib": "^2.3.0"
-      },
-      "peerDependencies": {
-        "@angular/common": ">= 13.3.0",
-        "@angular/compiler": ">= 13.3.0",
-        "@angular/core": ">= 13.3.0",
-        "@angular/forms": ">= 13.3.0",
-        "rxjs": "^6.0.0"
-      }
-    },
-    "node_modules/@richardlt/ng2-semantic-ui/node_modules/tslib": {
-      "version": "2.3.1",
-      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
-      "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw=="
-    },
     "node_modules/@schematics/angular": {
       "version": "13.3.0",
       "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-13.3.0.tgz",
@@ -8060,11 +8033,6 @@
       "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=",
       "dev": true
     },
-    "node_modules/bowser": {
-      "version": "1.9.4",
-      "resolved": "https://registry.npmjs.org/bowser/-/bowser-1.9.4.tgz",
-      "integrity": "sha512-9IdMmj2KjigRq6oWhmwv1W36pDuA4STQZ8q6YO9um+x07xgYNCD3Oou+WP/3L1HNz7iqythGet3/p4wvc8AAwQ=="
-    },
     "node_modules/brace-expansion": {
       "version": "1.1.11",
       "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -10260,11 +10228,6 @@
         "node": ">=0.10"
       }
     },
-    "node_modules/date-fns": {
-      "version": "2.0.0-alpha.1",
-      "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.0.0-alpha.1.tgz",
-      "integrity": "sha512-4gYdF1rDgv9X/0ko69pt+FgpQtDU5rgqZVmckIOhDicfCSTndwHMhUhLJw+pa4DlPdzIkEBtHg6L6VlQ6ueD1g=="
-    },
     "node_modules/date-format": {
       "version": "4.0.6",
       "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.6.tgz",
@@ -10919,14 +10882,6 @@
       "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.103.tgz",
       "integrity": "sha512-c/uKWR1Z/W30Wy/sx3dkZoj4BijbXX85QKWu9jJfjho3LBAXNEGAEW3oWiGb+dotA6C6BzCTxL2/aLes7jlUeg=="
     },
-    "node_modules/element-closest": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/element-closest/-/element-closest-2.0.2.tgz",
-      "integrity": "sha1-cqdAoQdFM4LijfnOXbtajfD5Zuw=",
-      "engines": {
-        "node": ">=4.0.0"
-      }
-    },
     "node_modules/emoji-regex": {
       "version": "8.0.0",
       "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
@@ -17099,11 +17054,6 @@
       "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=",
       "dev": true
     },
-    "node_modules/is-ua-webview": {
-      "version": "1.0.5",
-      "resolved": "https://registry.npmjs.org/is-ua-webview/-/is-ua-webview-1.0.5.tgz",
-      "integrity": "sha512-CfaCxjFTU3xUluvcKg0+3j5uXeyneANFnC9ynSCTFA06nFt1okFBXpkKVxqxtFAKcbsFl+vrOQ7g6hlaDIQu8A=="
-    },
     "node_modules/is-unc-path": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz",
@@ -21685,16 +21635,6 @@
       "integrity": "sha512-k+YsbhpA9e+EFfKjTCH3VW6aoKlyNYI6NYdTfDL4CIvFnvsuO84ttonmZE7rc+v23SLTH8XX+5w/Ak9v0xGY4g==",
       "dev": true
     },
-    "node_modules/popper.js": {
-      "version": "1.16.1",
-      "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
-      "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==",
-      "deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1",
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/popperjs"
-      }
-    },
     "node_modules/portfinder": {
       "version": "1.0.28",
       "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz",
@@ -31247,27 +31187,6 @@
         "@octokit/openapi-types": "^11.2.0"
       }
     },
-    "@richardlt/ng2-semantic-ui": {
-      "version": "0.10.0-alpha.6-fork-6",
-      "resolved": "https://registry.npmjs.org/@richardlt/ng2-semantic-ui/-/ng2-semantic-ui-0.10.0-alpha.6-fork-6.tgz",
-      "integrity": "sha512-D1UK0kFP+NvhfzA6szVrhWSQICzkgPqYozFFimErCVXxmShUjDu2vsU9O3I9h2h7iw6gYBZrb+XSj+sGXxiUpg==",
-      "requires": {
-        "bowser": "^1.7.2",
-        "date-fns": "2.0.0-alpha.1",
-        "element-closest": "^2.0.2",
-        "extend": "^3.0.1",
-        "is-ua-webview": "^1.0.0",
-        "popper.js": "^1.14.0",
-        "tslib": "^2.3.0"
-      },
-      "dependencies": {
-        "tslib": {
-          "version": "2.3.1",
-          "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
-          "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw=="
-        }
-      }
-    },
     "@schematics/angular": {
       "version": "13.3.0",
       "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-13.3.0.tgz",
@@ -33516,11 +33435,6 @@
       "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=",
       "dev": true
     },
-    "bowser": {
-      "version": "1.9.4",
-      "resolved": "https://registry.npmjs.org/bowser/-/bowser-1.9.4.tgz",
-      "integrity": "sha512-9IdMmj2KjigRq6oWhmwv1W36pDuA4STQZ8q6YO9um+x07xgYNCD3Oou+WP/3L1HNz7iqythGet3/p4wvc8AAwQ=="
-    },
     "brace-expansion": {
       "version": "1.1.11",
       "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -35331,11 +35245,6 @@
         "assert-plus": "^1.0.0"
       }
     },
-    "date-fns": {
-      "version": "2.0.0-alpha.1",
-      "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.0.0-alpha.1.tgz",
-      "integrity": "sha512-4gYdF1rDgv9X/0ko69pt+FgpQtDU5rgqZVmckIOhDicfCSTndwHMhUhLJw+pa4DlPdzIkEBtHg6L6VlQ6ueD1g=="
-    },
     "date-format": {
       "version": "4.0.6",
       "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.6.tgz",
@@ -35860,11 +35769,6 @@
       "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.103.tgz",
       "integrity": "sha512-c/uKWR1Z/W30Wy/sx3dkZoj4BijbXX85QKWu9jJfjho3LBAXNEGAEW3oWiGb+dotA6C6BzCTxL2/aLes7jlUeg=="
     },
-    "element-closest": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/element-closest/-/element-closest-2.0.2.tgz",
-      "integrity": "sha1-cqdAoQdFM4LijfnOXbtajfD5Zuw="
-    },
     "emoji-regex": {
       "version": "8.0.0",
       "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
@@ -40715,11 +40619,6 @@
       "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=",
       "dev": true
     },
-    "is-ua-webview": {
-      "version": "1.0.5",
-      "resolved": "https://registry.npmjs.org/is-ua-webview/-/is-ua-webview-1.0.5.tgz",
-      "integrity": "sha512-CfaCxjFTU3xUluvcKg0+3j5uXeyneANFnC9ynSCTFA06nFt1okFBXpkKVxqxtFAKcbsFl+vrOQ7g6hlaDIQu8A=="
-    },
     "is-unc-path": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz",
@@ -44250,11 +44149,6 @@
       "integrity": "sha512-k+YsbhpA9e+EFfKjTCH3VW6aoKlyNYI6NYdTfDL4CIvFnvsuO84ttonmZE7rc+v23SLTH8XX+5w/Ak9v0xGY4g==",
       "dev": true
     },
-    "popper.js": {
-      "version": "1.16.1",
-      "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
-      "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ=="
-    },
     "portfinder": {
       "version": "1.0.28",
       "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz",
diff --git a/ui/package.json b/ui/package.json
index 53e77dfd5..ff3cbf354 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -43,7 +43,6 @@
     "@ngxs/devtools-plugin": "3.7.3",
     "@ngxs/logger-plugin": "3.7.3",
     "@ngxs/store": "3.7.3",
-    "@richardlt/ng2-semantic-ui": "0.10.0-alpha.6-fork-6",
     "@sgu/auto-complete": "13.0.0-rc",
     "@swimlane/ngx-charts": "16.0.0",
     "@types/dagre-d3": "0.6.2",
diff --git a/ui/src/app/model/workflow-template.model.ts b/ui/src/app/model/workflow-template.model.ts
index 0c5ec045f..0bb768856 100644
--- a/ui/src/app/model/workflow-template.model.ts
+++ b/ui/src/app/model/workflow-template.model.ts
@@ -103,9 +103,9 @@ export class InstanceStatusUtil {
     public static color(status: InstanceStatus): string {
         switch (status) {
             case InstanceStatus.UP_TO_DATE:
-                return 'green';
+                return 'success';
             case InstanceStatus.NOT_UP_TO_DATE:
-                return 'red';
+                return 'error';
             case InstanceStatus.NOT_IMPORTED:
                 return 'orange';
         }
diff --git a/ui/src/app/shared/action/action.html b/ui/src/app/shared/action/action.html
index 2b0063738..7d3036ef3 100644
--- a/ui/src/app/shared/action/action.html
+++ b/ui/src/app/shared/action/action.html
@@ -1,37 +1,38 @@
-<form class="ui form" id="ActionEdit" *ngIf="editableAction">
-    <div class="ui segment">
-        <div class="field">
-            <div class="fields">
-                <div class="six wide inline field">
-                    <label>{{'common_name' | translate}}</label>
-                    <input *ngIf="edit;" type="text" name="actionName" [(ngModel)]="editableAction.name"
-                        (keydown)="editableAction.hasChanged = true; keyEvent($event)">
-                    <span *ngIf="!edit;" class="inline">{{editableAction.name}}</span>
-                </div>
-                <div class="seven wide inline field">
-                    <label class="labelPadding">{{'common_description' | translate}}</label>
-                    <textarea *ngIf="edit;" [disabled]="!edit" name="actionDescription"
-                        [(ngModel)]="editableAction.description" rows="{{getDescriptionHeight()}}"
-                        (keydown)="editableAction.hasChanged = true; keyEvent($event)"></textarea>
-                    <markdown *ngIf="!edit;" [data]="editableAction.description"></markdown>
-                </div>
-                <div class="one wide inline field">
+<form nz-form id="ActionEdit" *ngIf="editableAction">
+    <div class="nzSegment">
+            <nz-row>
+                <nz-col [nzSpan]="7">
+                    <nz-form-item>
+                        <nz-form-label>Name</nz-form-label>
+                        <nz-form-control>
+                            <input *ngIf="edit;" nz-input type="text" name="actionName" [(ngModel)]="editableAction.name"
+                                   (keydown)="editableAction.hasChanged = true; keyEvent($event)">
+                            <span *ngIf="!edit;" class="inline">{{editableAction.name}}</span>
+                        </nz-form-control>
+                    </nz-form-item>
+                </nz-col>
+                <nz-col [nzSpan]="10">
+                    <nz-form-item>
+                        <nz-form-label>Description</nz-form-label>
+                        <nz-form-control>
+                             <textarea *ngIf="edit;" nz-input [disabled]="!edit" name="actionDescription"
+                                       [(ngModel)]="editableAction.description" rows="{{getDescriptionHeight()}}"
+                                       (keydown)="editableAction.hasChanged = true; keyEvent($event)"></textarea>
+                            <markdown *ngIf="!edit;" [data]="editableAction.description"></markdown>
+                        </nz-form-control>
+                    </nz-form-item>
+                </nz-col>
+                <nz-col [nzSpan]="3">
                     <div class="ui checkbox">
                         <input type="checkbox" id="enabled_action_{{editableAction.id}}" name="enabled"
-                            [(ngModel)]="editableAction.enabled" (change)="editableAction.hasChanged = true"
-                            [disabled]="!edit">
+                               [(ngModel)]="editableAction.enabled" (change)="editableAction.hasChanged = true"
+                               [disabled]="!edit">
                         <label for="enabled_action_{{editableAction.id}}">{{ 'common_enable' | translate }}</label>
                     </div>
-                    <div class="ui checkbox mt10" *ngIf="editableAction.type !== 'Joined'">
-                        <input type="checkbox" id="deprecated_{{editableAction.id}}" name="deprecated"
-                            [(ngModel)]="editableAction.deprecated" (change)="editableAction.hasChanged = true"
-                            [disabled]="!edit">
-                        <label for="deprecated_{{editableAction.id}}">{{ 'common_deprecated' | translate }}</label>
-                    </div>
-                </div>
-                <div class="two wide right aligned field labelPadding">
+                </nz-col>
+                <nz-col [nzSpan]="4">
                     <a class="pointing"
-                        (click)="prepareEditRequirements(); requirementModalVisible = true">
+                       (click)="prepareEditRequirements(); requirementModalVisible = true">
                         {{ editableAction.requirements.length + ' '}}{{'common_requirements' | translate }}
                     </a>
                     <nz-modal [nzVisible]="requirementModalVisible" nzTitle="Action requirements" nzWidth="900px"
@@ -52,41 +53,26 @@
                             <button nz-button (click)="requirementModalVisible = false;parseRequirements();">Close</button>
                         </div>
                     </nz-modal>
-                </div>
-            </div>
-        </div>
-    </div>
-    <div class="ui segment" *ngIf="editableAction.type !== 'Joined'">
-        <div class="field">
-            <label>{{ 'common_parameters' | translate }}</label>
-            <div>
-                <app-parameter-list [project]="project" [parameters]="editableAction.parameters"
-                    [mode]="edit? 'edit': 'ro'" [hideSave]="true" (event)="parameterEvent($event)">
-                </app-parameter-list>
-            </div>
-            <div>
-                <app-parameter-form *ngIf="edit" [project]="project" (createParameterEvent)="parameterEvent($event)">
-                </app-parameter-form>
-            </div>
-        </div>
+                </nz-col>
+            </nz-row>
     </div>
 
     <div class="subtitle normalStep">
-        <h3 class="inline">{{ 'action_step_title' | translate }}<span *ngIf="steps">{{ ' (' + steps.length + ')'}}</span></h3>
-        <div class="right floated" *ngIf="steps && steps.length > 0">
-            <button class="ui blue button" (click)="collapsed = !collapsed">
+        <h3 class="inline">Job steps<span *ngIf="steps">{{ ' (' + steps.length + ')'}}</span></h3>
+        <div class="alignRight" *ngIf="steps && steps.length > 0">
+            <button nz-button (click)="collapsed = !collapsed">
                 <ng-container *ngIf="collapsed">
-                    <i class="hide icon"></i>
+                    <span nz-icon nzType="eye-invisible" nzTheme="outline"></span>
                     {{'common_hide_all' | translate}}
                 </ng-container>
                 <ng-container *ngIf="!collapsed">
-                    <i class="eye icon"></i>
+                    <span nz-icon nzType="eye" nzTheme="outline"></span>
                     {{'common_show_all' | translate}}
                 </ng-container>
             </button>
         </div>
     </div>
-    <div class="ui segment" *ngIf="steps.length > 0 && publicActions" [class.off]="!editableAction.enabled">
+    <div class="nzSegment" *ngIf="steps.length > 0 && publicActions" [class.off]="!editableAction.enabled">
         <div class="dragula-container" [dragula]="'bag-nonfinal'" [(dragulaModel)]="steps" *ngIf="steps">
             <div *ngFor="let step of steps; let idxStep = index" class="step" [class.off]="!step.enabled">
                 <app-action-step [action]="editableAction" [step]="step" [edit]="edit" [suggest]="suggest"
@@ -97,9 +83,7 @@
             </div>
         </div>
     </div>
-    <div class="ui info message" *ngIf="steps.length === 0">
-        {{ 'step_nonfinal_no' | translate }}
-    </div>
+    <nz-alert nzType="info" nzMessage="There is no step."  *ngIf="steps.length === 0"></nz-alert>
 </form>
 <nz-layout class="bottomBtnsWrapper"
      *ngIf="edit && !(pipeline && pipeline.previewMode)">
diff --git a/ui/src/app/shared/action/action.scss b/ui/src/app/shared/action/action.scss
index 0400daa06..5da96b5fb 100644
--- a/ui/src/app/shared/action/action.scss
+++ b/ui/src/app/shared/action/action.scss
@@ -1,6 +1,14 @@
 @import '../../../common';
 #ActionEdit {
   padding-bottom: 100px;
+
+  .nzSegment {
+    padding: 30px 10px;
+
+    nz-col {
+      padding: 0 10px;
+    }
+  }
 }
 .requirementForm {
   margin-top: 10px;
@@ -9,12 +17,8 @@
   }
 }
 
-.mt10 {
-  margin-top: 10px;
-}
-
-.paramName {
-  font-weight: 600;
+.alignRight {
+  float: right;
 }
 
 .dragula-container {
@@ -31,18 +35,6 @@
   }
 }
 
-.inline.field input {
-  width: 80%;
-}
-
-.inline.field textarea {
-  width: 80%;
-}
-
-.labelPadding {
-  padding-top: 8px;
-}
-
 .subtitle {
   height: 40px;
   line-height: 40px;
diff --git a/ui/src/app/shared/audit/list/audit.list.component.ts b/ui/src/app/shared/audit/list/audit.list.component.ts
index f539c0e58..9cbbe1cd3 100644
--- a/ui/src/app/shared/audit/list/audit.list.component.ts
+++ b/ui/src/app/shared/audit/list/audit.list.component.ts
@@ -43,9 +43,12 @@ export class AuditListComponent implements OnInit {
                 name: '',
                 disabled: !this.canRollback,
                 selector: (audit: AuditWorkflow) => ({
-                        title: 'common_rollback',
-                        click: () => this.rollback.emit(audit.id)
-                    }),
+                    buttonType: 'primary',
+                    buttonDanger: false,
+                    buttonConfirmationMessage: 'Are you sure you want to rollback this workflow ?',
+                    title: 'Rollback',
+                    click: () => this.rollback.emit(audit.id)
+                }),
             },
         ];
     }
diff --git a/ui/src/app/shared/button/confirm/confirm.button.html b/ui/src/app/shared/button/confirm/confirm.button.html
deleted file mode 100644
index 44883fb8d..000000000
--- a/ui/src/app/shared/button/confirm/confirm.button.html
+++ /dev/null
@@ -1,10 +0,0 @@
-<button class="ui {{color}} button {{class}}" [class.loading]="loading" [class.disabled]="loading || disabled" (click)="showConfirmation = true"
-    *ngIf="!showConfirmation" [disabled]="disabled">
-    <i *ngIf="icon" class="{{icon}} icon"></i>
-    <span>{{title | translate}}</span>
-</button>
-<div class="ui buttons {{class}}" *ngIf="showConfirmation">
-    <button class="ui grey button" [class.disabled]="loading" (click)="showConfirmation = false"><i class="ban icon"></i></button>
-    <div class="or"></div>
-    <button class="ui green button active" [class.disabled]="loading" (click)="confirmEvent()"><i class="check icon"></i></button>
-</div>
\ No newline at end of file
diff --git a/ui/src/app/shared/button/confirm/confirm.button.scss b/ui/src/app/shared/button/confirm/confirm.button.scss
deleted file mode 100644
index e017ac18f..000000000
--- a/ui/src/app/shared/button/confirm/confirm.button.scss
+++ /dev/null
@@ -1,1 +0,0 @@
-@import '../../../../common';
diff --git a/ui/src/app/shared/button/confirm/confirm.button.ts b/ui/src/app/shared/button/confirm/confirm.button.ts
deleted file mode 100644
index ce826fc37..000000000
--- a/ui/src/app/shared/button/confirm/confirm.button.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
-
-@Component({
-    selector: 'app-confirm-button',
-    templateUrl: './confirm.button.html',
-    styleUrls: ['./confirm.button.scss'],
-    changeDetection: ChangeDetectionStrategy.OnPush
-})
-export class ConfirmButtonComponent  {
-    @Input() loading = false;
-    @Input() icon = '';
-    @Input() disabled = false;
-    @Input() color = 'primary';
-    @Input() class: string;
-    @Output() event = new EventEmitter();
-    @Input() title: string;
-
-    showConfirmation = false;
-
-    constructor() {}
-
-    confirmEvent() {
-        this.event.emit();
-        this.reset();
-    }
-
-    reset(): void {
-        this.showConfirmation = false;
-    }
-}
diff --git a/ui/src/app/shared/shared.module.ts b/ui/src/app/shared/shared.module.ts
index 1a2727a2b..f2150d4b4 100644
--- a/ui/src/app/shared/shared.module.ts
+++ b/ui/src/app/shared/shared.module.ts
@@ -4,7 +4,6 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms';
 import { RouterModule } from '@angular/router';
 import { NguiAutoCompleteModule } from '@sgu/auto-complete';
 import { TranslateModule } from '@ngx-translate/core';
-import { SuiModule } from '@richardlt/ng2-semantic-ui';
 import { NgxChartsModule } from '@swimlane/ngx-charts';
 import { AuthenticationGuard } from 'app/guard/authentication.guard';
 import { FeatureGuard } from 'app/guard/feature.guard';
@@ -31,7 +30,6 @@ import { ActionStepFormComponent } from './action/step/form/step.form.component'
 import { ActionStepComponent } from './action/step/step.component';
 import { AuditListComponent } from './audit/list/audit.list.component';
 import { BreadcrumbComponent } from './breadcrumb/breadcrumb.component';
-import { ConfirmButtonComponent } from './button/confirm/confirm.button';
 import { DeleteButtonComponent } from './button/delete/delete.button';
 import { UploadButtonComponent } from './button/upload/upload.button.component';
 import { ChartComponentComponent } from './chart/chart.component';
@@ -66,7 +64,6 @@ import { ScrollviewComponent } from './scrollview/scrollview.component';
 import { SharedService } from './shared.service';
 import { StatusIconComponent } from './status/status.component';
 import { DataTableComponent, SelectorPipe, SelectPipe } from './table/data-table.component';
-import { PaginationComponent } from './table/pagination.component';
 import { TabComponent } from './tabs/tab.component';
 import { TabsComponent } from './tabs/tabs.component';
 import { ToastService } from './toast/ToastService';
@@ -121,9 +118,9 @@ 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 { ArrowDownOutline, ArrowRightOutline, AudioOutline, AudioMutedOutline, BellFill, BookOutline, CalendarOutline, CaretDownFill, CaretRightFill, CaretUpFill, CheckOutline, CodeOutline, CopyOutline, DeleteOutline, DragOutline, EyeInvisibleOutline, EyeOutline, KeyOutline,
-    HighlightFill, HistoryOutline, InfoCircleOutline, PhoneFill, PlusOutline, PlusSquareOutline, QuestionOutline, QuestionCircleOutline, RestOutline, SaveOutline, SettingFill, ShareAltOutline, StarOutline, StarFill, StopOutline, SyncOutline,
-    TableOutline, TagsOutline, ToolFill, UnlockFill, UnorderedListOutline, UploadOutline, UserOutline, WarningOutline, WarningFill } from '@ant-design/icons-angular/icons'
+import { AimOutline, ArrowDownOutline, ArrowRightOutline, AudioOutline, AudioMutedOutline, BellFill, BookOutline, CalendarOutline, CaretDownFill, CaretRightFill, CaretUpFill, CheckOutline, CodeOutline, CopyOutline, CrownOutline, DeleteOutline, DragOutline, EyeInvisibleOutline, EyeOutline, FieldTimeOutline, KeyOutline,
+    HighlightFill, HistoryOutline, InfoCircleOutline, LockOutline, PhoneFill, PlayCircleOutline, PlusOutline, PlusSquareOutline, QuestionOutline, QuestionCircleOutline, RestOutline, SafetyCertificateOutline, SaveOutline, SettingFill, ShareAltOutline, StarOutline, StarFill, StopOutline, SyncOutline,
+    TableOutline, TagsOutline, ToolFill, UndoOutline, UnlockFill, UnorderedListOutline, UploadOutline, UserOutline, WarningOutline, WarningFill } 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';
@@ -153,9 +150,9 @@ const ngZorroConfig: NzConfig = {
     icon: {nzTheme: "outline"}
 };
 
-const icons: IconDefinition[] = [ ArrowDownOutline, ArrowRightOutline, AudioOutline, AudioMutedOutline, BellFill, BookOutline, CalendarOutline, CaretDownFill, CaretRightFill, CaretUpFill, CheckOutline, CodeOutline, CopyOutline, DeleteOutline, DragOutline,
-    EyeInvisibleOutline, EyeOutline, KeyOutline, HighlightFill, HistoryOutline, InfoCircleOutline, PhoneFill, PlusOutline, PlusSquareOutline, QuestionOutline, QuestionCircleOutline,
-    RestOutline, SaveOutline, SettingFill, ShareAltOutline, StarOutline, StarFill, StopOutline, SyncOutline, TableOutline, TagsOutline, ToolFill, UnlockFill, UnorderedListOutline, UploadOutline, UserOutline, WarningOutline, WarningFill ];
+const icons: IconDefinition[] = [ AimOutline, ArrowDownOutline, ArrowRightOutline, AudioOutline, AudioMutedOutline, BellFill, BookOutline, CalendarOutline, CaretDownFill, CaretRightFill, CaretUpFill, CheckOutline, CodeOutline, CopyOutline, CrownOutline, DeleteOutline, DragOutline,
+    EyeInvisibleOutline, EyeOutline, FieldTimeOutline, KeyOutline, HighlightFill, HistoryOutline, InfoCircleOutline, LockOutline, PhoneFill, PlayCircleOutline, PlusOutline, PlusSquareOutline, QuestionOutline, QuestionCircleOutline,
+    RestOutline, SafetyCertificateOutline, SaveOutline, SettingFill, ShareAltOutline, StarOutline, StarFill, StopOutline, SyncOutline, TableOutline, TagsOutline, ToolFill, UndoOutline, UnlockFill, UnorderedListOutline, UploadOutline, UserOutline, WarningOutline, WarningFill ];
 
 @NgModule({
     imports: [
@@ -201,7 +198,6 @@ const icons: IconDefinition[] = [ ArrowDownOutline, ArrowRightOutline, AudioOutl
         MarkdownModule.forRoot(),
         NguiAutoCompleteModule,
         RouterModule,
-        SuiModule,
         InfiniteScrollModule,
         NgxChartsModule
     ],
@@ -221,7 +217,6 @@ const icons: IconDefinition[] = [ ArrowDownOutline, ArrowRightOutline, AudioOutl
         ChartComponentComponent,
         CommitListComponent,
         ConditionsComponent,
-        ConfirmButtonComponent,
         CutPipe,
         DataTableComponent,
         DeleteButtonComponent,
@@ -237,7 +232,6 @@ const icons: IconDefinition[] = [ ArrowDownOutline, ArrowRightOutline, AudioOutl
         MenuComponent,
         NgForNumber,
         NgxAutoScrollDirective,
-        PaginationComponent,
         ParameterFormComponent,
         ParameterListComponent,
         ParameterValueComponent,
@@ -333,7 +327,6 @@ const icons: IconDefinition[] = [ ArrowDownOutline, ArrowRightOutline, AudioOutl
         CommitListComponent,
         CommonModule,
         ConditionsComponent,
-        ConfirmButtonComponent,
         CutPipe,
         DataTableComponent,
         DeleteButtonComponent,
@@ -385,7 +378,6 @@ const icons: IconDefinition[] = [ ArrowDownOutline, ArrowRightOutline, AudioOutl
         NzTableModule,
         NzToolTipModule,
         NzUploadModule,
-        PaginationComponent,
         ParameterFormComponent,
         ParameterListComponent,
         ParameterValueComponent,
@@ -403,7 +395,6 @@ const icons: IconDefinition[] = [ ArrowDownOutline, ArrowRightOutline, AudioOutl
         SelectorPipe,
         SelectPipe,
         StatusIconComponent,
-        SuiModule,
         TabsComponent,
         TranslateModule,
         TruncatePipe,
diff --git a/ui/src/app/shared/table/data-table.html b/ui/src/app/shared/table/data-table.html
index c29568f1e..1440ae4d1 100644
--- a/ui/src/app/shared/table/data-table.html
+++ b/ui/src/app/shared/table/data-table.html
@@ -1,143 +1,107 @@
-<div *ngIf="filterFunc && allData && allData.length > 1" class="ui grid">
-    <div class="ui row">
-        <div class="ui wide column">
-            <div class="ui fluid input">
-                <input type="text" placeholder="{{ 'filter' | translate }}" [(ngModel)]="filter"
-                    (keyup)="filterKeyUp()">
-            </div>
-        </div>
-    </div>
-</div>
-<table class="ui striped fixed table" [class.selectable]="withLineClick">
+<nz-row *ngIf="filterFunc && allData && allData.length > 1">
+        <nz-col [nzSpan]="24">
+            <input nz-input type="text" placeholder="Filter" [(ngModel)]="filter"
+                   (keyup)="filterKeyUp()">
+        </nz-col>
+</nz-row>
+<nz-table [nzLoading]="loading" [nzData]="filteredData" #table>
     <thead>
-        <tr>
-            <th *ngIf="withSelect" class="wide">
-                <div class="ui checked checkbox">
-                    <input type="checkbox" [ngModel]="selectedAll" (ngModelChange)="onSelectAllChange($event)">
-                    <label></label>
-                </div>
+    <tr>
+        <th *ngIf="withSelect" class="wide">
+            <label nz-checkbox [ngModel]="selectedAll" (ngModelChange)="onSelectAllChange($event)"></label>
+        </th>
+        <ng-container *ngFor="let c of columns">
+            <th *ngIf="!c.disabled" class="wide {{c.class}}" [class.sortable]="c.sortable && c !== sortedColumn"
+                [class.ascending]="c === sortedColumn && sortedColumnDirection === 'asc'"
+                [class.descending]="c === sortedColumn && sortedColumnDirection === 'desc'"
+                (click)="columnClick($event, c)">
+                {{ c.name | translate }}
             </th>
-            <ng-container *ngFor="let c of columns">
-                <th *ngIf="!c.disabled" class="wide {{c.class}}" [class.sortable]="c.sortable && c !== sortedColumn"
-                    [class.ascending]="c === sortedColumn && sortedColumnDirection === 'asc'"
-                    [class.descending]="c === sortedColumn && sortedColumnDirection === 'desc'"
-                    (click)="columnClick($event, c)">
-                    {{ c.name | translate }}
-                </th>
-            </ng-container>
-        </tr>
+        </ng-container>
+    </tr>
     </thead>
-    <tbody *ngIf="loading">
-        <tr>
-            <td class="center aligned" [colSpan]="columnsCount">
-                <div class="ui active small inline loader"></div>
-                <div>{{ 'common_loading' | translate }}</div>
-            </td>
-        </tr>
-    </tbody>
-    <tbody *ngIf="!loading && dataForCurrentPage.length === 0">
-        <tr>
-            <td class="center" [colSpan]="columnsCount">
-                <div>{{ 'common_no_data' | translate }}</div>
-            </td>
-        </tr>
-    </tbody>
     <tbody *ngIf="!loading && dataForCurrentPage.length > 0">
-        <tr *ngFor="let d of dataForCurrentPage; let i = index" (click)="lineClick(i, d)"
-            [class.pointing]="withLineClick" [class.active]="withLineClick && indexSelected === i">
-            <td *ngIf="withSelect" class="wide">
-                <div class="ui checked checkbox">
-                    <input type="checkbox" [ngModel]="selected[d.key()]"
-                        (ngModelChange)="onSelectChange($event, d.key())">
-                    <label></label>
-                </div>
-            </td>
-            <ng-container *ngFor="let c of (columns | selector:d:i)">
-                <td *ngIf="!c.disabled && c.hidden"></td>
-                <td *ngIf="!c.disabled && !c.hidden" [ngSwitch]="c.type" class="wide" [ngClass]="c.class">
-                    <i *ngSwitchCase="'icon'" [ngClass]="c.selector.icon" [title]="c.selector.title"></i>
-                    <a *ngSwitchCase="'router-link'" class="ui" [routerLink]="c.selector.link">
-                        {{c.selector.value}}
-                    </a>
-                    <a *ngSwitchCase="'link-click'" class="ui pointing" (click)="c.selector.callback()">
-                        {{c.selector.value}}
-                    </a>
-                    <a *ngSwitchCase="'link'" class="ui pointing" [href]="c.selector.link" target="_blank">
-                        {{c.selector.value}}
-                    </a>
-                    <markdown *ngSwitchCase="'markdown'" [data]="c.selector"></markdown>
-                    <div *ngSwitchCase="'img-text'" class="vcenter">
-                        <img *ngIf="c.selector.img" [src]="c.selector.img">
-                        <div class="{{c.selector.valueclass}}">
-                            {{ c.selector.value }}
-                        </div>
+    <tr *ngFor="let d of table.data; let i = index" (click)="lineClick(i, d)"
+        [class.pointing]="withLineClick" [class.active]="withLineClick && indexSelected === i">
+        <td *ngIf="withSelect" class="wide">
+            <label nz-checkbox [ngModel]="selected[d.key()]" (ngModelChange)="onSelectChange($event, d.key())"></label>
+        </td>
+        <ng-container *ngFor="let c of (columns | selector:d:i)">
+            <td *ngIf="!c.disabled && c.hidden"></td>
+            <td *ngIf="!c.disabled && !c.hidden" [ngSwitch]="c.type" class="wide" [ngClass]="c.class">
+                <ng-container *ngSwitchCase="'text-html'">
+                    <pre [innerHTML]="c.selector"></pre>
+                </ng-container>
+                <i *ngSwitchCase="'icon'" nz-icon nzType="{{c.selector.iconType}}" nzTheme="{{c.selector.iconTheme}}" class="{{c.selector.iconColor}}"></i>
+                <div *ngSwitchCase="'img-text'" class="vcenter">
+                    <img *ngIf="c.selector.img" [src]="c.selector.img">
+                    <div class="{{c.selector.valueclass}}">
+                        {{ c.selector.value }}
                     </div>
-                    <ng-container *ngSwitchCase="'text-html'">
-                        <pre [innerHTML]="c.selector"></pre>
-                    </ng-container>
-                    <ng-container *ngSwitchCase="'time-ago'"> {{c.selector | amTimeAgo}} </ng-container>
-                    <ng-container *ngSwitchCase="'date'"> {{c.selector | amCalendar}} </ng-container>
-                    <button class="ui button" [ngClass]="c.selector.class" *ngSwitchCase="'button'"
-                        (click)="c.selector.click()">
-                        {{c.selector.title | translate}}
-                        <i class="icon" [ngClass]="c.selector.icon" *ngIf="c.selector.icon"></i>
-                    </button>
-                    <app-confirm-button *ngSwitchCase="'confirm-button'" [class]="'small'"
-                        [color]="c.selector.color ? c.selector.color : 'primary'" (event)="c.selector.click()"
-                        [title]="c.selector.title">
-                    </app-confirm-button>
-                    <span class="ui label" [ngClass]="c.selector.class"
-                        *ngSwitchCase="'label'">{{c.selector.value | translate}}</span>
-                    <ng-container *ngSwitchCase="'router-link-with-icons'">
-                        <a class="ui" [routerLink]="c.selector.link">{{c.selector.value}}</a>
-                        <span *ngFor="let i of c.selector.icons" nz-tooltip [nzTooltipTitle]="i.label | translate">
-                            {{' '}}<i [ngClass]="i.class" title="{{i.title | translate }}"></i>
+                </div>
+                <a *ngSwitchCase="'link-click'" (click)="c.selector.callback()">
+                    {{c.selector.value}}
+                </a>
+                <a *ngSwitchCase="'link'" [href]="c.selector.link" target="_blank">
+                    {{c.selector.value}}
+                </a>
+                <a *ngSwitchCase="'router-link'" [routerLink]="c.selector.link">
+                    {{c.selector.value}}
+                </a>
+                <ng-container *ngSwitchCase="'router-link-with-icons'">
+                    <a [routerLink]="c.selector.link">{{c.selector.value}}</a>
+                    <span *ngFor="let i of c.selector.icons" nz-tooltip [nzTooltipTitle]="i.label | translate">
+                            {{' '}}<i nz-icon nzType="{{i.iconType}}" nzTheme="{{i.iconTheme}}" class="{{i.iconColor}}"></i>
                         </span>
+                </ng-container>
+                <ng-container *ngSwitchCase="'router-link-with-labels'">
+                    <a [routerLink]="c.selector.link">{{c.selector.value}}</a>
+                    {{' '}}<nz-tag *ngFor="let l of c.selector.labels" nzColor="{{l.color}}">{{l.title | translate}}</nz-tag>
+                </ng-container>
+                <markdown *ngSwitchCase="'markdown'" [data]="c.selector"></markdown>
+                <ng-container *ngSwitchCase="'date'"> {{c.selector | amCalendar}} </ng-container>
+                <button *ngSwitchCase="'button'" nz-button nzType="{{c.selector.buttonType}}" [nzDanger]="c.selector.buttonDanger" class="{{c.class}}"
+                        (click)="c.selector.click()">
+                    <i nz-icon nzType="{{c.selector.iconType}}" nzTheme="{{c.selector.iconTheme}}" class="{{c.selector.iconColor}}"></i>
+                    {{c.selector.title | translate}}
+
+                </button>
+                <button *ngSwitchCase="'confirm-button'" class="{{c.class}}"
+                        nz-button nzType="{{c.selector.buttonType}}" [nzDanger]="c.selector.buttonDanger"
+                        nz-popconfirm nzPopconfirmTitle="{{c.selector.buttonConfirmationMessage}}" (nzOnConfirm)="c.selector.click()">{{c.selector.title}}</button>
+                <nz-tag nzColor="{{c.selector.class}}" *ngSwitchCase="'label'" class="{{c.class}}" >{{c.selector.value | translate}}</nz-tag>
+                <div *ngSwitchCase="'text-copy'">
+                    <ng-container *ngIf="c.selector">
+                        <nz-input-group nzSearch [nzAddOnAfter]="suffixIconButton">
+                            <input nz-input type="text" value="{{c.selector}}"  />
+                        </nz-input-group>
+                        <ng-template #suffixIconButton>
+                            <button nzSearch nz-button nzType="primary" ngxClipboard [cbContent]="c.selector"><i nz-icon nzType="copy"></i></button>
+                        </ng-template>
                     </ng-container>
-                    <ng-container *ngSwitchCase="'router-link-with-labels'">
-                        <a class="ui" [routerLink]="c.selector.link">{{c.selector.value}}</a>
-                        {{' '}}<span *ngFor="let l of c.selector.labels" class="ui label {{l.color}}">{{l.title | translate}}</span>
-                    </ng-container>
-                    <div *ngSwitchCase="'text-copy'" class="ui fluid action input">
-                        <ng-container *ngIf="c.selector">
-                            <input type="text" value="{{c.selector}}">
-                            <button class="ui icon button copyButton" [title]="'common_copy_clipboard' | translate"
-                                    ngxClipboard [cbContent]="c.selector">
-                                <i class="copy icon"></i>
-                            </button>
-                        </ng-container>
-                    </div>
-                    <span *ngSwitchCase="'text-labels'" class="text-labels">
+                </div>
+                <span *ngSwitchCase="'text-labels'" class="text-labels">
                         {{c.selector.value |
-                                translate}}
-                        <span *ngFor="let l of c.selector.labels"
-                            class="ui label {{l.color}}">{{l.title | translate}}</span>
-                    </span>
-                    <span *ngSwitchCase="'text-icons'">
+                    translate}}
+                    <nz-tag *ngFor="let l of c.selector.labels" nzColor="{{l.color}}">{{l.title | translate}}</nz-tag>
+                </span>
+                <span *ngSwitchCase="'text-icons'">
                         {{c.selector.value | translate}}
-                        <span *ngFor="let i of c.selector.icons" nz-tooltip [nzTooltipTitle]="i.label | translate" nzTooltipTrigger="{{i.trigger ? i.trigger : 'hover'}}">
-                            {{' '}}<i [ngClass]="i.class" title="{{i.title | translate }}"></i>
+                    <span *ngFor="let i of c.selector.icons" nz-tooltip [nzTooltipTitle]="i.label | translate" nzTooltipTrigger="{{i.trigger ? i.trigger : 'hover'}}">
+                            {{' '}}<i *ngIf="i.type" nz-icon nzType="{{i.type}}" nzTheme="{{i.theme}}" class="{{i.class}}"></i>
                         </span>
-                    </span>
-                    <ng-container *ngSwitchDefault>
-                        <ng-container *ngIf="c.selector && c.translate">
-                            {{c.selector | translate}}
-                        </ng-container>
-                        <ng-container *ngIf="c.selector && !c.translate">
-                            {{c.selector}}
-                        </ng-container>
+                </span>
+                <ng-container *ngSwitchCase="'time-ago'"> {{c.selector | amTimeAgo}} </ng-container>
+                <ng-container *ngSwitchDefault>
+                    <ng-container *ngIf="c.selector && c.translate">
+                        {{c.selector | translate}}
                     </ng-container>
-                </td>
-            </ng-container>
-        </tr>
-    </tbody>
-    <tfoot tfoot *ngIf="nbElementsByPage && pagesCount > 1">
-        <tr>
-            <td [colSpan]="columnsCount">
-                <app-pagination [collectionSize]="filteredData.length" [pageSize]="nbElementsByPage"
-                    (pageChange)="pageChange($event)" [page]="currentPage">
-                </app-pagination>
+                    <ng-container *ngIf="c.selector && !c.translate">
+                        {{c.selector}}
+                    </ng-container>
+                </ng-container>
             </td>
-        </tr>
-    </tfoot>
-</table>
+        </ng-container>
+    </tr>
+    </tbody>
+</nz-table>
diff --git a/ui/src/app/shared/table/data-table.scss b/ui/src/app/shared/table/data-table.scss
index 68e310298..0c0aa1ab0 100644
--- a/ui/src/app/shared/table/data-table.scss
+++ b/ui/src/app/shared/table/data-table.scss
@@ -1,5 +1,8 @@
 @import '../../../common';
-
+nz-row {
+  margin-top: 10px;
+  margin-bottom: 10px;
+}
 th.sortable:after {
     font-family: Icons;
     content: '\f0dc';
@@ -47,4 +50,36 @@ pre {
     white-space: pre-wrap;
     word-wrap: break-word;
     font-family: 'Lato', 'Helvetica Neue', Arial, Helvetica, sans-serif;
-}
\ No newline at end of file
+}
+
+.green {
+  color: $cds_color_green;
+}
+.red {
+  color: $cds_color_red;
+}
+.blue {
+  color: $cds_color_teal;
+}
+.grey {
+  color: $cds_trigger_color;
+}
+
+.orange {
+  color: $cds_color_orange;
+}
+
+td {
+  i {
+    font-size: 1.3em;
+  }
+  button {
+    i {
+      font-size: 1em;
+    }
+  }
+
+  .rightAlign {
+    float: right;
+  }
+}
diff --git a/ui/src/app/shared/table/pagination.component.ts b/ui/src/app/shared/table/pagination.component.ts
deleted file mode 100644
index 7bae69c9e..000000000
--- a/ui/src/app/shared/table/pagination.component.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-import {
-    AfterViewInit,
-    ChangeDetectionStrategy,
-    ChangeDetectorRef,
-    Component,
-    ElementRef,
-    EventEmitter,
-    HostListener,
-    Input,
-    OnChanges,
-    Output,
-    ViewChild
-} from '@angular/core';
-
-@Component({
-    selector: 'app-pagination',
-    templateUrl: './pagination.html',
-    styleUrls: ['./pagination.scss'],
-    changeDetection: ChangeDetectionStrategy.OnPush
-})
-export class PaginationComponent implements OnChanges, AfterViewInit {
-    @ViewChild('paginationWrapper') paginationWrapper: ElementRef;
-    @Input() collectionSize: number;
-    @Input() pageSize: number;
-    @Input() page: number;
-    @Output() pageChange = new EventEmitter<number>();
-
-    maxSize = 10;
-
-    constructor(public _cd: ChangeDetectorRef) { }
-
-    ngAfterViewInit() {
-        this.resize();
-    }
-
-    ngOnChanges() {
-        this.resize();
-    }
-
-    onPageChange(n: number) {
-        this.pageChange.emit(n);
-    }
-
-    @HostListener('window:resize', ['$event'])
-    onResize(event) {
-        this.resize();
-    }
-
-    resize() {
-        if (this.paginationWrapper) {
-            let wrapperWidth = this.paginationWrapper.nativeElement.clientWidth;
-            // 75px is approximately the size of a button, 4 buttons are removed (left, right and ellipsis)
-            let maxPage = Math.trunc(wrapperWidth / 65) - 4;
-            this.maxSize = maxPage < 10 ? maxPage : 10;
-            this.maxSize = this.maxSize < 1 ? 1 : this.maxSize;
-            this._cd.detectChanges();
-        }
-    }
-}
diff --git a/ui/src/app/shared/table/pagination.html b/ui/src/app/shared/table/pagination.html
deleted file mode 100644
index 26c5a5292..000000000
--- a/ui/src/app/shared/table/pagination.html
+++ /dev/null
@@ -1,5 +0,0 @@
-<div #paginationWrapper>
-    <sui-pagination class="ui right floated" [collectionSize]="collectionSize" [pageSize]="pageSize" [maxSize]="maxSize"
-        [hasEllipses]="true" (pageChange)="onPageChange($event)" [page]="page">
-    </sui-pagination>
-</div>
\ No newline at end of file
diff --git a/ui/src/app/shared/table/pagination.scss b/ui/src/app/shared/table/pagination.scss
deleted file mode 100644
index 0f190bcc8..000000000
--- a/ui/src/app/shared/table/pagination.scss
+++ /dev/null
@@ -1,1 +0,0 @@
-@import '../../../common';
\ No newline at end of file
diff --git a/ui/src/app/shared/variable/list/variable.html b/ui/src/app/shared/variable/list/variable.html
index d5fe4c428..dde7da0ec 100644
--- a/ui/src/app/shared/variable/list/variable.html
+++ b/ui/src/app/shared/variable/list/variable.html
@@ -1,5 +1,5 @@
 <div class="variable" *ngIf="ready && variableTypes">
-    <nz-row class="ui grid">
+    <nz-row>
         <nz-col [nzOffset]="20" [nzSpan]="4" class="alignRight">
             <input nz-input type="text" placeholder="Filter" [(ngModel)]="filter" (ngModelChange)="filterVariables()">
         </nz-col>
diff --git a/ui/src/app/shared/vcs/vcs.strategy.html b/ui/src/app/shared/vcs/vcs.strategy.html
index ff61f49a2..1319101e7 100644
--- a/ui/src/app/shared/vcs/vcs.strategy.html
+++ b/ui/src/app/shared/vcs/vcs.strategy.html
@@ -35,13 +35,13 @@
             <nz-form-item>
                 <nz-form-label [nzSpan]="3">User</nz-form-label>
                 <nz-form-control [nzSpan]="7">
-                    <input nz-input name="user" type="text" class="ui input" [(ngModel)]="strategy.user">
+                    <input nz-input name="user" type="text" [(ngModel)]="strategy.user">
                 </nz-form-control>
             </nz-form-item>
             <nz-form-item>
                 <nz-form-label [nzSpan]="3">Password</nz-form-label>
                 <nz-form-control [nzSpan]="7">
-                    <input nz-input name="password" type="password" class="ui input" [(ngModel)]="strategy.password">
+                    <input nz-input name="password" type="password" [(ngModel)]="strategy.password">
                 </nz-form-control>
             </nz-form-item>
         </ng-container>
diff --git a/ui/src/app/shared/workflow-template/bulk-modal/workflow-template.bulk-modal.component.ts b/ui/src/app/shared/workflow-template/bulk-modal/workflow-template.bulk-modal.component.ts
index a1d635c80..2a39955a4 100644
--- a/ui/src/app/shared/workflow-template/bulk-modal/workflow-template.bulk-modal.component.ts
+++ b/ui/src/app/shared/workflow-template/bulk-modal/workflow-template.bulk-modal.component.ts
@@ -59,13 +59,13 @@ export class WorkflowTemplateBulkModalComponent implements OnInit, OnDestroy {
     ) {
         this.columnsInstances = [
             <Column<WorkflowTemplateInstance>>{
-                name: 'common_workflow',
+                name: 'Workflow',
                 class: 'ten',
                 selector: (i: WorkflowTemplateInstance) => i.key()
             }, <Column<WorkflowTemplateInstance>>{
                 type: ColumnType.LABEL,
-                name: 'common_status',
-                class: 'five right aligned',
+                name: 'Status',
+                class: 'rightAlign',
                 selector: (i: WorkflowTemplateInstance) => {
                     let status = i.status(this.workflowTemplate);
                     return {
@@ -85,8 +85,8 @@ export class WorkflowTemplateBulkModalComponent implements OnInit, OnDestroy {
                 selector: (i: WorkflowTemplateBulkOperation) => i.error
             }, <Column<WorkflowTemplateBulkOperation>>{
                 type: ColumnType.LABEL,
-                name: 'common_status',
-                class: 'right aligned',
+                name: 'Status',
+                class: 'rightAlign',
                 selector: (i: WorkflowTemplateBulkOperation) => ({
                         class: OperationStatusUtil.color(i.status),
                         value: OperationStatusUtil.translate(i.status)
diff --git a/ui/src/app/shared/workflow/menu/edit-hook/menu.edit.hook.component.ts b/ui/src/app/shared/workflow/menu/edit-hook/menu.edit.hook.component.ts
index b5008b863..bd3c049a5 100644
--- a/ui/src/app/shared/workflow/menu/edit-hook/menu.edit.hook.component.ts
+++ b/ui/src/app/shared/workflow/menu/edit-hook/menu.edit.hook.component.ts
@@ -1,6 +1,5 @@
 import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core';
 import { Store } from '@ngxs/store';
-import { IPopup } from '@richardlt/ng2-semantic-ui';
 import { WNodeHook, Workflow } from 'app/model/workflow.model';
 import { WorkflowNodeRunHookEvent } from 'app/model/workflow.run.model';
 import { AutoUnsubscribe } from 'app/shared/decorator/autoUnsubscribe';
diff --git a/ui/src/app/views/admin/hook-task/list/hook-task.list.component.ts b/ui/src/app/views/admin/hook-task/list/hook-task.list.component.ts
index a53bb553b..989874ea8 100644
--- a/ui/src/app/views/admin/hook-task/list/hook-task.list.component.ts
+++ b/ui/src/app/views/admin/hook-task/list/hook-task.list.component.ts
@@ -1,5 +1,5 @@
 import { ChangeDetectionStrategy, ChangeDetectorRef, Component } from '@angular/core';
-import { WorkflowHookTask } from 'app/model/workflow.hook.model';
+import { HookStatus, WorkflowHookTask } from 'app/model/workflow.hook.model';
 import { HookService } from 'app/service/hook/hook.service';
 import { PathItem } from 'app/shared/breadcrumb/breadcrumb.component';
 import { Column, ColumnType, Filter } from 'app/shared/table/data-table.component';
@@ -42,7 +42,9 @@ export class HookTaskListComponent {
             <Column<WorkflowHookTask>>{
                 type: ColumnType.ICON,
                 selector: (d: WorkflowHookTask) => ({
-                    icon: d.stopped ? ['stop', 'red', 'icon'] : ['play', 'green', 'icon']
+                    iconTheme: 'outline',
+                    iconType: d.stopped ? 'stop' : 'play-circle',
+                    iconColor: d.stopped ? 'red' : 'green'
                 })
             },
             <Column<WorkflowHookTask>>{
diff --git a/ui/src/app/views/admin/hook-task/show/hook-task.show.component.ts b/ui/src/app/views/admin/hook-task/show/hook-task.show.component.ts
index 1a1bf52af..dc52d8005 100644
--- a/ui/src/app/views/admin/hook-task/show/hook-task.show.component.ts
+++ b/ui/src/app/views/admin/hook-task/show/hook-task.show.component.ts
@@ -42,13 +42,23 @@ export class HookTaskShowComponent {
             <Column<TaskExecution>>{
                 type: ColumnType.ICON,
                 selector: (d: TaskExecution) => ({
-                    icon: (() => {
+                    iconTheme: 'outline',
+                    iconType: (() => {
                         if (d.status === HookStatus.DONE) {
-                            return ['check', 'green', 'icon'];
+                            return 'check';
                         } else if (d.status === HookStatus.FAIL) {
-                            return ['ban', 'red', 'icon'];
+                            return 'stop';
                         } else {
-                            return ['wait', 'blue', 'icon'];
+                            return 'field-time';
+                        }
+                    })(),
+                    iconColor: (() => {
+                        if (d.status === HookStatus.DONE) {
+                            return 'green';
+                        } else if (d.status === HookStatus.FAIL) {
+                            return 'red';
+                        } else {
+                            return 'blue';
                         }
                     })()
                 })
diff --git a/ui/src/app/views/admin/service/list/service.list.component.ts b/ui/src/app/views/admin/service/list/service.list.component.ts
index 357ddcf35..e3ff637ae 100644
--- a/ui/src/app/views/admin/service/list/service.list.component.ts
+++ b/ui/src/app/views/admin/service/list/service.list.component.ts
@@ -53,7 +53,7 @@ export class ServiceListComponent {
                 selector: (c: MonitoringStatusLine) => c.hostname
             },
             <Column<MonitoringStatusLine>>{
-                name: 'common_status',
+                name: 'Status',
                 type: ColumnType.LABEL,
                 selector: (c: MonitoringStatusLine) => ({
                     class: MonitoringStatusLineUtil.color(c),
diff --git a/ui/src/app/views/admin/service/show/service.show.component.ts b/ui/src/app/views/admin/service/show/service.show.component.ts
index 9c116dbef..eb76d4f9f 100644
--- a/ui/src/app/views/admin/service/show/service.show.component.ts
+++ b/ui/src/app/views/admin/service/show/service.show.component.ts
@@ -63,7 +63,7 @@ export class ServiceShowComponent implements OnInit, OnDestroy {
                 selector: (c: MonitoringStatusLine) => c.hostname
             },
             <Column<MonitoringStatusLine>>{
-                name: 'common_status',
+                name: 'Status',
                 type: ColumnType.LABEL,
                 selector: (c: MonitoringStatusLine) => ({
                         class: MonitoringStatusLineUtil.color(c),
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 a7dee9589..05de02b28 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
@@ -29,7 +29,7 @@
                                        *ngIf="selectedIntegration.model.additional_default_config[k].type === 'string'">
                                 <input nz-input name="{{k}}-value" type="password" [(ngModel)]="selectedIntegration.model.additional_default_config[k].value"
                                        *ngIf="selectedIntegration.model.additional_default_config[k].type === 'password'">
-                                <textarea nz-input name="{{k}}-value" class="ui textarea" rows="3" [(ngModel)]="selectedIntegration.model.additional_default_config[k].value"
+                                <textarea nz-input name="{{k}}-value" rows="3" [(ngModel)]="selectedIntegration.model.additional_default_config[k].value"
                                           *ngIf="selectedIntegration.model.additional_default_config[k].type === 'text'"></textarea>
                             </nz-form-control>
                         </nz-form-item>
@@ -74,7 +74,7 @@
                                                    *ngIf="application.deployment_strategies[pf][k].type === 'string'">
                                             <input nz-input type="password" name="{{k}}-value" [(ngModel)]="application.deployment_strategies[pf][k].value"
                                                    *ngIf="application.deployment_strategies[pf][k].type === 'password'">
-                                            <textarea nz-input class="ui textarea" name="{{k}}-value" rows="3" [(ngModel)]="application.deployment_strategies[pf][k].value"
+                                            <textarea nz-input name="{{k}}-value" rows="3" [(ngModel)]="application.deployment_strategies[pf][k].value"
                                                       *ngIf="application.deployment_strategies[pf][k].type === 'text'"></textarea>
                                         </nz-form-control>
                                     </nz-form-item>
diff --git a/ui/src/app/views/application/show/application.html b/ui/src/app/views/application/show/application.html
index b0817bc90..4ffde79eb 100644
--- a/ui/src/app/views/application/show/application.html
+++ b/ui/src/app/views/application/show/application.html
@@ -69,9 +69,7 @@
                                     [environments]="environments"></app-usage>
                             </ng-container>
                             <ng-container *ngIf="usageCount === 0">
-                                <div class="ui info message">
-                                    {{ 'application_no_usage' | translate }}
-                                </div>
+                                <nz-alert nzType="info" nzMessage="The application is not used"></nz-alert>
                             </ng-container>
                         </div>
                         <div *ngSwitchCase="'keys'">
@@ -89,5 +87,5 @@
     </div>
 </ng-template>
 <ng-template #loadApp>
-    <div class="ui text active loader">Loading application...</div>
+    <nz-spin nzTip="Loading application..."></nz-spin>
 </ng-template>
diff --git a/ui/src/app/views/application/show/home/application.home.html b/ui/src/app/views/application/show/home/application.home.html
index bea717660..866b3ee16 100644
--- a/ui/src/app/views/application/show/home/application.home.html
+++ b/ui/src/app/views/application/show/home/application.home.html
@@ -1,5 +1,5 @@
-<div class="ui grid">
-    <div class="ui twelve wide column">
+<nz-row>
+    <nz-col [nzSpan]="17">
         <ng-container *ngIf="dashboards && dashboards.length > 0">
             <ng-container *ngFor="let d of dashboards">
                 <div class="dashboard">
@@ -11,17 +11,15 @@
             </ng-container>
         </ng-container>
         <ng-container *ngIf="dashboards && dashboards.length === 0">
-            <div class="ui info message">
-                {{ 'application_home_no_graph' | translate }}
-            </div>
+            <nz-alert nzType="info" nzMessage="No graph to display (vulnerabilities, unit tests)"></nz-alert>
         </ng-container>
         <ng-container *ngIf="!dashboards">
-            <div class="ui text active loader">{{ 'common_loading' | translate }}</div>
+            <nz-spin nzTip="Loading..."></nz-spin>
         </ng-container>
-    </div>
-    <div class="ui four wide column">
-        <div class="ui segment">
-            <h4>{{ 'application_home_repo' | translate }}</h4>
+    </nz-col>
+    <nz-col [nzSpan]="6" [nzOffset]="1">
+        <div class="nzSegment">
+            <h4>Git repository</h4>
             <ng-container *ngIf="application.vcs_server">
                 <ul>
                     <li *ngIf="overview">
@@ -35,23 +33,23 @@
                 </ul>
             </ng-container>
             <ng-container *ngIf="!application.vcs_server">
-                <div class="ui info message">
+                <nz-alert nzType="info" [nzMessage]="gitTmpl"></nz-alert>
+                <ng-template #gitTmpl>
                     <a [routerLink]="['/project', project.key, 'application', application.name]"
-                        [queryParams]="{tab: 'advanced'}">{{
-                        'application_home_no_repo' | translate }}</a>
-                </div>
+                        [queryParams]="{tab: 'advanced'}">Link a repository</a>
+                </ng-template>
             </ng-container>
-            <h4>{{ 'application_home_integration' | translate }}</h4>
+            <h4>Linked integrations</h4>
             <ul *ngIf="application.deployment_strategies">
                 <li *ngFor="let p of application.deployment_strategies | keys">
                     {{ p }}
                 </li>
             </ul>
-            <div class="ui info message" *ngIf="!application.deployment_strategies">
+            <nz-alert *ngIf="!application.deployment_strategies" nzType="info" [nzMessage]="integTmpl"></nz-alert>
+            <ng-template #integTmpl>
                 <a [routerLink]="['/project', project.key,'application', application.name]"
-                    [queryParams]="{tab: 'advanced'}">{{
-                    'application_home_no_integration' | translate }}</a>
-            </div>
+                    [queryParams]="{tab: 'advanced'}">Link an integration</a>
+            </ng-template>
             <h4>Usage</h4>
             <ng-container *ngIf="application.usage && application.usage.workflows">
                 <ul>
@@ -62,14 +60,11 @@
             </ng-container>
             <ng-container
                 *ngIf="!application.usage || !application.usage.workflows || application.usage.workflows.length === 0">
-                <div class="ui info message">
-                    {{ 'application_home_no_usage' | translate }}
-                </div>
+                <nz-alert nzType="info" nzMessage="Application is not used"></nz-alert>
             </ng-container>
-
         </div>
-    </div>
-</div>
+    </nz-col>
+</nz-row>
 
 <!-- HISTORY -->
 <div class="history" *ngIf="overview?.history">
diff --git a/ui/src/app/views/application/show/home/application.home.scss b/ui/src/app/views/application/show/home/application.home.scss
index c5f940ef9..99739149a 100644
--- a/ui/src/app/views/application/show/home/application.home.scss
+++ b/ui/src/app/views/application/show/home/application.home.scss
@@ -1,5 +1,12 @@
 @import '../../../../../common';
+.nzSegment {
+  padding: 20px 20px;
+}
 
+nz-alert {
+  margin-top: 5px;
+  margin-bottom: 10px;
+}
 .dashboard {
   text-align: center;
   margin-bottom: 20px;
@@ -24,4 +31,4 @@ i {
       background-color: grey;
     }
   }
-}
\ No newline at end of file
+}
diff --git a/ui/src/app/views/pipeline/show/audit/pipeline.audit.component.ts b/ui/src/app/views/pipeline/show/audit/pipeline.audit.component.ts
index 4d63ffb59..3435e9378 100644
--- a/ui/src/app/views/pipeline/show/audit/pipeline.audit.component.ts
+++ b/ui/src/app/views/pipeline/show/audit/pipeline.audit.component.ts
@@ -80,9 +80,12 @@ export class PipelineAuditComponent implements OnInit {
                 name: '',
                 disabled: !this.project.permissions.writable,
                 selector: (audit: PipelineAudit) => ({
-                        title: 'common_rollback',
-                        click: () => this.rollback(audit.id)
-                    }),
+                    buttonType: 'primary',
+                    buttonDanger: false,
+                    buttonConfirmationMessage: 'Are you sure you want to rollback this pipeline ?',
+                    title: 'Rollback',
+                    click: () => this.rollback(audit.id)
+                }),
             },
         ];
     }
diff --git a/ui/src/app/views/project/show/keys/project.keys.html b/ui/src/app/views/project/show/keys/project.keys.html
index abf6e84f1..f12f2e0bd 100644
--- a/ui/src/app/views/project/show/keys/project.keys.html
+++ b/ui/src/app/views/project/show/keys/project.keys.html
@@ -3,9 +3,7 @@
     <ng-container *ngIf="project.keys && project.keys.length > 0">
         <app-keys-list [keys]="keys" [edit]="project.permissions.writable" (deleteEvent)="manageKeyEvent($event)"></app-keys-list>
     </ng-container>
-    <div class="ui info message" *ngIf="!loading && (!project.keys || project.keys.length === 0)">
-        {{ 'keys_no' | translate }}
-    </div>
+    <nz-alert nzType="info" nzMessage="There is no keys" *ngIf="!loading && (!project.keys || project.keys.length === 0)"></nz-alert>
     <h3 *ngIf="project.permissions.writable">
         {{ 'keys_add_title' | translate }}
     </h3>
@@ -13,4 +11,4 @@
         <app-keys-form [loading]="loading" (keyEvent)="manageKeyEvent($event)" prefix="proj-"></app-keys-form>
     </ng-container>
 </div>
-<div class="ui text active loader" *ngIf="!ready">{{ 'keys_loading' | translate}}</div>
+<nz-spin  *ngIf="!ready" nzTip="Loading..."></nz-spin>
diff --git a/ui/src/app/views/project/show/keys/project.keys.scss b/ui/src/app/views/project/show/keys/project.keys.scss
index e69de29bb..6f3d5d4f8 100644
--- a/ui/src/app/views/project/show/keys/project.keys.scss
+++ b/ui/src/app/views/project/show/keys/project.keys.scss
@@ -0,0 +1,3 @@
+nz-alert {
+  margin-bottom: 10px;
+}
diff --git a/ui/src/app/views/project/show/pipeline/pipeline.list.html b/ui/src/app/views/project/show/pipeline/pipeline.list.html
index 291a7a84c..179125972 100644
--- a/ui/src/app/views/project/show/pipeline/pipeline.list.html
+++ b/ui/src/app/views/project/show/pipeline/pipeline.list.html
@@ -18,7 +18,7 @@
             <app-card [title]="pip.name" [description]="pip.description"></app-card>
         </nz-col>
     </nz-row>
-    <nz-row *ngIf="!project.application_names || project.application_names.length === 0">
+    <nz-row *ngIf="!project.pipeline_names || project.pipeline_names.length === 0">
         <nz-col [nzSpan]="24">
             <nz-alert nzType="info" nzMessage="No pipeline"></nz-alert>
         </nz-col>
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 8821999e8..2c97068ba 100644
--- a/ui/src/app/views/project/show/variable/variable.list.html
+++ b/ui/src/app/views/project/show/variable/variable.list.html
@@ -12,4 +12,4 @@
       <app-variable [project]="project" [variables]="project.variables" [mode]="project.permissions.writable? 'edit': 'ro'" (event)="variableEvent($event)" auditContext="project"></app-variable>
   </div>
 </div>
-<div *ngIf="loading" class="ui text active loader">Loading variables...</div>
+<nz-spin *ngIf="loading" nzTip="Loading variables..."></nz-spin>
diff --git a/ui/src/app/views/project/show/workflow/workflow.list.html b/ui/src/app/views/project/show/workflow/workflow.list.html
index d180912db..539901560 100644
--- a/ui/src/app/views/project/show/workflow/workflow.list.html
+++ b/ui/src/app/views/project/show/workflow/workflow.list.html
@@ -4,7 +4,7 @@
             <h3>List of workflows in the project</h3>
         </nz-col>
     </nz-row>
-    <nz-row class="ui row">
+    <nz-row>
         <nz-col [nzSpan]="8">
             <nz-button-group>
                 <button nz-button [nzType]="viewMode === 'blocs'?'primary':'default'" (click)="setViewMode('blocs')">
diff --git a/ui/src/app/views/settings/action/add/action.add.html b/ui/src/app/views/settings/action/add/action.add.html
index ee3b6c44f..aedca3adb 100644
--- a/ui/src/app/views/settings/action/add/action.add.html
+++ b/ui/src/app/views/settings/action/add/action.add.html
@@ -1,17 +1,15 @@
 <div class="wrapper">
     <app-breadcrumb [path]="path"></app-breadcrumb>
     <app-scrollview class="scrollview">
-        <div class="ui grid">
-            <div class="thirteen wide column">
-                <div *ngIf="job" class="ui blue message">
-                    {{'action_help_add_from' | translate: {projectKey: projectKey, pipelineName: pipeline.name, jobName: job.action.name} }}
-                </div>
+        <nz-row>
+            <nz-col [nzSpan]="19">
+                <nz-alert *ngIf="job" nzType="info" nzMessage="{{'action_help_add_from' | translate: {projectKey: projectKey, pipelineName: pipeline.name, jobName: job.action.name} }}"></nz-alert>
                 <app-action-form [groups]="groups" [action]="action" (save)="actionSave($event)" [loading]="loading">
                 </app-action-form>
-            </div>
-            <div class="three wide column">
+            </nz-col>
+            <nz-col [nzSpan]="5">
                 <app-action-help></app-action-help>
-            </div>
-        </div>
+            </nz-col>
+        </nz-row>
     </app-scrollview>
 </div>
diff --git a/ui/src/app/views/settings/action/add/action.add.scss b/ui/src/app/views/settings/action/add/action.add.scss
index d15427ece..eb62a5ca5 100644
--- a/ui/src/app/views/settings/action/add/action.add.scss
+++ b/ui/src/app/views/settings/action/add/action.add.scss
@@ -1,15 +1,3 @@
-@import '../../../../../common';
-.ui.input {
-    position: inherit;
-}
-
-#ActionAdd {
-    display: flex;
-    flex-direction: column;
-    align-content: stretch;
-    height: 100%;
-    padding: 20px;
-    h2 {
-        text-align: center;
-    }
+nz-alert {
+  margin-bottom: 20px;
 }
diff --git a/ui/src/app/views/settings/action/edit/action.edit.component.ts b/ui/src/app/views/settings/action/edit/action.edit.component.ts
index 170413ffc..e58ae0a4d 100644
--- a/ui/src/app/views/settings/action/edit/action.edit.component.ts
+++ b/ui/src/app/views/settings/action/edit/action.edit.component.ts
@@ -86,13 +86,16 @@ export class ActionEditComponent implements OnInit, OnDestroy {
                 disabled: true,
                 type: ColumnType.CONFIRM_BUTTON,
                 name: 'Action',
-                class: 'two right aligned',
+                class: 'rightAlign',
                 selector: (aa: AuditAction) => ({
-                        title: 'common_rollback',
-                        click: () => {
- this.clickRollback(aa);
-}
-                    })
+                    buttonType: 'primary',
+                    buttonDanger: false,
+                    buttonConfirmationMessage: 'Are you sure you want to rollback this action ?',
+                    title: 'Rollback',
+                    click: () => {
+                        this.clickRollback(aa);
+                    }
+                })
             }
         ];
 
diff --git a/ui/src/app/views/settings/action/edit/action.edit.html b/ui/src/app/views/settings/action/edit/action.edit.html
index b0b7a0671..69bb7a447 100644
--- a/ui/src/app/views/settings/action/edit/action.edit.html
+++ b/ui/src/app/views/settings/action/edit/action.edit.html
@@ -2,43 +2,42 @@
     <app-breadcrumb [path]="path"></app-breadcrumb>
     <app-tabs [tabs]="tabs" (onSelect)="selectTab($event)"></app-tabs>
     <app-scrollview class="scrollview" *ngIf="selectedTab" [ngSwitch]="selectedTab.key">
-        <div *ngSwitchCase="'action'" class="ui grid">
-            <div class="thirteen wide column">
+        <nz-row *ngSwitchCase="'action'">
+            <nz-col [nzSpan]="19">
                 <app-action-form *ngIf="action" [groups]="groups" [action]="action" (save)="actionSave($event)"
                     (delete)="actionDelete()" [loading]="loading"></app-action-form>
-            </div>
-            <div class="three wide column">
+            </nz-col>
+            <nz-col [nzSpan]="5">
                 <app-action-help></app-action-help>
-                <ng-container *ngIf="action">
-                    <br />
-                    <div class="field" *ngIf="action.first_audit">
+                <div class="nzSegment" *ngIf="action">
+                    <div *ngIf="action.first_audit">
                         <h3>{{ "common_created_by" | translate }}</h3>
                         {{ action.first_audit.triggered_by }}
                     </div>
                     <br />
-                    <div class="field" *ngIf="action.last_audit">
+                    <div *ngIf="action.last_audit">
                         <h3>{{ "common_last_modified" | translate }}</h3>
                         {{ action.last_audit.created }}
                     </div>
-                </ng-container>
-            </div>
-        </div>
-        <div *ngSwitchCase="'audits'" class="ui grid">
-            <div class="sixteen wide column">
+                </div>
+            </nz-col>
+        </nz-row>
+        <nz-row *ngSwitchCase="'audits'">
+            <nz-col [nzSpan]="24">
                 <app-data-table [withPagination]="10" [columns]="columnsAudits" [data]="audits" [loading]="loadingAudits"
                     [withLineClick]="true" (clickLine)="clickAudit($event)"></app-data-table>
-            </div>
-            <div class="sixteen wide column">
+            </nz-col>
+            <nz-col [nzSpan]="24">
                 <app-diff-list *ngIf="diffItems" [items]="diffItems"></app-diff-list>
-            </div>
-        </div>
-        <div *ngSwitchCase="'usage'" class="ui grid">
-            <div class="sixteen wide column">
-                <div class="ui active centered inline loader" *ngIf="loadingUsage"></div>
+            </nz-col>
+        </nz-row>
+        <nz-row *ngSwitchCase="'usage'">
+            <nz-col [nzSpan]="24">
+                <nz-spin nzTip="Loading..." *ngIf="loadingUsage"></nz-spin>
                 <ng-container *ngIf="!loadingUsage">
                     <app-action-usage [usage]="usage"></app-action-usage>
                 </ng-container>
-            </div>
-        </div>
+            </nz-col>
+        </nz-row>
     </app-scrollview>
-</div>
\ No newline at end of file
+</div>
diff --git a/ui/src/app/views/settings/action/edit/action.edit.scss b/ui/src/app/views/settings/action/edit/action.edit.scss
index c5f9df163..dbcde07b4 100644
--- a/ui/src/app/views/settings/action/edit/action.edit.scss
+++ b/ui/src/app/views/settings/action/edit/action.edit.scss
@@ -1,1 +1,6 @@
-@import '../../../../../common';
\ No newline at end of file
+.nzSegment {
+  margin-left: 20px;
+  margin-top: 20px;
+
+  padding: 20px 20px;
+}
diff --git a/ui/src/app/views/settings/action/help/action.help.html b/ui/src/app/views/settings/action/help/action.help.html
index d5e29891a..64fc31029 100644
--- a/ui/src/app/views/settings/action/help/action.help.html
+++ b/ui/src/app/views/settings/action/help/action.help.html
@@ -1,9 +1,6 @@
-<div class="three wide column">
-    <div class="ui segment">
-        <h2 class="ui header"><i class="fa fa-book"></i> {{ 'settings_tips' | translate }}</h2>
-        <div class="ui list">
-            <a class="item" href="#" [routerLink]="['/docs', 'docs', 'actions']" target="_blank" rel="noopener noreferrer">{{'action_help_line_1'
+
+    <div class="nzSegment">
+        <h2><span nz-icon nzType="book" nzTheme="outline"></span> {{ 'settings_tips' | translate }}</h2>
+        <a class="item" href="#" [routerLink]="['/docs', 'docs', 'actions']" target="_blank" rel="noopener noreferrer">{{'action_help_line_1'
                 | translate}}</a>
-        </div>
     </div>
-</div>
diff --git a/ui/src/app/views/settings/action/help/action.help.scss b/ui/src/app/views/settings/action/help/action.help.scss
index 153e0d0de..5a60d24f2 100644
--- a/ui/src/app/views/settings/action/help/action.help.scss
+++ b/ui/src/app/views/settings/action/help/action.help.scss
@@ -1,1 +1,6 @@
 @import '../../../../../common';
+
+.nzSegment {
+  margin-left: 20px;
+  padding: 20px 20px;
+}
diff --git a/ui/src/app/views/settings/action/list/action.list.html b/ui/src/app/views/settings/action/list/action.list.html
index b5ed873f5..0be64551e 100644
--- a/ui/src/app/views/settings/action/list/action.list.html
+++ b/ui/src/app/views/settings/action/list/action.list.html
@@ -1,7 +1,6 @@
 <div class="wrapper">
     <app-breadcrumb [path]="path">
-        <a class="ui right floated green button small buttonBreadcrumb" [routerLink]="['add']">{{ 'btn_add' | translate
-            }}</a>
+        <button nz-button nzType="primary" [routerLink]="['add']" class="alignRight">Add</button>
     </app-breadcrumb>
     <app-tabs [tabs]="tabs" (onSelect)="selectTab($event)"></app-tabs>
     <app-scrollview class="scrollview" [ngSwitch]="selectedTab.key">
diff --git a/ui/src/app/views/settings/action/list/action.list.scss b/ui/src/app/views/settings/action/list/action.list.scss
index 76e0aace7..62faa1945 100644
--- a/ui/src/app/views/settings/action/list/action.list.scss
+++ b/ui/src/app/views/settings/action/list/action.list.scss
@@ -1,4 +1,3 @@
-@import '../../../../../common';
-.ui.input {
-    position: inherit;
+.alignRight {
+  float: right;
 }
diff --git a/ui/src/app/views/settings/action/show/action.show.html b/ui/src/app/views/settings/action/show/action.show.html
index fd9f9f253..b4eac81f7 100644
--- a/ui/src/app/views/settings/action/show/action.show.html
+++ b/ui/src/app/views/settings/action/show/action.show.html
@@ -2,33 +2,21 @@
     <app-breadcrumb [path]="path"></app-breadcrumb>
     <app-tabs [tabs]="tabs" (onSelect)="selectTab($event)"></app-tabs>
     <app-scrollview class="scrollview" *ngIf="selectedTab" [ngSwitch]="selectedTab.key">
-        <div *ngSwitchCase="'action'" class="ui grid">
-            <div class="thirteen wide column">
+        <nz-row *ngSwitchCase="'action'">
+            <nz-col [nzSpan]="19">
                 <app-action [action]="action" [edit]="false"></app-action>
-            </div>
-            <div class="three wide column">
+            </nz-col>
+            <nz-col [nzSpan]="5">
                 <app-action-help></app-action-help>
-                <ng-container *ngIf="action">
-                    <br />
-                    <div class="field" *ngIf="action.first_audit">
-                        <h3>{{ "common_created_by" | translate }}</h3>
-                        {{ action.first_audit.triggered_by }}
-                    </div>
-                    <br />
-                    <div class="field" *ngIf="action.last_audit">
-                        <h3>{{ "common_last_modified" | translate }}</h3>
-                        {{ action.last_audit.created }}
-                    </div>
-                </ng-container>
-            </div>
-        </div>
-        <div *ngSwitchCase="'usage'" class="ui grid">
-            <div class="sixteen wide column">
-                <div class="ui active centered inline loader" *ngIf="loadingUsage"></div>
+            </nz-col>
+        </nz-row>
+        <nz-row *ngSwitchCase="'usage'">
+            <nz-col [nzSpan]="24">
+                <nz-spin nzTip="Loading..." *ngIf="loadingUsage"></nz-spin>
                 <ng-container *ngIf="!loadingUsage">
                     <app-action-usage [usage]="usage"></app-action-usage>
                 </ng-container>
-            </div>
-        </div>
+            </nz-col>
+        </nz-row>
     </app-scrollview>
 </div>
diff --git a/ui/src/app/views/settings/action/show/action.show.scss b/ui/src/app/views/settings/action/show/action.show.scss
index c5f9df163..5a60d24f2 100644
--- a/ui/src/app/views/settings/action/show/action.show.scss
+++ b/ui/src/app/views/settings/action/show/action.show.scss
@@ -1,1 +1,6 @@
-@import '../../../../../common';
\ No newline at end of file
+@import '../../../../../common';
+
+.nzSegment {
+  margin-left: 20px;
+  padding: 20px 20px;
+}
diff --git a/ui/src/app/views/settings/user/consumer-details-modal/consumer-details-modal.component.ts b/ui/src/app/views/settings/user/consumer-details-modal/consumer-details-modal.component.ts
index f5e582f9f..f0d179a02 100644
--- a/ui/src/app/views/settings/user/consumer-details-modal/consumer-details-modal.component.ts
+++ b/ui/src/app/views/settings/user/consumer-details-modal/consumer-details-modal.component.ts
@@ -94,7 +94,7 @@ export class ConsumerDetailsModalComponent implements OnInit {
                     let labels = [];
 
                     if (c.disabled) {
-                        labels.push({ color: 'red', title: 'user_auth_consumer_disabled' });
+                        labels.push({ color: 'error', title: 'user_auth_consumer_disabled' });
                     }
 
                     return {
@@ -114,9 +114,10 @@ export class ConsumerDetailsModalComponent implements OnInit {
             <Column<AuthConsumer>>{
                 type: ColumnType.BUTTON,
                 name: 'Action',
-                class: 'two right aligned',
+                class: 'rightAlign',
                 selector: (c: AuthConsumer) => ({
-                        title: 'common_details',
+                        title: 'Details',
+                        buttonDanger: false,
                         click: () => this.clickConsumerDetails(c)
                     })
             }
diff --git a/ui/src/app/views/settings/user/edit/user.edit.component.ts b/ui/src/app/views/settings/user/edit/user.edit.component.ts
index 7480adc2a..63c158980 100644
--- a/ui/src/app/views/settings/user/edit/user.edit.component.ts
+++ b/ui/src/app/views/settings/user/edit/user.edit.component.ts
@@ -3,7 +3,6 @@ import { NgForm } from '@angular/forms';
 import { ActivatedRoute, Router } from '@angular/router';
 import { TranslateService } from '@ngx-translate/core';
 import { Store } from '@ngxs/store';
-import { Transition, TransitionController, TransitionDirection } from '@richardlt/ng2-semantic-ui';
 import { AuthConsumer, AuthDriverManifest, AuthDriverManifests, AuthSession } from 'app/model/authentication.model';
 import { Group } from 'app/model/group.model';
 import { AuthentifiedUser, AuthSummary, UserContact } from 'app/model/user.model';
@@ -17,14 +16,13 @@ import { AuthenticationState } from 'app/store/authentication.state';
 import * as moment from 'moment';
 import { forkJoin } from 'rxjs';
 import { finalize } from 'rxjs/operators';
-import { CloseEventType, ConsumerCreateModalComponent } from '../consumer-create-modal/consumer-create-modal.component';
+import { ConsumerCreateModalComponent } from '../consumer-create-modal/consumer-create-modal.component';
 import {
     CloseEvent,
     CloseEventType as DetailsCloseEventType,
     ConsumerDetailsModalComponent
 } from '../consumer-details-modal/consumer-details-modal.component';
 import { NzModalService } from 'ng-zorro-antd/modal';
-import { AsCodeSaveModalComponent } from 'app/shared/ascode/save-modal/ascode.save-modal.component';
 
 const defaultMenuItems = [<Item>{
     translate: 'user_profile_btn',
@@ -44,7 +42,6 @@ const usernamePattern = new RegExp('^[a-zA-Z0-9._-]{1,}$');
     changeDetection: ChangeDetectionStrategy.OnPush
 })
 export class UserEditComponent implements OnInit {
-    transitionController = new TransitionController();
 
     @ViewChild('ldapSigninForm')
     ldapSigninForm: NgForm;
@@ -54,7 +51,6 @@ export class UserEditComponent implements OnInit {
 
     loading = false;
     deleteLoading = false;
-    groupsAdmin: Array<Group>;
     userPatternError = false;
     username: string;
     currentAuthSummary: AuthSummary;
@@ -128,10 +124,10 @@ export class UserEditComponent implements OnInit {
                     let labels = [];
 
                     if (c.primary) {
-                        labels.push({ color: 'green', title: 'user_contact_primary' });
+                        labels.push({ color: 'success', title: 'user_contact_primary' });
                     }
                     if (!c.verified) {
-                        labels.push({ color: 'red', title: 'user_contact_not_verified' });
+                        labels.push({ color: 'error', title: 'user_contact_not_verified' });
                     }
 
                     return {
@@ -155,14 +151,12 @@ export class UserEditComponent implements OnInit {
         this.columnsConsumers = [
             <Column<AuthConsumer>>{
                 type: ColumnType.TEXT_LABELS,
-                name: 'common_name',
+                name: 'Name',
                 selector: (c: AuthConsumer) => {
                     let labels = [];
-
                     if (c.disabled) {
-                        labels.push({ color: 'red', title: 'user_auth_consumer_disabled' });
+                        labels.push({ color: 'error', title: 'user_auth_consumer_disabled' });
                     }
-
                     return {
                         value: c.name,
                         labels
@@ -198,7 +192,9 @@ export class UserEditComponent implements OnInit {
 
                         icons.push({
                             label: text,
-                            class: ['info', 'exclamation', 'triangle', 'icon', 'yellow', 'link'],
+                            type: 'warning',
+                            theme: 'outline',
+                            class: ['orange'],
                             title: text
                         });
                     }
@@ -245,9 +241,10 @@ export class UserEditComponent implements OnInit {
             <Column<AuthConsumer>>{
                 type: ColumnType.BUTTON,
                 name: 'Action',
-                class: 'two right aligned',
+                class: 'rightAlign',
                 selector: (c: AuthConsumer) => ({
-                    title: 'common_details',
+                    title: 'Details',
+                    buttonDanger: false,
                     click: () => {
                         this.clickConsumerDetails(c);
                     }
@@ -275,27 +272,28 @@ export class UserEditComponent implements OnInit {
                         let consumer = this.mConsumers[s.consumer_id];
 
                         let icon = {
+                            theme: 'outline',
+                            type: '',
                             label: `ID: ${s.consumer_id}`,
-                            title: `ID: ${s.consumer_id}`
+                            title: `ID: ${s.consumer_id}`,
                         };
                         switch (consumer.type) {
                             case 'builtin':
-                                icon['class'] = ['info', 'circle', 'icon', 'link'];
+                                icon.type = 'info-circle';
                                 break;
                             case 'local':
-                                icon['class'] = ['lock', 'icon'];
+                                icon.type = 'lock';
                                 break;
                             case 'ldap':
-                                icon['class'] = ['address', 'book', 'icon'];
+                                icon.type = 'audit';
                                 break;
                             case 'corporate-sso':
-                                icon['class'] = ['shield', 'alternate', 'icon'];
+                                icon.type = 'safety-certificate';
                                 break;
                             case 'openid-connect':
-                                icon['class'] = ['openid', 'icon'];
+                                icon.type = 'safety-certificate';
                                 break;
                             default:
-                                icon['class'] = [consumer.type, 'icon'];
                                 break;
                         }
 
@@ -349,8 +347,10 @@ export class UserEditComponent implements OnInit {
                 class: 'two right aligned',
                 disabled: true,
                 selector: (s: AuthSession) => ({
-                    title: 'user_auth_revoke_btn',
-                    color: 'red',
+                    buttonType: 'primary',
+                    buttonDanger: true,
+                    buttonConfirmationMessage: 'Are you sure you want to revoke this session ?',
+                    title: 'Revoke',
                     click: () => {
                         this.clickSessionRevoke(s);
                     }
@@ -413,16 +413,8 @@ export class UserEditComponent implements OnInit {
             });
             return;
         }
-
-        this.transitionController.animate(
-            new Transition('scale', 150, TransitionDirection.Out, () => {
-                this.showLDAPSigninForm = true;
-                this._cd.detectChanges();
-                this.transitionController.animate(
-                    new Transition('scale', 150, TransitionDirection.In, () => { })
-                );
-            })
-        );
+        this.showLDAPSigninForm = true;
+        this._cd.detectChanges();
     }
 
     clickConsumerDetach(c: AuthConsumer): void {
diff --git a/ui/src/app/views/settings/user/edit/user.edit.html b/ui/src/app/views/settings/user/edit/user.edit.html
index 233129bb1..4bc993d11 100644
--- a/ui/src/app/views/settings/user/edit/user.edit.html
+++ b/ui/src/app/views/settings/user/edit/user.edit.html
@@ -8,52 +8,65 @@
             <ng-container *ngSwitchCase="'profile'" class="ui grid">
                 <div *ngIf="user;then showUser;else loadUser"></div>
                 <ng-template #showUser>
-                    <form class="ui form">
-                        <div class="ui grid">
-                            <div class="ui row">
-                                <div class="wide column">
-                                    <div class="field">
-                                        <label>{{'user_label_username' | translate}}</label>
-                                        <input class="ui input" type="text" name="username" [(ngModel)]="user.username"
-                                            [disabled]="loading" readonly>
-                                    </div>
-                                    <div class="field">
-                                        <label>{{'user_label_fullname' | translate}}</label>
-                                        <input class="ui input" type="text" name="fullname" [(ngModel)]="user.fullname"
-                                            [disabled]="loading" [readonly]="!editable">
-                                    </div>
-                                    <div *ngIf="user.organization" class="field">
-                                        <label>Organization</label>
-                                        <input class="ui input" type="text" name="organization"
-                                            [ngModel]="user.organization" readonly>
-                                    </div>
-                                    <div class="field">
-                                        <label for="admin">{{'user_label_ring' | translate}}</label>
+                    <form nz-form>
+                        <nz-row>
+                            <nz-col [nzSpan]="24">
+                                <nz-form-item>
+                                    <nz-form-label>Username</nz-form-label>
+                                    <nz-form-control>
+                                        <input nz-input type="text" name="username" [(ngModel)]="user.username"
+                                               [disabled]="loading" readonly>
+                                    </nz-form-control>
+                                </nz-form-item>
+                                <nz-form-item>
+                                    <nz-form-label>Fullname</nz-form-label>
+                                    <nz-form-control>
+                                        <input nz-input type="text" name="fullname" [(ngModel)]="user.fullname"
+                                               [disabled]="loading" [readonly]="!editable">
+                                    </nz-form-control>
+                                </nz-form-item>
+                                <nz-form-item *ngIf="user.organization">
+                                    <nz-form-label>Organization</nz-form-label>
+                                    <nz-form-control>
+                                        <input nz-input type="text" name="organization"
+                                               [ngModel]="user.organization" readonly>
+                                    </nz-form-control>
+                                </nz-form-item>
+                                <nz-form-item>
+                                    <nz-form-label>Ring</nz-form-label>
+                                    <nz-form-control>
                                         <nz-select name="admin" [nzDisabled]="loading" *ngIf="currentAuthSummary.isAdmin()"
-                                                [(ngModel)]="user.ring">
+                                                   [(ngModel)]="user.ring">
                                             <nz-option nzValue="USER" nzLabel="USER"></nz-option>
                                             <nz-option nzValue="MAINTAINER" nzLabel="MAINTAINER"></nz-option>
                                             <nz-option nzValue="ADMIN" nzLabel="ADMIN"></nz-option>
                                         </nz-select>
-                                        <input *ngIf="!currentAuthSummary.isAdmin()" class="ui input" type="text"
-                                            name="admin" [ngModel]="user.ring" [readonly]="true">
-                                    </div>
-                                    <div class="field inline" *ngIf="editable">
-                                        <app-delete-button class="left floated" (event)="clickDelete()"
-                                            [loading]="deleteLoading">
-                                        </app-delete-button>
-                                        <button class="ui green right floated button" [class.loading]="loading"
-                                            (click)="clickSave()">
-                                            <i class="save icon"></i>{{ 'btn_save' | translate }}
-                                        </button>
-                                    </div>
-                                </div>
-                            </div>
-                        </div>
+                                        <input *ngIf="!currentAuthSummary.isAdmin()"nz-input type="text"
+                                               name="admin" [ngModel]="user.ring" [readonly]="true">
+                                    </nz-form-control>
+                                </nz-form-item>
+                            </nz-col>
+                        </nz-row>
+                        <nz-row *ngIf="editable">
+                            <nz-col [nzSpan]="12">
+                                <button nz-button nzDanger nzType="primary"
+                                        nz-popconfirm nzPopconfirmTitle="Are you sure you want to delete this user ?"
+                                        (nzOnConfirm)="clickDelete()">
+                                    <i nz-icon nzType="delete" nzTheme="outline"></i>
+                                    Delete
+                                </button>
+                            </nz-col>
+                            <nz-col [nzSpan]="12" class="alignRight">
+                                <button nz-button nzType="primary" [nzLoading]="loading"
+                                        (click)="clickSave()">
+                                    <i nz-icon nzType="save" nzTheme="outline"></i>Save
+                                </button>
+                            </nz-col>
+                        </nz-row>
                     </form>
                 </ng-template>
                 <ng-template #loadUser>
-                    <div class="ui text active loader">{{ 'user_load_user' | translate }}</div>
+                    <nz-spin nzTip="Loading user..."></nz-spin>
                 </ng-template>
             </ng-container>
             <div *ngSwitchCase="'groups'" class="ui grid">
@@ -89,7 +102,7 @@
                                         </div>
                                     </ng-container>
                                     <ng-container *ngSwitchCase="'ldap'">
-                                        <div [suiTransition]="transitionController">
+                                        <div>
                                             <ng-container *ngIf="!showLDAPSigninForm">
                                                 <div class="center aligned header">
                                                     <i class="ui address book icon huge"></i>
@@ -140,23 +153,19 @@
                                 <div class="extra content center aligned">
                                     <ng-container *ngIf="mConsumers[d.type]" [ngSwitch]="d.type">
                                         <ng-container *ngSwitchCase="'local'">
-                                            <button class="ui primary button small"
-                                                (click)="clickConsumerDetails(mConsumers[d.type])">{{'common_details' |
-                                                translate}}</button>
+                                            <button nz-button
+                                                (click)="clickConsumerDetails(mConsumers[d.type])">Details</button>
                                             <button *ngIf="user.id === currentAuthSummary.user.id"
-                                                class="ui green button small"
+                                                nz-button nzType="primary"
                                                 (click)="clickConsumerLocalReset()">{{'account_password_btn_reset' |
                                                 translate}}</button>
                                         </ng-container>
                                         <ng-container *ngSwitchDefault>
-                                            <button class="ui primary button small"
-                                                (click)="clickConsumerDetails(mConsumers[d.type])">{{'common_details' |
-                                                translate}}</button>
-                                            <app-confirm-button *ngIf="user.id === currentAuthSummary.user.id"
-                                                [class]="'small'" [color]="'red'"
-                                                (event)="clickConsumerDetach(mConsumers[d.type])"
-                                                [title]="'user_auth_consumer_detach_btn'">
-                                            </app-confirm-button>
+                                            <button nz-button
+                                                (click)="clickConsumerDetails(mConsumers[d.type])">Details</button>
+                                            <button *ngIf="user.id === currentAuthSummary.user.id" nz-button nzDanger nzType="primary"
+                                                    nz-popconfirm nzPopconfirmTitle="Are you sure you want to detach it ?"
+                                                    (nzOnConfirm)="clickConsumerDetach(mConsumers[d.type])">Detach</button>
                                         </ng-container>
                                     </ng-container>
                                     <ng-container *ngIf="!mConsumers[d.type]" [ngSwitch]="d.type">
diff --git a/ui/src/app/views/settings/user/edit/user.edit.scss b/ui/src/app/views/settings/user/edit/user.edit.scss
index ec6334761..2d06fc8d9 100644
--- a/ui/src/app/views/settings/user/edit/user.edit.scss
+++ b/ui/src/app/views/settings/user/edit/user.edit.scss
@@ -2,6 +2,9 @@
 nz-select {
   width: 100%;
 }
+.alignRight {
+  text-align: right;
+}
 .wrapper {
     display: flex;
     flex-direction: column;
diff --git a/ui/src/app/views/settings/user/list/user.list.component.ts b/ui/src/app/views/settings/user/list/user.list.component.ts
index 4bb6e73be..862d28ec7 100644
--- a/ui/src/app/views/settings/user/list/user.list.component.ts
+++ b/ui/src/app/views/settings/user/list/user.list.component.ts
@@ -24,10 +24,10 @@ export class UserListComponent {
         this.columns = [
             <Column<AuthentifiedUser>>{
                 type: ColumnType.ICON,
-                class: 'one',
                 selector: (u: AuthentifiedUser) => ({
                     title: u.ring,
-                    icon: u.ring === 'ADMIN' ? ['user', 'outline', 'icon'] : ['user', 'icon']
+                    iconTheme: 'outline',
+                    iconType: u.ring === 'ADMIN' ? 'crown' : 'user'
                 })
             },
             <Column<AuthentifiedUser>>{
diff --git a/ui/src/app/views/settings/worker-model/list/worker-model.list.component.ts b/ui/src/app/views/settings/worker-model/list/worker-model.list.component.ts
index ee107229b..8d55ce4d3 100644
--- a/ui/src/app/views/settings/worker-model/list/worker-model.list.component.ts
+++ b/ui/src/app/views/settings/worker-model/list/worker-model.list.component.ts
@@ -58,30 +58,39 @@ export class WorkerModelListComponent {
 
                     if (wm.disabled) {
                         icons.push({
-                            label: 'worker_model_disabled',
-                            class: ['grey', 'ban', 'icon'],
-                            title: 'worker_model_disabled'
+                            label: 'This worker model is disabled',
+                            iconTheme: 'outline',
+                            iconType: 'stop',
+                            iconColor: 'grey',
+                            title: 'This worker model is disabled'
                         });
                     }
                     if (wm.nb_spawn_err > 0) {
                         icons.push({
-                            label: 'worker_model_spawn_error_tooltip',
-                            class: ['exclamation', 'triangle', 'icon', 'red'],
-                            title: 'worker_model_warning'
+                            label: 'Model in error',
+                            iconTheme: 'outline',
+                            iconType: 'warning',
+                            iconColor: 'red',
+                            title: 'This worker model is in error. Please check it'
                         });
                     }
                     if (wm.is_official) {
                         icons.push({
-                            label: 'worker_model_official_tooltip',
+                            label: 'Official worker model maintained by CDS Administrators',
+                            iconTheme: 'outline',
+                            iconType: 'check-circle',
+                            iconColor: 'green',
                             class: ['check', 'circle', 'outline', 'icon', 'green'],
-                            title: 'worker_model_official_tooltip'
+                            title: 'Official worker model maintained by CDS Administrators'
                         });
                     }
                     if (wm.is_deprecated) {
                         icons.push({
-                            label: 'worker_model_deprecated_tooltip',
-                            class: ['exclamation', 'circle', 'icon', 'orange'],
-                            title: 'worker_model_official_tooltip'
+                            label: 'This worker model is deprecated',
+                            iconTheme: 'outline',
+                            iconType: 'warning',
+                            iconColor: 'orange',
+                            title: 'This worker model is deprecated'
                         });
                     }
 
diff --git a/ui/src/app/views/settings/workflow-template/edit/workflow-template.edit.component.ts b/ui/src/app/views/settings/workflow-template/edit/workflow-template.edit.component.ts
index d291ff846..08589c6f7 100644
--- a/ui/src/app/views/settings/workflow-template/edit/workflow-template.edit.component.ts
+++ b/ui/src/app/views/settings/workflow-template/edit/workflow-template.edit.component.ts
@@ -158,7 +158,7 @@ export class WorkflowTemplateEditComponent implements OnInit, OnDestroy {
                 }
             }, <Column<WorkflowTemplateInstance>>{
                 type: ColumnType.LABEL,
-                name: 'common_status',
+                name: 'Status',
                 class: 'three',
                 selector: (i: WorkflowTemplateInstance) => {
                     let status = i.status(this.workflowTemplate);
@@ -170,10 +170,11 @@ export class WorkflowTemplateEditComponent implements OnInit, OnDestroy {
             }, <Column<WorkflowTemplateInstance>>{
                 type: ColumnType.BUTTON,
                 name: 'Action',
-                class: 'two right aligned',
+                class: 'rightAlign',
                 selector: (i: WorkflowTemplateInstance) => ({
-                        title: 'common_update',
-                        class: 'primary small',
+                        title: 'Update',
+                        buttonType: 'primary',
+                        buttonDanger: false,
                         click: () => {
                             this.clickUpdate(i);
                         }
@@ -206,13 +207,16 @@ export class WorkflowTemplateEditComponent implements OnInit, OnDestroy {
                     this.columnsAudits.push(<Column<AuditWorkflowTemplate>>{
                         type: ColumnType.CONFIRM_BUTTON,
                         name: 'Action',
-                        class: 'two right aligned',
+                        class: 'rightAlign',
                         selector: (a: AuditWorkflowTemplate) => ({
-                                title: 'common_rollback',
-                                click: () => {
-                                    this.clickRollback(a);
-                                }
-                            })
+                            buttonType: 'primary',
+                            buttonDanger: false,
+                            buttonConfirmationMessage: 'Are you sure you want to rollback this workflow template ?',
+                            title: 'Rollback',
+                            click: () => {
+                                this.clickRollback(a);
+                            }
+                        })
                     });
                 }
 
diff --git a/ui/src/app/views/settings/workflow-template/list/workflow-template.list.component.ts b/ui/src/app/views/settings/workflow-template/list/workflow-template.list.component.ts
index b9b29c130..b052ea679 100644
--- a/ui/src/app/views/settings/workflow-template/list/workflow-template.list.component.ts
+++ b/ui/src/app/views/settings/workflow-template/list/workflow-template.list.component.ts
@@ -8,6 +8,7 @@ import { finalize } from 'rxjs/operators';
 @Component({
     selector: 'app-workflow-template-list',
     templateUrl: './workflow-template.list.html',
+    styleUrls: ['./workflow-template.list.scss'],
     changeDetection: ChangeDetectionStrategy.OnPush
 })
 export class WorkflowTemplateListComponent {
diff --git a/ui/src/app/views/settings/workflow-template/list/workflow-template.list.html b/ui/src/app/views/settings/workflow-template/list/workflow-template.list.html
index 535f7ae9c..02b92db07 100644
--- a/ui/src/app/views/settings/workflow-template/list/workflow-template.list.html
+++ b/ui/src/app/views/settings/workflow-template/list/workflow-template.list.html
@@ -1,10 +1,9 @@
 <div class="wrapper">
     <app-breadcrumb [path]="path">
-        <a class="ui right floated green button small buttonBreadcrumb" [routerLink]="['add']">{{ 'btn_add' | translate
-            }}</a>
+        <button nz-button nzType="primary" class="alignRight" [routerLink]="['add']">Add</button>
     </app-breadcrumb>
     <app-scrollview class="scrollview">
         <app-data-table [withFilter]="filter" [withPagination]="25" [columns]="columns" [data]="workflowTemplates"
             [loading]="loading"></app-data-table>
     </app-scrollview>
-</div>
\ No newline at end of file
+</div>
diff --git a/ui/src/app/views/settings/workflow-template/list/workflow-template.list.scss b/ui/src/app/views/settings/workflow-template/list/workflow-template.list.scss
new file mode 100644
index 000000000..62faa1945
--- /dev/null
+++ b/ui/src/app/views/settings/workflow-template/list/workflow-template.list.scss
@@ -0,0 +1,3 @@
+.alignRight {
+  float: right;
+}
diff --git a/ui/src/app/views/workflow/graph/workflow.graph.html b/ui/src/app/views/workflow/graph/workflow.graph.html
index 1da896d45..d110ee7ac 100644
--- a/ui/src/app/views/workflow/graph/workflow.graph.html
+++ b/ui/src/app/views/workflow/graph/workflow.graph.html
@@ -1,5 +1,5 @@
 <div class="svg-wrapper" #svgGraph>
-    <button class="ui icon button big" (click)="clickOrigin()">
+    <button nz-button nzSize="large" class="origin" (click)="clickOrigin()">
         <i class="crosshairs icon"></i>
     </button>
 </div>
diff --git a/ui/src/app/views/workflow/graph/workflow.graph.scss b/ui/src/app/views/workflow/graph/workflow.graph.scss
index a38c9bb1c..940396154 100644
--- a/ui/src/app/views/workflow/graph/workflow.graph.scss
+++ b/ui/src/app/views/workflow/graph/workflow.graph.scss
@@ -23,8 +23,12 @@
   cursor: all-scroll;
 }
 
-.button {
+.origin {
   position: fixed;
   bottom: 20px;
   right: 20px;
+
+  i {
+    margin: 0;
+  }
 }
diff --git a/ui/src/app/views/workflow/run/node/history/history.component.ts b/ui/src/app/views/workflow/run/node/history/history.component.ts
index e95288c01..a43400b90 100644
--- a/ui/src/app/views/workflow/run/node/history/history.component.ts
+++ b/ui/src/app/views/workflow/run/node/history/history.component.ts
@@ -41,20 +41,28 @@ export class WorkflowNodeRunHistoryComponent implements OnInit {
                 type: ColumnType.ICON,
                 name: 'common_status',
                 selector: (nodeRun: WorkflowNodeRun) => ({
-                    icon: (() => {
-                        if (nodeRun.status === PipelineStatus.FAIL || nodeRun.status === PipelineStatus.STOPPED) {
-                            return ['remove', 'red', 'icon'];
-                        }
+                    iconTheme: 'outline',
+                    iconType: (() => {
                         if (nodeRun.status === PipelineStatus.SUCCESS) {
-                            return ['check', 'green', 'icon'];
-                        }
-                        if (nodeRun.status === PipelineStatus.WAITING || nodeRun.status === PipelineStatus.BUILDING) {
-                            return ['wait', 'blue', 'icon'];
+                            return 'check';
+                        } else if (nodeRun.status === PipelineStatus.FAIL || nodeRun.status === PipelineStatus.STOPPED) {
+                            return 'stop';
+                        } else if (nodeRun.status === PipelineStatus.WAITING || nodeRun.status === PipelineStatus.BUILDING) {
+                            return 'field-time';
+                        } else {
+                            return 'stop';
                         }
-                        if (PipelineStatus.neverRun(nodeRun.status)) {
-                            return ['ban', 'grey', 'icon'];
+                    })(),
+                    iconColor: (() => {
+                        if (nodeRun.status === PipelineStatus.SUCCESS) {
+                            return 'green';
+                        } else if (nodeRun.status === PipelineStatus.FAIL || nodeRun.status === PipelineStatus.STOPPED) {
+                            return 'red';
+                        } else if (nodeRun.status === PipelineStatus.WAITING || nodeRun.status === PipelineStatus.BUILDING) {
+                            return 'blue';
+                        } else {
+                            return 'grey';
                         }
-                        return ['stop', 'grey', 'icon'];
                     })()
                 })
             },
diff --git a/ui/src/app/views/workflow/run/workflow.run.html b/ui/src/app/views/workflow/run/workflow.run.html
index 86d1466af..8a843d123 100644
--- a/ui/src/app/views/workflow/run/workflow.run.html
+++ b/ui/src/app/views/workflow/run/workflow.run.html
@@ -31,7 +31,7 @@
             </nz-collapse>
         </nz-col>
     </nz-row>
-    <nz-row *ngIf="warnings && warnings.length > 0" class="ui grid">
+    <nz-row *ngIf="warnings && warnings.length > 0">
         <nz-col [nzOffset]="2" [nzSpan]="20">
             <!-- Warning PANEL -->
             <nz-collapse>
diff --git a/ui/src/app/views/workflow/show/notification/form/workflow.notifications.form.html b/ui/src/app/views/workflow/show/notification/form/workflow.notifications.form.html
index 8a8dadd2b..30bd3a628 100644
--- a/ui/src/app/views/workflow/show/notification/form/workflow.notifications.form.html
+++ b/ui/src/app/views/workflow/show/notification/form/workflow.notifications.form.html
@@ -22,7 +22,7 @@
                     <nz-select nzShowSearch *ngIf="!readOnly" name="nodes" nzMode="multiple" [(ngModel)]="notification.source_node_ref">
                         <nz-option *ngFor="let n of nodes" [nzLabel]="n.name" [nzValue]="n.name"></nz-option>
                     </nz-select>
-                    <input *ngIf="readOnly" class="ui input" type="text" name="nodes"
+                    <input nz-input *ngIf="readOnly" type="text" name="nodes"
                            [ngModel]="notification.source_node_ref ? notification.source_node_ref.join(',') : ''"
                            [readonly]="true">
                 </nz-form-control>
diff --git a/ui/src/assets/i18n/en.json b/ui/src/assets/i18n/en.json
index 13944016f..9a4fa6390 100644
--- a/ui/src/assets/i18n/en.json
+++ b/ui/src/assets/i18n/en.json
@@ -28,7 +28,6 @@
   "account_password_title": "Forgotten password",
   "account_signup_init_token": "Initialization token",
   "account_verify_title": "Account information",
-  "action_step_title": "Job steps",
   "action_list_title": "Actions",
   "action_deleted": "Action deleted",
   "action_delete": "Delete action",
@@ -43,12 +42,6 @@
   "cdsapi_loading": "CDS API is starting. Please wait.",
   "application_ascode_updated": "Draft updated",
   "application_home_history": "History",
-  "application_home_repo": "GIT repository",
-  "application_home_no_repo": "Link a repository",
-  "application_home_integration": "Linked integrations",
-  "application_home_no_integration": "Link an integration",
-  "application_home_no_graph": "No graph to display (vulnerabilities, unit tests)",
-  "application_home_no_usage": "Application is not used",
   "application_integration_added": "Integration added",
   "application_integration_deleted": "Integration deleted",
   "application_integration_updated": "Integration updated",
@@ -58,7 +51,6 @@
   "application_deleted_by": "The application {{appName}} has been deleted by {{username}}",
   "application_name": "Application name",
   "application_from_repository": "Application imported from {{repo}}",
-  "application_no_usage": "The application is not used",
   "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: ",
@@ -105,7 +97,6 @@
   "common_created": "Creation date",
   "common_project": "Project",
   "common_preview": "Preview",
-  "common_details": "Details",
   "common_diff_to_much_item": "To much items to show",
   "common_settings": "Settings",
   "common_admin": "Admin",
@@ -131,12 +122,9 @@
   "common_loading_environments": "Loading environments",
   "common_see": "see",
   "common_name": "Name",
-
   "common_group": "Group",
   "common_value": "Value",
   "common_no": "No",
-
-  "common_parameters": "Parameters",
   "common_pipeline_start_title": "Start: ",
   "common_pipeline": "Pipeline",
   "common_integration": "Integration",
@@ -181,10 +169,8 @@
   "common_up_to_date": "Up to date",
   "common_not_up_to_date": "New version available",
   "common_read_more": "Read more",
-  "common_update": "Update",
   "common_split": "Split",
   "common_unified": "Unified",
-  "common_rollback": "Rollback",
   "common_find_help": "Maybe you could find some help",
   "common_here": "here",
   "common_deleted": "Deleted with success",
@@ -198,9 +184,7 @@
   "keys_added": "Key added",
   "keys_add_title": "Add a key",
   "keys_list_title": "Keys list",
-  "keys_loading": "Loading keys...",
   "keys_removed": "Key has been removed",
-  "keys_no": "There is no keys",
   "keys_ssh_key_help": "A SSH Key is useful when you want to git clone some code from a Git Repository. CDS can generate a key pair and you can copy / paste the public key to your access keys on your repositories. If you want to use manually the key, please read:",
   "keys_pgp_key_help": "A PGP Key is useful when you want to git tag a repository. If you want to use manually the key, please read:",
   "usage_application_list": "Applications used",
@@ -399,7 +383,6 @@
   "stage_job_updated": "Job updated",
   "stage_updated": "Stage updated",
   "step_add_job": "Add job",
-  "step_nonfinal_no": "There is no step.",
   "timeline_loading": "Loading timeline...",
   "timeline_no": "Timeline is empty",
   "timeline_title": "Timeline",
@@ -418,8 +401,6 @@
   "user_label_username": "Username",
   "user_label_password": "Password",
   "user_label_email": "Mail address",
-  "user_label_ring": "Ring",
-  "user_load_user": "Loading user...",
   "user_is_admin": "This user is a CDS Administrator",
   "user_saved": "User saved",
   "user_deleted": "User is deleted",
@@ -437,12 +418,10 @@
   "user_auth_groups": "Groups",
   "user_auth_consumer": "Consumer",
   "user_auth_expire_at": "Expire at",
-  "user_auth_revoke_btn": "Revoke",
   "user_auth_consumers": "Consumers",
   "user_auth_my_consumers": "My consumers",
   "user_auth_sessions": "Sessions",
   "user_auth_session_current": "Current",
-  "user_auth_consumer_detach_btn": "Detach",
   "user_auth_consumer_disabled": "Disabled",
   "user_auth_consumer_warning_last_group_removed": "Last group removed.",
   "user_auth_consumer_warning_group_invalid": "The group '{{name}}' was invalidated.",
@@ -472,8 +451,6 @@
   "worker_model_capabilities_up_to_date": "Binaries list is up to date",
   "worker_model_capabilities_need_refresh": "Binaries list need to be refresh by CDS. Last Registration:",
   "worker_model_state": "State of worker model",
-  "worker_model_disable": "Disable it",
-  "worker_model_disabled": "This worker model is disabled",
   "worker_model_deleted": "Worker model deleted",
   "worker_model_deprecated_tooltip": "This worker model is deprecated",
   "worker_model_official_tooltip": "Official worker model maintained by CDS Administrators",
@@ -497,7 +474,6 @@
   "worker_model_pattern_saved": "Pattern saved",
   "worker_model_pattern_deleted": "Pattern deleted",
   "worker_model_status": "Status",
-  "worker_model_warning": "This worker model is in error. Please check it",
   "worker_model_pattern_title": "Patterns of configuration scripts",
   "worker_model_post_cmd": "Post worker command",
   "worker_model_no_usage": "This worker model is not explicitly used in a pipeline",
diff --git a/ui/src/styles.scss b/ui/src/styles.scss
index 1a8bbe94d..06ba70180 100644
--- a/ui/src/styles.scss
+++ b/ui/src/styles.scss
@@ -493,36 +493,6 @@ foreignObject {
     overflow: visible;
 }
 
-// Ovveride of ng2-semantic-ui
-sui-search {
-    .results {
-        margin-top: 1.5em !important;
-    }
-
-    .ui.input {
-        .prompt {
-            background-color: rgba(225, 226, 229, 0.1);
-            border-color: transparent;
-
-            &:focus {
-                border: 1.5px solid #50cfd9;
-            }
-
-            color: white;
-            opacity: 1;
-        }
-
-        i {
-            &.search.icon {
-                opacity: 1;
-            }
-
-            color: white;
-            opacity: 1;
-        }
-    }
-}
-
 .codeAdded {
     background-color: #cdffd8;
 


Test patch:
diff --git a/engine/api/rbac/dao_rbac_test.go b/engine/api/rbac/dao_rbac_test.go
index b2c0bd7ba..312cd7127 100644
--- a/engine/api/rbac/dao_rbac_test.go
+++ b/engine/api/rbac/dao_rbac_test.go
@@ -107,7 +107,7 @@ globals:
 
 	require.NoError(t, rbac.Insert(context.Background(), db, &r))
 
-	rbacDB, err := rbac.LoadRbacByName(context.TODO(), db, r.Name, rbac.LoadOptions.Default)
+	rbacDB, err := rbac.LoadRBACByName(context.TODO(), db, r.Name, rbac.LoadOptions.Default)
 	require.NoError(t, err)
 
 	// Global part
@@ -174,7 +174,7 @@ projects:
 
 	require.NoError(t, rbac.Insert(context.Background(), db, &r))
 
-	rbacDB, err := rbac.LoadRbacByName(context.TODO(), db, r.Name, rbac.LoadOptions.Default)
+	rbacDB, err := rbac.LoadRBACByName(context.TODO(), db, r.Name, rbac.LoadOptions.Default)
 	require.NoError(t, err)
 
 	require.Equal(t, "read", rbacDB.Projects[0].Role)
@@ -195,7 +195,7 @@ projects:
 
 	require.NoError(t, rbac.Update(context.TODO(), db, &rUpdated))
 
-	rbacDBUpdate, err := rbac.LoadRbacByName(context.TODO(), db, r.Name, rbac.LoadOptions.Default)
+	rbacDBUpdate, err := rbac.LoadRBACByName(context.TODO(), db, r.Name, rbac.LoadOptions.Default)
 	require.NoError(t, err)
 	require.Equal(t, "manage", rbacDBUpdate.Projects[0].Role)
 
diff --git a/engine/api/v2_rbac_test.go b/engine/api/v2_rbac_test.go
new file mode 100644
index 000000000..cad834dc7
--- /dev/null
+++ b/engine/api/v2_rbac_test.go
@@ -0,0 +1,69 @@
+package api
+
+import (
+	"context"
+	"fmt"
+	"github.com/ovh/cds/engine/api/rbac"
+	"io"
+	"net/http/httptest"
+	"strings"
+	"testing"
+
+	"github.com/ovh/cds/engine/api/test"
+	"github.com/ovh/cds/engine/api/test/assets"
+	"github.com/ovh/cds/sdk"
+	"github.com/stretchr/testify/require"
+)
+
+func Test_postImportRbacHandler(t *testing.T) {
+	api, db, _ := newTestAPI(t)
+
+	_, err := db.Exec("DELETE FROM rbac")
+	require.NoError(t, err)
+
+	p := assets.InsertTestProject(t, db, api.Cache, sdk.RandomString(10), sdk.RandomString(10))
+	u, pass := assets.InsertAdminUser(t, db)
+	g := assets.InsertTestGroup(t, db, sdk.RandomString(10))
+
+	vars := map[string]string{}
+	uri := api.Router.GetRouteV2("POST", api.postImportRbacHandler, vars)
+	test.NotEmpty(t, uri)
+	req := assets.NewAuthentifiedRequest(t, u, pass, "POST", uri, nil)
+
+	body := fmt.Sprintf(`name: perm-test
+projects:
+  - role: read
+    projects: [%s]
+    users: [%s]
+    groups: [%s]
+globals:
+ - role: manage-permission
+   users: [%s]
+   groups: [%s]
+`, p.Key, u.Username, g.Name, u.Username, g.Name)
+
+	// Here, we insert the vcs server as a CDS administrator
+	req.Body = io.NopCloser(strings.NewReader(body))
+	req.Header.Set("Content-Type", "application/x-yaml")
+
+	w := httptest.NewRecorder()
+	api.Router.Mux.ServeHTTP(w, req)
+	require.Equal(t, 201, w.Code)
+
+	rbacDB, err := rbac.LoadRBACByName(context.TODO(), db, "perm-test", rbac.LoadOptions.LoadRBACProject, rbac.LoadOptions.LoadRBACGlobal)
+	require.NoError(t, err)
+
+	require.Equal(t, 1, len(rbacDB.Globals))
+	require.Equal(t, "manage-permission", rbacDB.Globals[0].Role)
+	require.Equal(t, 1, len(rbacDB.Globals[0].RBACUsersIDs))
+	require.Equal(t, 1, len(rbacDB.Globals[0].RBACGroupsIDs))
+	require.Equal(t, u.ID, rbacDB.Globals[0].RBACUsersIDs[0])
+	require.Equal(t, g.ID, rbacDB.Globals[0].RBACGroupsIDs[0])
+
+	require.Equal(t, 1, len(rbacDB.Projects))
+	require.Equal(t, "read", rbacDB.Projects[0].Role)
+	require.Equal(t, 1, len(rbacDB.Projects[0].RBACUsersIDs))
+	require.Equal(t, 1, len(rbacDB.Projects[0].RBACGroupsIDs))
+	require.Equal(t, u.ID, rbacDB.Projects[0].RBACUsersIDs[0])
+	require.Equal(t, g.ID, rbacDB.Projects[0].RBACGroupsIDs[0])
+}
diff --git a/engine/worker/internal/handler_cache_test.go b/engine/worker/internal/handler_cache_test.go
index 7b81ca258..b7deab675 100644
--- a/engine/worker/internal/handler_cache_test.go
+++ b/engine/worker/internal/handler_cache_test.go
@@ -107,8 +107,8 @@ func Test_cachePushPullHandler(t *testing.T) {
 	m := mock_cdsclient.NewMockWorkerInterface(ctrl)
 	wkPush.client = m
 	wkPull.client = m
-	wkPush.cdnHttpAddr = "https://cdn.local"
-	wkPull.cdnHttpAddr = "https://cdn.local"
+	wkPush.cfg = &workerruntime.WorkerConfig{CDNEndpoint: "https://cdn.local"}
+	wkPull.cfg = &workerruntime.WorkerConfig{CDNEndpoint: "https://cdn.local"}
 
 	var bodyBytes []byte
 	m.EXPECT().CDNItemUpload(gomock.Any(), "https://cdn.local", gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(
diff --git a/engine/worker/internal/start_test.go b/engine/worker/internal/start_test.go
index 4e994b42e..6a30db0a9 100644
--- a/engine/worker/internal/start_test.go
+++ b/engine/worker/internal/start_test.go
@@ -357,6 +357,7 @@ export FOO_FROM_HOOK=BAR`,
 		Model:               "my-model",
 		Region:              "local-test",
 		Basedir:             basedir,
+		GelfServiceAddr:     "localhost:8090",
 	}
 	if err := w.Init(cfg, afero.NewBasePathFs(fs, basedir)); err != nil {
 		t.Fatalf("worker init failed: %v", err)
diff --git a/sdk/interpolate/interpolate_test.go b/sdk/interpolate/interpolate_test.go
index 635e40900..2ea375ac9 100644
--- a/sdk/interpolate/interpolate_test.go
+++ b/sdk/interpolate/interpolate_test.go
@@ -673,3 +673,12 @@ func TestDashReplacementWithµµµ(t *testing.T) {
 	assert.NoError(t, err)
 	assert.Equal(t, "result.headers.x-cache is a", got)
 }
+
+func TestStringQuote(t *testing.T) {
+	vars := map[string]string{
+		"content": `{"foo": "{\"bar\":\"baz\"}"}`,
+	}
+	got, err := Do("content is {{.content | stringQuote}}", vars)
+	assert.NoError(t, err)
+	assert.Equal(t, `content is {\"foo\": \"{\\\"bar\\\":\\\"baz\\\"}\"}`, got)
+}
diff --git a/sdk/rbac_global_test.go b/sdk/rbac_global_test.go
index 1186bb5b0..caffbd2fc 100644
--- a/sdk/rbac_global_test.go
+++ b/sdk/rbac_global_test.go
@@ -5,34 +5,34 @@ import (
 	"testing"
 )
 
-func TestRbacGlobalInvalidGlobalRole(t *testing.T) {
+func TestRBACGlobalInvalidGlobalRole(t *testing.T) {
 	rb := RBACGlobal{
 		Role:          "runWorkflow",
 		RBACGroupsIDs: []int64{1},
 		RBACUsersIDs:  []string{"aa-aa-aa"},
 	}
-	err := isValidRbacGlobal("myRule", rb)
+	err := isValidRBACGlobal("myRule", rb)
 	require.Error(t, err)
 	require.Contains(t, err.Error(), "rbac myRule: role runWorkflow is not allowed on a global permission")
 }
-func TestRbacGlobalInvalidGroupAndUsers(t *testing.T) {
+func TestRBACGlobalInvalidGroupAndUsers(t *testing.T) {
 	rb := RBACGlobal{
 		Role:          RoleCreateProject,
 		RBACGroupsIDs: []int64{},
 		RBACUsersIDs:  []string{},
 	}
-	err := isValidRbacGlobal("myRule", rb)
+	err := isValidRBACGlobal("myRule", rb)
 	require.Error(t, err)
 	require.Contains(t, err.Error(), "rbac myRule: missing groups or users on global permission")
 }
 
-func TestRbacGlobalEmptyRole(t *testing.T) {
+func TestRBACGlobalEmptyRole(t *testing.T) {
 	rb := RBACGlobal{
 		Role:          "",
 		RBACGroupsIDs: []int64{1},
 		RBACUsersIDs:  []string{},
 	}
-	err := isValidRbacGlobal("myRule", rb)
+	err := isValidRBACGlobal("myRule", rb)
 	require.Error(t, err)
 	require.Contains(t, err.Error(), "rbac myRule: role for global permission cannot be empty")
 }
diff --git a/sdk/rbac_project_test.go b/sdk/rbac_project_test.go
index 6179ecfe6..87a969bf9 100644
--- a/sdk/rbac_project_test.go
+++ b/sdk/rbac_project_test.go
@@ -7,7 +7,7 @@ import (
 	"github.com/stretchr/testify/require"
 )
 
-func TestRbacProjectInvalidRole(t *testing.T) {
+func TestRBACProjectInvalidRole(t *testing.T) {
 	rb := RBACProject{
 		RBACProjectKeys: []string{"foo"},
 		All:             false,
@@ -15,11 +15,11 @@ func TestRbacProjectInvalidRole(t *testing.T) {
 		RBACGroupsIDs:   []int64{1},
 		RBACUsersIDs:    []string{"aa-aa-aa"},
 	}
-	err := isValidRbacProject("myRule", rb)
+	err := isValidRBACProject("myRule", rb)
 	require.Error(t, err)
 	require.Contains(t, err.Error(), fmt.Sprintf("rbac myRule: role %s is not allowed on a project permission", RoleCreateProject))
 }
-func TestRbacProjectInvalidGroupAndUsers(t *testing.T) {
+func TestRBACProjectInvalidGroupAndUsers(t *testing.T) {
 	rb := RBACProject{
 		RBACProjectKeys: []string{"foo"},
 		All:             false,
@@ -27,11 +27,11 @@ func TestRbacProjectInvalidGroupAndUsers(t *testing.T) {
 		RBACGroupsIDs:   []int64{},
 		RBACUsersIDs:    []string{},
 	}
-	err := isValidRbacProject("myRule", rb)
+	err := isValidRBACProject("myRule", rb)
 	require.Error(t, err)
 	require.Contains(t, err.Error(), "rbac myRule: missing groups or users on project permission")
 }
-func TestRbacProjectInvalidProjectKeys(t *testing.T) {
+func TestRBACProjectInvalidProjectKeys(t *testing.T) {
 	rb := RBACProject{
 		RBACProjectKeys: []string{},
 		All:             false,
@@ -39,11 +39,11 @@ func TestRbacProjectInvalidProjectKeys(t *testing.T) {
 		RBACGroupsIDs:   []int64{1},
 		RBACUsersIDs:    []string{},
 	}
-	err := isValidRbacProject("myRule", rb)
+	err := isValidRBACProject("myRule", rb)
 	require.Error(t, err)
 	require.Contains(t, err.Error(), "rbac myRule: must have at least 1 project on a project permission")
 }
-func TestRbacProjectEmptyRole(t *testing.T) {
+func TestRBACProjectEmptyRole(t *testing.T) {
 	rb := RBACProject{
 		RBACProjectKeys: []string{"foo"},
 		All:             false,
@@ -51,11 +51,11 @@ func TestRbacProjectEmptyRole(t *testing.T) {
 		RBACGroupsIDs:   []int64{1},
 		RBACUsersIDs:    []string{},
 	}
-	err := isValidRbacProject("myRule", rb)
+	err := isValidRBACProject("myRule", rb)
 	require.Error(t, err)
 	require.Contains(t, err.Error(), "rbac myRule: role for project permission cannot be empty")
 }
-func TestRbacProjectInvalidAllAndListOfProject(t *testing.T) {
+func TestRBACProjectInvalidAllAndListOfProject(t *testing.T) {
 	rb := RBACProject{
 		RBACProjectKeys: []string{"foo"},
 		All:             true,
@@ -63,7 +63,7 @@ func TestRbacProjectInvalidAllAndListOfProject(t *testing.T) {
 		RBACGroupsIDs:   []int64{1},
 		RBACUsersIDs:    []string{},
 	}
-	err := isValidRbacProject("myRule", rb)
+	err := isValidRBACProject("myRule", rb)
 	require.Error(t, err)
 	require.Contains(t, err.Error(), "rbac myRule: you can't have a list of project and the all flag checked on a project permission")
 }
diff --git a/tests/06_sc_workflow_with_artifactory.yml b/tests/06_sc_workflow_with_artifactory.yml
index 8692c1859..c9fd62894 100644
--- a/tests/06_sc_workflow_with_artifactory.yml
+++ b/tests/06_sc_workflow_with_artifactory.yml
@@ -23,10 +23,14 @@ testcases:
     info: "{{.result.systemout}}"
     assertions:
     - result.code ShouldEqual 0
-    - "result.systemout ShouldContainSubstring Workflowtest-artifactory-multiple-maturity #1 has been launched"
+    - "result.systemout ShouldContainSubstring has been launched"
+  - script:  {{.cdsctl}} -f {{.cdsctl.config}} workflow history {{.projectKey}} test-artifactory-multiple-maturity --format json # get the latest run number
+    vars:
+      runNumber:
+        from: result.systemoutjson.systemoutjson0.num
 - name: check_workflow_is_success
   steps:
-  - script: {{.cdsctl}} -f {{.cdsctl.config}} workflow status {{.projectKey}}  test-artifactory-multiple-maturity 1 --format json # check the workflow status
+  - script: {{.cdsctl}} -f {{.cdsctl.config}} workflow status {{.projectKey}}  test-artifactory-multiple-maturity {{.run_workflow.runNumber}} --format json # check the workflow status
     retry: 120
     delay: 5
     info: "{{.result.systemout}}"
diff --git a/tests/08_v2_analyze.yml b/tests/08_v2_analyze.yml
index 5d8c3ba8c..e986ab4a3 100644
--- a/tests/08_v2_analyze.yml
+++ b/tests/08_v2_analyze.yml
@@ -15,7 +15,7 @@ testcases:
   - script: gpg --import ./fixtures/gpg/key.priv
   - script: gpg --import ./fixtures/gpg/key.pub
   - script: 'curl -X POST {{.git.host}}/api/v1/user/gpg_keys -u "{{.git.user}}:{{.git.password}}" -H "Content-type: application/json" -d @./fixtures/gpg/key.pub.json'
-
+  - script: {{.cdsctl}} -f {{.cdsctl.config}} experimental rbac import ./fixtures/rbac/rbac_admin.yml --force
 
 - name: import / export vcs server
   steps:
@@ -73,9 +73,8 @@ testcases:
       delay: 2
       assertions:
         - result.code ShouldEqual 0
-        - 'result.systemout ShouldContainSubstring "status: Skipped"'
+        - 'result.systemout ShouldContainSubstring "status: Success"'
         - 'result.systemout ShouldContainSubstring "key_sign_id: E027B500E97E52E7"'
-        - result.systemout ShouldContainSubstring "doesn't have enough right on project ITCLIPRJVCS"
 
 - name: delete repository on project
   steps:
diff --git a/tests/fixtures/04SCWorkflowRunSimplePlugin/go.sum b/tests/fixtures/04SCWorkflowRunSimplePlugin/go.sum
index f70cf5c69..9132a168c 100644
--- a/tests/fixtures/04SCWorkflowRunSimplePlugin/go.sum
+++ b/tests/fixtures/04SCWorkflowRunSimplePlugin/go.sum
@@ -294,8 +294,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
 github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
 github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
 github.com/ulikunitz/xz v0.5.9 h1:RsKRIA2MO8x56wkkcd3LbtcE/uMszhb6DpRf+3uwa3I=
@@ -668,8 +668,8 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/tests/fixtures/rbac/rbac_admin.yml b/tests/fixtures/rbac/rbac_admin.yml
new file mode 100644
index 000000000..b1cea20e7
--- /dev/null
+++ b/tests/fixtures/rbac/rbac_admin.yml
@@ -0,0 +1,8 @@
+name: perm-test
+projects:
+  - role: manage
+    projects: [ITCLIPRJVCS]
+    users: [cds.integration.tests.rw]
+globals:
+  - role: manage-permission
+    users: [cds.integration.tests.rw]
diff --git a/ui/src/app/views/workflow/run/node/test/table/test.table.component.ts b/ui/src/app/views/workflow/run/node/test/table/test.table.component.ts
index 75ed5ac35..2d51681f1 100644
--- a/ui/src/app/views/workflow/run/node/test/table/test.table.component.ts
+++ b/ui/src/app/views/workflow/run/node/test/table/test.table.component.ts
@@ -5,6 +5,7 @@ import { Column, ColumnType, Filter } from 'app/shared/table/data-table.componen
 import { cloneDeep } from 'lodash-es';
 import { Subscription } from 'rxjs';
 import { finalize } from 'rxjs/operators';
+import { HookStatus } from 'app/model/workflow.hook.model';
 
 
 @Component({
@@ -66,15 +67,25 @@ export class WorkflowRunTestTableComponent implements OnInit {
         this.columns = [
             <Column<TestCase>>{
                 type: ColumnType.ICON,
-                class: 'one',
                 selector: (tc: TestCase) => ({
-                    icon: (() => {
+                    iconTheme: 'outline',
+                    iconType: (() => {
                         if (tc.status === 'success') {
-                            return ['green', 'check', 'icon'];
+                            return 'check';
                         } else if (tc.status === 'failed') {
-                            return ['red', 'remove', 'circle', 'icon'];
+                            return 'stop';
+                        } else {
+                            return 'stop';
+                        }
+                    })(),
+                    iconColor: (() => {
+                        if (tc.status === 'success') {
+                            return 'green';
+                        } else if (tc.status === 'failed') {
+                            return 'red';
+                        } else {
+                            return 'grey';
                         }
-                        return ['grey', 'ban', 'icon'];
                     })()
                 })
             },
@@ -84,11 +95,12 @@ export class WorkflowRunTestTableComponent implements OnInit {
             },
             <Column<TestCase>>{
                 type: ColumnType.BUTTON,
-                name: '',
-                class: 'two right aligned',
+                name: 'Action',
+                class: 'rightAlign',
                 selector: (tc: TestCase) => ({
-                    icon: 'eye',
-                    class: 'icon small',
+                    buttonDanger: false,
+                    iconType: 'eye',
+                    iconTheme: 'outline',
                     click: () => this.clickTestCase(tc)
                 })
             },