Repository: gravitee-io/graviteeio-access-management
Pull request #700 (2020-01-08 12:08:14)

feat(acl): remove global dashboard for a new landing page
closes gravitee-io/issues#3024


Patch:
diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/exception/SystemRoleDeleteException.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/exception/SystemRoleDeleteException.java
new file mode 100644
index 0000000000..679a5f4ba5
--- /dev/null
+++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/exception/SystemRoleDeleteException.java
@@ -0,0 +1,41 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.gravitee.am.service.exception;
+
+import io.gravitee.common.http.HttpStatusCode;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class SystemRoleDeleteException extends AbstractManagementException {
+
+    private final String role;
+
+    public SystemRoleDeleteException(String role) {
+        this.role = role;
+    }
+
+    @Override
+    public int getHttpStatusCode() {
+        return HttpStatusCode.BAD_REQUEST_400;
+    }
+
+    @Override
+    public String getMessage() {
+        return "Role [" + role + "] is flag as system and cannot be deleted.";
+    }
+}
diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/exception/SystemRoleUpdateException.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/exception/SystemRoleUpdateException.java
new file mode 100644
index 0000000000..05d032314b
--- /dev/null
+++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/exception/SystemRoleUpdateException.java
@@ -0,0 +1,41 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.gravitee.am.service.exception;
+
+import io.gravitee.common.http.HttpStatusCode;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class SystemRoleUpdateException extends AbstractManagementException {
+
+    private final String role;
+
+    public SystemRoleUpdateException(String role) {
+        this.role = role;
+    }
+
+    @Override
+    public int getHttpStatusCode() {
+        return HttpStatusCode.BAD_REQUEST_400;
+    }
+
+    @Override
+    public String getMessage() {
+        return "Role [" + role + "] is flag as system and cannot be updated.";
+    }
+}
diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/RoleServiceImpl.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/RoleServiceImpl.java
index e5bb10c0e0..18a899f24b 100644
--- a/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/RoleServiceImpl.java
+++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/RoleServiceImpl.java
@@ -28,10 +28,7 @@ import io.gravitee.am.repository.management.api.RoleRepository;
 import io.gravitee.am.service.AuditService;
 import io.gravitee.am.service.EventService;
 import io.gravitee.am.service.RoleService;
-import io.gravitee.am.service.exception.AbstractManagementException;
-import io.gravitee.am.service.exception.RoleAlreadyExistsException;
-import io.gravitee.am.service.exception.RoleNotFoundException;
-import io.gravitee.am.service.exception.TechnicalManagementException;
+import io.gravitee.am.service.exception.*;
 import io.gravitee.am.service.model.NewRole;
 import io.gravitee.am.service.model.UpdateRole;
 import io.gravitee.am.service.reporter.builder.AuditBuilder;
@@ -150,6 +147,12 @@ public class RoleServiceImpl implements RoleService {
 
         return roleRepository.findById(id)
                 .switchIfEmpty(Maybe.error(new RoleNotFoundException(id)))
+                .map(role -> {
+                    if (role.isSystem()) {
+                        throw new SystemRoleUpdateException(id);
+                    }
+                    return role;
+                })
                 .flatMapSingle(oldRole -> {
                     // check if role name is unique
                     return checkRoleUniqueness(updateRole.getName(), oldRole.getId(), domain)
@@ -185,6 +188,12 @@ public class RoleServiceImpl implements RoleService {
         LOGGER.debug("Delete role {}", roleId);
         return roleRepository.findById(roleId)
                 .switchIfEmpty(Maybe.error(new RoleNotFoundException(roleId)))
+                .map(role -> {
+                    if (role.isSystem()) {
+                        throw new SystemRoleDeleteException(roleId);
+                    }
+                    return role;
+                })
                 .flatMapCompletable(role -> roleRepository.delete(roleId)
                         .andThen(Completable.fromSingle(eventService.create(new Event(Type.ROLE, new Payload(role.getId(), role.getDomain(), Action.DELETE)))))
                         .doOnComplete(() -> auditService.report(AuditBuilder.builder(RoleAuditBuilder.class).principal(principal).type(EventType.ROLE_DELETED).role(role)))
diff --git a/gravitee-am-ui/src/app/app-routing.module.ts b/gravitee-am-ui/src/app/app-routing.module.ts
index edfe2dcfc8..f52ff4a635 100644
--- a/gravitee-am-ui/src/app/app-routing.module.ts
+++ b/gravitee-am-ui/src/app/app-routing.module.ts
@@ -40,7 +40,7 @@ import {DomainSettingsExtensionGrantsComponent} from "./domain/settings/extensio
 import {DomainSettingsAccountComponent} from "./domain/settings/account/account.component";
 import {DomainSettingsPoliciesComponent} from "./domain/settings/policies/policies.component";
 import {DomainSettingsMembershipsComponent} from "./domain/settings/memberships/memberships.component";
-import {DomainCreationComponent} from "./settings/domains/creation/domain-creation.component";
+import {DomainCreationComponent} from "./domain/creation/domain-creation.component";
 import {ProviderCreationComponent} from "./domain/settings/providers/creation/provider-creation.component";
 import {ProviderComponent} from "./domain/settings/providers/provider/provider.component";
 import {LogoutCallbackComponent} from "./logout/callback/callback.component";
@@ -65,7 +65,6 @@ import {ScopeResolver} from "./resolvers/scope.resolver";
 import {ScopesResolver} from "./resolvers/scopes.resolver";
 import {ScopeCreationComponent} from "./domain/settings/scopes/creation/scope-creation.component";
 import {ScopeComponent} from './domain/settings/scopes/scope/scope.component';
-import {DashboardComponent} from "./dashboard/dashboard.component";
 import {SettingsComponent} from "./settings/settings.component";
 import {DummyComponent} from "./components/dummy/dummy.component";
 import {UsersComponent} from "./domain/settings/users/users.component";
@@ -136,43 +135,9 @@ import {ManagementRoleComponent} from "./settings/management/roles/role/role.com
 import {MembershipsResolver} from "./resolvers/memberships.resolver";
 import {SettingsResolver} from "./resolvers/settings.resolver";
 import {AuthGuard} from "./guards/auth-guard.service";
+import {HomeComponent} from "./home/home.component";
 
 const routes: Routes = [
-  { path: 'dashboard',
-    component: DashboardComponent,
-    data: {
-      menu: {
-        label: 'Dashboard',
-        icon: 'dashboard',
-        firstLevel: true
-      }
-    }
-  },
-  { path: 'dashboard/applications',
-    component: ApplicationsComponent,
-    resolve: {
-      applications: ApplicationsResolver
-    },
-    data: {
-      menu: {
-        label: 'Applications',
-        icon: 'apps',
-        firstLevel: true
-      }
-    },
-  },
-  { path: 'dashboard/applications/new',
-    component: ApplicationCreationComponent,
-    resolve: {
-      domains: DomainsResolver
-    },
-    data: {
-      menu: {
-        displayFirstLevel: true,
-        activeParentPath: 'dashboard/applications'
-      }
-    }
-  },
   {
     path: 'settings', component: SettingsComponent,
     data: {
@@ -1198,7 +1163,17 @@ const routes: Routes = [
   { path: 'logout', component: LogoutComponent },
   { path: 'logout/callback', component: LogoutCallbackComponent },
   { path: 'dummy', component: DummyComponent },
-  { path: '', redirectTo: '/dashboard', pathMatch: 'full' }
+  { path: 'h', component: HomeComponent },
+  { path: 'h/domains/new',
+    component: DomainCreationComponent,
+    canActivate: [AuthGuard],
+    data: {
+      perms: {
+        only: ['management_domain_create']
+      }
+    },
+  },
+  { path: '', redirectTo: 'h', pathMatch: 'full'}
 ];
 
 @NgModule({
diff --git a/gravitee-am-ui/src/app/app.module.ts b/gravitee-am-ui/src/app/app.module.ts
index c91f8bc6aa..4df8aa77e2 100644
--- a/gravitee-am-ui/src/app/app.module.ts
+++ b/gravitee-am-ui/src/app/app.module.ts
@@ -46,6 +46,7 @@ import "codemirror/addon/selection/mark-selection";
 
 import { AppRoutingModule } from './app-routing.module';
 import { AppComponent } from './app.component';
+import { HomeComponent } from "./home/home.component";
 import { SidenavComponent } from './components/sidenav/sidenav.component';
 import { SidenavSettingsComponent } from "./components/sidenav-settings/sidenav-settings.component";
 import { LoginComponent } from './login/login.component';
@@ -54,11 +55,12 @@ import { DomainsComponent } from './settings/domains/domains.component';
 import { DomainService } from './services/domain.service';
 import { DomainComponent } from './domain/domain.component';
 import { SidenavService } from "./components/sidenav/sidenav.service";
+import { NavbarService } from "./components/navbar/navbar.service";
 import { ConfirmComponent } from './components/dialog/confirm/confirm.component';
 import { DialogService } from "./services/dialog.service";
 import { SnackbarService } from "./services/snackbar.service";
 import { EmptystateComponent } from './components/emptystate/emptystate.component';
-import { DomainCreationComponent } from './settings/domains/creation/domain-creation.component';
+import { DomainCreationComponent } from './domain/creation/domain-creation.component';
 import { ProviderCreationComponent } from './domain/settings/providers/creation/provider-creation.component';
 import { ProviderCreationStep1Component } from './domain/settings/providers/creation/steps/step1/step1.component';
 import { ProviderCreationStep2Component } from './domain/settings/providers/creation/steps/step2/step2.component';
@@ -120,8 +122,7 @@ import { ScopeResolver } from "./resolvers/scope.resolver";
 import { ScopeCreationComponent } from './domain/settings/scopes/creation/scope-creation.component';
 import { ScopeComponent } from './domain/settings/scopes/scope/scope.component';
 import { SnackbarComponent } from "./components/snackbar/snackbar.component";
-import { NavbarComponent } from './components/navbar/navbar.component';
-import { DashboardComponent } from './dashboard/dashboard.component';
+import { NavbarComponent} from './components/navbar/navbar.component';
 import { DashboardService} from "./services/dashboard.service";
 import { WidgetApplicationsComponent } from './components/widget/applications/applications.component';
 import { WidgetTopApplicationsComponent } from './components/widget/top-applications/top-applications.component';
@@ -236,6 +237,7 @@ import { HasPermissionDirective } from "./directives/has-permission.directive";
 @NgModule({
   declarations: [
     AppComponent,
+    HomeComponent,
     SidenavComponent,
     SidenavSettingsComponent,
     LoginComponent,
@@ -295,7 +297,6 @@ import { HasPermissionDirective } from "./directives/has-permission.directive";
     ExtensionGrantFormComponent,
     SnackbarComponent,
     NavbarComponent,
-    DashboardComponent,
     WidgetApplicationsComponent,
     WidgetTopApplicationsComponent,
     WidgetTotalApplicationsComponent,
@@ -392,6 +393,7 @@ import { HasPermissionDirective } from "./directives/has-permission.directive";
     DomainService,
     ProviderService,
     SidenavService,
+    NavbarService,
     DialogService,
     SnackbarService,
     PlatformService,
diff --git a/gravitee-am-ui/src/app/components/navbar/navbar.component.html b/gravitee-am-ui/src/app/components/navbar/navbar.component.html
index 98b9bbbde5..ef3f71d2c5 100644
--- a/gravitee-am-ui/src/app/components/navbar/navbar.component.html
+++ b/gravitee-am-ui/src/app/components/navbar/navbar.component.html
@@ -17,17 +17,17 @@
 -->
 <mat-toolbar>
     <span class="fill-remaining-space"></span>
-    <button *ngIf="isAuthenticated()" mat-icon-button [matMenuTriggerFor]="userAccountMenu" (click)="listDomains()">
-      <mat-icon style="color: grey;">account_circle</mat-icon>
+    <span class="currentDomain">{{currentResource?.name | lowercase}}</span>
+    <button class="userAccountMenu" mat-icon-button [matMenuTriggerFor]="userAccountMenu" disableRipple (click)="listDomains()">
+      <img [src]="avatarUrl(user)" alt="Photo of a {{user}}" (error)="userAvatar.src = 'assets/default-account-icon.png'" #userAvatar width="30"/>
+      <mat-icon>keyboard_arrow_down</mat-icon>
     </button>
 </mat-toolbar>
 
 <mat-menu #userAccountMenu="matMenu" [overlapTrigger]="false">
   <div class="userAccount" fxLayout="column">
     <div class="userAccountProfile" fxLayout="row">
-      <div class="userAccountPicture">
-        <mat-icon>account_circle</mat-icon>
-      </div>
+      <img [src]="avatarUrl(user)" alt="Photo of a {{user}}" (error)="userAvatar.src = 'assets/default-account-icon.png'" #userAvatar width="80"/>
       <div class="userAccountInfo" fxLayoutAlign="start center" style="margin: 20px;">
         <span>{{user}}</span>
       </div>
@@ -44,16 +44,10 @@
         </a>
       </mat-list>
     </div>
-    <div class="userAccountActions">
-      <a [routerLink]="['/settings']" mat-menu-item>
-        <mat-icon>settings</mat-icon>
-        <span>Global settings</span>
-      </a>
-    </div>
-    <div class="userAccountActions">
-      <a [routerLink]="['/logout']" mat-menu-item>
-        <mat-icon>exit_to_app</mat-icon>
-        <span>Sign out</span>
+    <div class="userAccountActions" *ngFor="let link of navLinks">
+      <a [routerLink]="link.href" mat-menu-item>
+        <mat-icon>{{link.icon}}</mat-icon>
+        <span>{{link.label}}</span>
       </a>
     </div>
   </div>
diff --git a/gravitee-am-ui/src/app/components/navbar/navbar.component.scss b/gravitee-am-ui/src/app/components/navbar/navbar.component.scss
index d72d940ba2..1d6ba61f84 100644
--- a/gravitee-am-ui/src/app/components/navbar/navbar.component.scss
+++ b/gravitee-am-ui/src/app/components/navbar/navbar.component.scss
@@ -10,6 +10,19 @@ mat-toolbar {
   .fill-remaining-space {
     flex: 1 1 auto;
   }
+  .currentDomain {
+    font-size: 14px;
+    margin-right: 8px;
+  }
+  .userAccountMenu {
+    mat-icon {
+      color: grey;
+      font-size: 16px;
+      width: 16px;
+      height: 16px;
+      line-height: 16px;
+    }
+  }
 }
 
 /deep/ .mat-toolbar-row {
@@ -26,14 +39,6 @@ mat-toolbar {
 
 .userAccount {
   width: 380px;
-  .userAccountPicture {
-    mat-icon {
-      font-size: 78px;
-      height: 78px;
-      width: 78px;
-      color: grey;
-    }
-  }
   .userAccountActions {
     border-top: 1px solid rgb(226, 229, 231);
   }
diff --git a/gravitee-am-ui/src/app/components/navbar/navbar.component.ts b/gravitee-am-ui/src/app/components/navbar/navbar.component.ts
index c25842846d..bc1ed2049a 100644
--- a/gravitee-am-ui/src/app/components/navbar/navbar.component.ts
+++ b/gravitee-am-ui/src/app/components/navbar/navbar.component.ts
@@ -13,33 +13,54 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import { Component, OnInit } from '@angular/core';
-import { AuthService } from "../../services/auth.service";
-import { DomainService } from "../../services/domain.service";
-import { Router } from "@angular/router";
+import {Component, OnDestroy, OnInit} from '@angular/core';
+import {Router} from "@angular/router";
+import {AuthService} from "../../services/auth.service";
+import {DomainService} from "../../services/domain.service";
+import {Subscription} from "rxjs";
+import {NavbarService} from "./navbar.service";
+import {SnackbarService} from "../../services/snackbar.service";
+import * as _ from 'lodash';
 
 @Component({
   selector: 'gs-navbar',
   templateUrl: './navbar.component.html',
   styleUrls: ['./navbar.component.scss']
 })
-export class NavbarComponent implements OnInit {
+export class NavbarComponent implements OnInit, OnDestroy {
+  private subscription: Subscription;
   domains: any[];
+  currentResource: any = {};
+  navLinks: any = [
+    {'href': '/h/domains/new' , 'label': 'Create domain', 'icon': 'add'},
+    {'href': '/settings' , 'label': 'Global settings', 'icon': 'settings'},
+    {'href': '/logout' , 'label': 'Sign out', 'icon': 'exit_to_app'},
+  ];
 
-  constructor(private authService: AuthService, private domainService: DomainService, private router:Router) { }
-
-  ngOnInit() {
+  constructor(private authService: AuthService,
+              private domainService: DomainService,
+              private navbarService: NavbarService,
+              private snackbarService: SnackbarService,
+              private router: Router) {
     if (!this.authService.user()) {
-      this.authService.userInfo().subscribe();
+      this.authService.userInfo().subscribe(() => this.initNavLinks());
+    } else {
+      this.initNavLinks();
     }
   }
 
-  get user() {
-    return this.authService.user() != null ? this.authService.user().preferred_username : null;
+  ngOnInit() {
+    this.subscription = this.navbarService.notifyObservable$.subscribe(data => {
+      this.currentResource = data;
+    });
   }
 
-  isAuthenticated() {
-    return this.authService.isAuthenticated();
+  ngOnDestroy(): void {
+    this.subscription.unsubscribe();
+  }
+
+  get user() {
+    return this.authService.user() != null ? this.authService.user().preferred_username : null;
   }
 
   listDomains() {
@@ -51,4 +72,21 @@ export class NavbarComponent implements OnInit {
     this.router.navigateByUrl('/dummy', { skipLocationChange: true })
       .then(() => this.router.navigate(routerLink));
   }
+
+  avatarUrl(user) {
+    return user ? 'assets/material-letter-icons/' + user.charAt(0).toUpperCase() + '.svg' : '';
+  }
+
+  private initNavLinks() {
+    if (!this.canDisplay(['management_domain_create'])) {
+      _.remove(this.navLinks, { href: '/h/domains/new' });
+    }
+    if (!this.canDisplay(['management_settings_read'])) {
+      _.remove(this.navLinks, { href: '/settings' });
+    }
+  }
+
+  private canDisplay(permissions): boolean {
+    return this.authService.isAdmin() || this.authService.hasPermissions(permissions);
+  }
 }
diff --git a/gravitee-am-ui/src/app/dashboard/dashboard.component.spec.ts b/gravitee-am-ui/src/app/components/navbar/navbar.service.spec.ts
similarity index 55%
rename from gravitee-am-ui/src/app/dashboard/dashboard.component.spec.ts
rename to gravitee-am-ui/src/app/components/navbar/navbar.service.spec.ts
index 5a238bc60a..4fe6ef39be 100644
--- a/gravitee-am-ui/src/app/dashboard/dashboard.component.spec.ts
+++ b/gravitee-am-ui/src/app/components/navbar/navbar.service.spec.ts
@@ -13,28 +13,18 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { TestBed, inject } from '@angular/core/testing';
 
-import { DashboardComponent } from './dashboard.component';
-
-describe('DashboardComponent', () => {
-  let component: DashboardComponent;
-  let fixture: ComponentFixture<DashboardComponent>;
-
-  beforeEach(async(() => {
-    TestBed.configureTestingModule({
-      declarations: [ DashboardComponent ]
-    })
-    .compileComponents();
-  }));
+import { NavbarService } from './navbar.service';
 
+describe('NavbarService', () => {
   beforeEach(() => {
-    fixture = TestBed.createComponent(DashboardComponent);
-    component = fixture.componentInstance;
-    fixture.detectChanges();
+    TestBed.configureTestingModule({
+      providers: [NavbarService]
+    });
   });
 
-  it('should be created', () => {
-    expect(component).toBeTruthy();
-  });
+  it('should ...', inject([NavbarService], (service: NavbarService) => {
+    expect(service).toBeTruthy();
+  }));
 });
diff --git a/gravitee-am-ui/src/app/dashboard/dashboard.component.ts b/gravitee-am-ui/src/app/components/navbar/navbar.service.ts
similarity index 67%
rename from gravitee-am-ui/src/app/dashboard/dashboard.component.ts
rename to gravitee-am-ui/src/app/components/navbar/navbar.service.ts
index dacb239bc2..0ec9e790f8 100644
--- a/gravitee-am-ui/src/app/dashboard/dashboard.component.ts
+++ b/gravitee-am-ui/src/app/components/navbar/navbar.service.ts
@@ -13,18 +13,19 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import { Component, OnInit } from '@angular/core';
+import { Injectable } from '@angular/core';
+import { Subject } from "rxjs";
 
-@Component({
-  selector: 'app-dashboard',
-  templateUrl: './dashboard.component.html',
-  styleUrls: ['./dashboard.component.scss']
-})
-export class DashboardComponent implements OnInit {
+@Injectable()
+export class NavbarService {
+  private currentResource = new Subject<any>();
+  public notifyObservable$ = this.currentResource.asObservable();
 
   constructor() { }
 
-  ngOnInit() {
+  notify(data) {
+    if (data) {
+      this.currentResource.next(data);
+    }
   }
-
 }
diff --git a/gravitee-am-ui/src/app/components/sidenav/sidenav.component.html b/gravitee-am-ui/src/app/components/sidenav/sidenav.component.html
index 91b51cf5cf..ab68b823a1 100644
--- a/gravitee-am-ui/src/app/components/sidenav/sidenav.component.html
+++ b/gravitee-am-ui/src/app/components/sidenav/sidenav.component.html
@@ -39,10 +39,6 @@
       </mat-list-item>
     </div>
 
-    <mat-list-item *ngIf="!reducedMode && !displaySettingsLevel && currentSubPaths && currentSubPaths.length > 0">
-      <h4>{{currentResource?.name}}</h4>
-    </mat-list-item>
-
     <mat-list-item *ngFor="let subPath of currentSubPaths">
       <a [routerLink]="subPath.fullPath" title="{{subPath.data.menu.label}}"
          routerLinkActive="sidenav-active">
diff --git a/gravitee-am-ui/src/app/components/sidenav/sidenav.component.ts b/gravitee-am-ui/src/app/components/sidenav/sidenav.component.ts
index 84ad969443..b04496b454 100644
--- a/gravitee-am-ui/src/app/components/sidenav/sidenav.component.ts
+++ b/gravitee-am-ui/src/app/components/sidenav/sidenav.component.ts
@@ -31,14 +31,13 @@ import { AuthGuard } from "../../guards/auth-guard.service";
 export class SidenavComponent implements OnInit, OnDestroy {
   title = AppConfig.settings.portalTitle;
   version = AppConfig.settings.version;
-  reducedMode: boolean = false;
+  reducedMode = false;
   paths: any[] = [];
   subPaths: any = {};
   currentSubPaths: any[] = [];
-  currentResource: any = {};
   subscription: Subscription;
-  displayFirstLevel: boolean = false;
-  displaySettingsLevel: boolean = false;
+  displayFirstLevel = false;
+  displaySettingsLevel = false;
 
   constructor(private router: Router,
               private currentRoute: ActivatedRoute,
@@ -56,9 +55,6 @@ export class SidenavComponent implements OnInit, OnDestroy {
       }
     }
     this.watchRoute();
-    this.subscription = this.sidenavService.notifyObservable$.subscribe(data => {
-      this.currentResource = data;
-    });
   }
 
   ngOnDestroy() {
diff --git a/gravitee-am-ui/src/app/components/sidenav/sidenav.service.ts b/gravitee-am-ui/src/app/components/sidenav/sidenav.service.ts
index d29419989b..37120433d5 100644
--- a/gravitee-am-ui/src/app/components/sidenav/sidenav.service.ts
+++ b/gravitee-am-ui/src/app/components/sidenav/sidenav.service.ts
@@ -18,19 +18,11 @@ import { Subject } from "rxjs";
 
 @Injectable()
 export class SidenavService {
-  private currentResource = new Subject<any>();
   private resizeSidenav = new Subject<boolean>();
-  public notifyObservable$ = this.currentResource.asObservable();
   public resizeSidenavObservable = this.resizeSidenav.asObservable();
 
   constructor() { }
 
-  notify(data) {
-    if (data) {
-      this.currentResource.next(data);
-    }
-  }
-
   resize(reducedMode) {
     this.resizeSidenav.next(reducedMode);
   }
diff --git a/gravitee-am-ui/src/app/dashboard/dashboard.component.html b/gravitee-am-ui/src/app/dashboard/dashboard.component.html
deleted file mode 100644
index e8250837dc..0000000000
--- a/gravitee-am-ui/src/app/dashboard/dashboard.component.html
+++ /dev/null
@@ -1,57 +0,0 @@
-<!--
-
-    Copyright (C) 2015 The Gravitee team (http://gravitee.io)
-
-    Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-            http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
-
--->
-<div class="dashboard-container">
-  <h1>Dashboard</h1>
-  <div class="dashboard-content" fxLayout="row">
-    <section fxFlex="70">
-      <div fxLayout="row" fxLayoutGap="30px" class="main-widgets">
-        <widget-total-applications></widget-total-applications>
-        <widget-total-tokens></widget-total-tokens>
-      </div>
-      <div fxLayout="row">
-        <widget-applications></widget-applications>
-        <widget-top-applications></widget-top-applications>
-      </div>
-    </section>
-    <div class="dashboard-description" fxFlex>
-      <h3>Activities</h3>
-      <div class="dashboard-description-content">
-        <p>
-          Retrieve all activities and usage statistics of the platform.
-        </p>
-      </div>
-      <div class="dashboard-description-actions">
-        <mat-list>
-          <h3 matSubheader>Shortcuts</h3>
-          <a mat-button [routerLink]="['/dashboard', 'applications', 'new']">
-            <mat-list-item>
-              <mat-icon mat-list-icon>playlist_add</mat-icon>
-              <h4 matLine>Create application</h4>
-            </mat-list-item>
-          </a>
-          <a mat-button [routerLink]="['/settings', 'domains', 'new']">
-            <mat-list-item>
-              <mat-icon mat-list-icon>add</mat-icon>
-              <h4 matLine>Create domain</h4>
-            </mat-list-item>
-          </a>
-        </mat-list>
-      </div>
-    </div>
-  </div>
-</div>
diff --git a/gravitee-am-ui/src/app/dashboard/dashboard.component.scss b/gravitee-am-ui/src/app/dashboard/dashboard.component.scss
deleted file mode 100644
index 0103fece58..0000000000
--- a/gravitee-am-ui/src/app/dashboard/dashboard.component.scss
+++ /dev/null
@@ -1,35 +0,0 @@
-.dashboard-container {
-  padding: 20px 70px 20px 50px;
-  h1 {
-    font-weight: 400;
-  }
-  .main-widgets {
-    border-top: 1px solid rgb(226, 229, 231);
-    border-bottom: 1px solid rgb(226, 229, 231);
-    padding: 30px;
-  }
-  .dashboard-description {
-    border-left: 1px solid rgb(226, 229, 231);
-    margin-left: 20px;
-    padding-left: 20px;
-    h3 {
-      font-weight: 400;
-    }
-    .dashboard-description-content {
-      border-top: 1px solid rgb(226, 229, 231);
-      p {
-        margin-top: 20px;
-        font-size: 14px;
-      }
-      small {
-        font-size: 12px;
-        color: rgba(0, 0, 0, 0.38);
-      }
-    }
-    .dashboard-description-actions {
-      a {
-        text-decoration: none;
-      }
-    }
-  }
-}
diff --git a/gravitee-am-ui/src/app/domain/creation/domain-creation.component.html b/gravitee-am-ui/src/app/domain/creation/domain-creation.component.html
new file mode 100644
index 0000000000..d9d87be678
--- /dev/null
+++ b/gravitee-am-ui/src/app/domain/creation/domain-creation.component.html
@@ -0,0 +1,47 @@
+<!--
+
+    Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+            http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<div class="new-domain-container">
+  <a *ngIf="displayNavLink" [routerLink]="['']" class="back-link"><small><< <mat-icon>home</mat-icon></small></a>
+  <h1>New domain</h1>
+  <div class="new-domain-content">
+    <form (ngSubmit)="create()" #domainForm="ngForm" fxLayout="column" fxFlex="70">
+      <div class="gs-form-section">
+        <mat-form-field appearance="outline" floatLabel="always">
+          <input matInput type="text" placeholder="Name" name="name" [(ngModel)]="domain.name" required>
+          <mat-hint>A name for your security domain. The security domain's name is used to generate the security domain's path. The security domain's path will be used as a context path for the API operations.</mat-hint>
+        </mat-form-field>
+        <mat-form-field appearance="outline" floatLabel="always" style="margin-top: 20px;">
+          <textarea matInput placeholder="Description" name="description" [(ngModel)]="domain.description"></textarea>
+          <mat-hint>A description for your security domain.</mat-hint>
+        </mat-form-field>
+      </div>
+      <div fxLayout="row" fxLayoutGap="10px" fxLayoutAlign="center center" style="margin-top: 20px; border-top: 1px solid #e2e5e7; padding: 10px;">
+        <button mat-raised-button color="accent" [disabled]="!domainForm.valid || domainForm.pristine" type="submit">CREATE</button>
+        <a mat-stroked-button [routerLink]="['']">CANCEL</a>
+      </div>
+    </form>
+    <div class="new-domain-description" fxFlex>
+      <h3>Security domain ?</h3>
+      <div class="new-domain-description-content">
+        <p>
+          Security domain will let you create clients, register users and manage identity providers to provide centralize authentication, authorization and user management.
+        </p>
+      </div>
+    </div>
+  </div>
+</div>
diff --git a/gravitee-am-ui/src/app/settings/domains/creation/domain-creation.component.scss b/gravitee-am-ui/src/app/domain/creation/domain-creation.component.scss
similarity index 68%
rename from gravitee-am-ui/src/app/settings/domains/creation/domain-creation.component.scss
rename to gravitee-am-ui/src/app/domain/creation/domain-creation.component.scss
index 5f3915f989..a7f016f61a 100644
--- a/gravitee-am-ui/src/app/settings/domains/creation/domain-creation.component.scss
+++ b/gravitee-am-ui/src/app/domain/creation/domain-creation.component.scss
@@ -1,19 +1,20 @@
 .new-domain-container {
   padding: 50px 70px 20px 50px;
+  a.back-link {
+    color: #006CAB;
+    text-decoration: none;
+    &:hover {
+      color: #2196f3;
+    }
+    mat-icon {
+      vertical-align: middle;
+      margin-top: -2px;
+    }
+  }
   h1 {
     font-weight: 400;
   }
   .new-domain-content {
-    padding: 50px 0 20px;
-    form {
-      padding: 20px;
-      background: #fafafa;
-      border-radius: 2px;
-      border: 1px solid rgb(226, 229, 231);
-      .domain-general-form-actions {
-        margin-top: 20px;
-      }
-    }
     .new-domain-description {
       margin-left: 20px;
       h3 {
diff --git a/gravitee-am-ui/src/app/settings/domains/creation/domain-creation.component.spec.ts b/gravitee-am-ui/src/app/domain/creation/domain-creation.component.spec.ts
similarity index 100%
rename from gravitee-am-ui/src/app/settings/domains/creation/domain-creation.component.spec.ts
rename to gravitee-am-ui/src/app/domain/creation/domain-creation.component.spec.ts
diff --git a/gravitee-am-ui/src/app/settings/domains/creation/domain-creation.component.ts b/gravitee-am-ui/src/app/domain/creation/domain-creation.component.ts
similarity index 66%
rename from gravitee-am-ui/src/app/settings/domains/creation/domain-creation.component.ts
rename to gravitee-am-ui/src/app/domain/creation/domain-creation.component.ts
index b560205846..627fa64525 100644
--- a/gravitee-am-ui/src/app/settings/domains/creation/domain-creation.component.ts
+++ b/gravitee-am-ui/src/app/domain/creation/domain-creation.component.ts
@@ -14,10 +14,10 @@
  * limitations under the License.
  */
 import { Component, OnInit } from '@angular/core';
-import { DomainService } from "../../../services/domain.service";
-import { SnackbarService } from "../../../services/snackbar.service";
+import { DomainService } from "../../services/domain.service";
+import { SnackbarService } from "../../services/snackbar.service";
 import { Router } from "@angular/router";
-import { BreadcrumbService } from "../../../../libraries/ng2-breadcrumb/components/breadcrumbService";
+import { BreadcrumbService } from "../../../libraries/ng2-breadcrumb/components/breadcrumbService";
 
 @Component({
   selector: 'app-creation',
@@ -26,16 +26,21 @@ import { BreadcrumbService } from "../../../../libraries/ng2-breadcrumb/componen
 })
 export class DomainCreationComponent implements OnInit {
   domain: any = {};
+  displayNavLink: boolean;
 
-  constructor(private domainService: DomainService, private snackbarService: SnackbarService, private router: Router, private breadcrumbService: BreadcrumbService) { }
+  constructor(private domainService: DomainService,
+              private snackbarService: SnackbarService,
+              private router: Router,
+              private breadcrumbService: BreadcrumbService) { }
 
   ngOnInit() {
     this.breadcrumbService.addFriendlyNameForRoute('/domains', 'Domains');
+    this.displayNavLink = !this.router.url.startsWith('/settings');
   }
 
   create() {
     this.domainService.create(this.domain).subscribe(data => {
-      this.snackbarService.open("Domain " + data.name + " created");
+      this.snackbarService.open('Domain ' + data.name + ' created');
       this.router.navigate(['/domains', data.id])
     });
   }
diff --git a/gravitee-am-ui/src/app/domain/dashboard/dashboard.component.html b/gravitee-am-ui/src/app/domain/dashboard/dashboard.component.html
index 065c279e8b..1515ed5803 100644
--- a/gravitee-am-ui/src/app/domain/dashboard/dashboard.component.html
+++ b/gravitee-am-ui/src/app/domain/dashboard/dashboard.component.html
@@ -16,7 +16,7 @@
 
 -->
 <div class="domain-overview-container">
-  <h1>{{domain.name}}</h1>
+  <h1>Dashboard</h1>
   <div class="domain-overview-content" fxLayout="row">
     <section fxFlex="70">
       <div fxLayout="row" fxLayoutGap="30px" class="main-widgets">
diff --git a/gravitee-am-ui/src/app/domain/domain.component.ts b/gravitee-am-ui/src/app/domain/domain.component.ts
index 5c1280e1d1..a4d32cc0d8 100644
--- a/gravitee-am-ui/src/app/domain/domain.component.ts
+++ b/gravitee-am-ui/src/app/domain/domain.component.ts
@@ -13,12 +13,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import { Component, OnInit } from '@angular/core';
-import { ActivatedRoute } from "@angular/router";
-import { SidenavService } from "../components/sidenav/sidenav.service";
-import { BreadcrumbService } from "../../libraries/ng2-breadcrumb/components/breadcrumbService";
-import { DomainService } from "../services/domain.service";
-import { SnackbarService } from "../services/snackbar.service";
+import {Component, OnInit} from '@angular/core';
+import {ActivatedRoute} from "@angular/router";
+import {SidenavService} from "../components/sidenav/sidenav.service";
+import {BreadcrumbService} from "../../libraries/ng2-breadcrumb/components/breadcrumbService";
+import {DomainService} from "../services/domain.service";
+import {NavbarService} from "../components/navbar/navbar.service";
+import {SnackbarService} from "../services/snackbar.service";
 
 @Component({
   selector: 'app-domain',
@@ -30,8 +31,9 @@ export class DomainComponent implements OnInit {
 
   constructor(private route: ActivatedRoute,
               private sidenavService: SidenavService,
-              private snackbarService: SnackbarService,
+              private navbarService: NavbarService,
               private breadcrumbService: BreadcrumbService,
+              private snackbarService: SnackbarService,
               private domainService: DomainService) {
   }
 
@@ -39,25 +41,25 @@ export class DomainComponent implements OnInit {
     this.domain = this.route.snapshot.data['domain'];
     this.domainService.domainUpdated$.subscribe(domain => this.domain = domain);
     setTimeout(() => {
-      this.sidenavService.notify(this.domain);
+      this.navbarService.notify(this.domain);
     });
     this.initBreadcrumb();
   }
 
   initBreadcrumb() {
-    this.breadcrumbService.addFriendlyNameForRoute('/domains/'+this.domain.id, this.domain.name);
-    this.breadcrumbService.addFriendlyNameForRouteRegex('/domains/'+this.domain.id+'/settings/(providers|certificates|roles)/.*', ' ');
-    this.breadcrumbService.addFriendlyNameForRouteRegex('/domains/'+this.domain.id+'/settings/(providers|certificates|roles)/new$', 'new');
+    this.breadcrumbService.addFriendlyNameForRoute('/domains/' + this.domain.id, this.domain.name);
+    this.breadcrumbService.addFriendlyNameForRouteRegex('/domains/' + this.domain.id + '/settings/(providers|certificates|roles)/.*', ' ');
+    this.breadcrumbService.addFriendlyNameForRouteRegex('/domains/' + this.domain.id + '/settings/(providers|certificates|roles)/new$', 'new');
     this.breadcrumbService.hideRoute('/domains');
-    this.breadcrumbService.hideRouteRegex('/domains/'+this.domain.id+'/settings/providers/.*/settings$');
-    this.breadcrumbService.hideRouteRegex('/domains/'+this.domain.id+'/clients/.*/settings$');
+    this.breadcrumbService.hideRouteRegex('/domains/' + this.domain.id + '/settings/providers/.*/settings$');
+    this.breadcrumbService.hideRouteRegex('/domains/' + this.domain.id + '/clients/.*/settings$');
   }
 
   enable() {
     this.domain.enabled = true;
     this.domainService.enable(this.domain.id, this.domain).subscribe(response => {
       this.domain = response;
-      this.snackbarService.open("Domain " + this.domain.name + " enabled");
+      this.snackbarService.open('Domain ' + this.domain.name + ' enabled');
     });
   }
 
diff --git a/gravitee-am-ui/src/app/domain/settings/general/general.component.ts b/gravitee-am-ui/src/app/domain/settings/general/general.component.ts
index b6d099529e..ee50e4e064 100644
--- a/gravitee-am-ui/src/app/domain/settings/general/general.component.ts
+++ b/gravitee-am-ui/src/app/domain/settings/general/general.component.ts
@@ -20,9 +20,9 @@ import {DomainService} from "../../../services/domain.service";
 import {DialogService} from "../../../services/dialog.service";
 import {SnackbarService} from "../../../services/snackbar.service";
 import {BreadcrumbService} from "../../../../libraries/ng2-breadcrumb/components/breadcrumbService";
-import {SidenavService} from "../../../components/sidenav/sidenav.service";
 import {AuthService} from "../../../services/auth.service";
 import * as _ from "lodash";
+import {NavbarService} from "../../../components/navbar/navbar.service";
 
 export interface Tag {
   id: string;
@@ -48,8 +48,8 @@ export class DomainSettingsGeneralComponent implements OnInit {
               private router: Router,
               private route: ActivatedRoute,
               private breadcrumbService: BreadcrumbService,
-              private sidenavService: SidenavService,
-              private authService: AuthService) {
+              private authService: AuthService,
+              private navbarService: NavbarService) {
   }
 
   ngOnInit() {
@@ -100,7 +100,6 @@ export class DomainSettingsGeneralComponent implements OnInit {
     this.domainService.patchGeneralSettings(this.domain.id, this.domain).subscribe(response => {
       this.domain = response;
       this.domainService.notify(this.domain);
-      this.sidenavService.notify(this.domain);
       this.breadcrumbService.addFriendlyNameForRoute('/domains/' + this.domain.id, this.domain.name);
       this.formChanged = false;
       this.snackbarService.open('Domain ' + this.domain.name + ' updated');
@@ -115,6 +114,7 @@ export class DomainSettingsGeneralComponent implements OnInit {
         if (res) {
           this.domainService.delete(this.domain.id).subscribe(response => {
             this.snackbarService.open('Domain ' + this.domain.name + ' deleted');
+            this.navbarService.notify({});
             this.router.navigate(['']);
           })
         }
diff --git a/gravitee-am-ui/src/app/domain/settings/openid/client-registration/settings/settings.component.ts b/gravitee-am-ui/src/app/domain/settings/openid/client-registration/settings/settings.component.ts
index 65a4742cbd..f65eb21509 100644
--- a/gravitee-am-ui/src/app/domain/settings/openid/client-registration/settings/settings.component.ts
+++ b/gravitee-am-ui/src/app/domain/settings/openid/client-registration/settings/settings.component.ts
@@ -18,7 +18,6 @@ import {ActivatedRoute, Router} from "@angular/router";
 import {DomainService} from "../../../../../services/domain.service";
 import {DialogService} from "../../../../../services/dialog.service";
 import {SnackbarService} from "../../../../../services/snackbar.service";
-import {SidenavService} from "../../../../../components/sidenav/sidenav.service";
 import {AuthService} from "../../../../../services/auth.service";
 
 @Component({
@@ -39,7 +38,6 @@ export class ClientRegistrationSettingsComponent implements OnInit {
               private snackbarService: SnackbarService,
               private router: Router,
               private route: ActivatedRoute,
-              private sidenavService: SidenavService,
               private authService: AuthService) {}
 
   ngOnInit() {
@@ -103,7 +101,6 @@ export class ClientRegistrationSettingsComponent implements OnInit {
     this.domainService.patchOpenidDCRSettings(this.domain.id, this.domain).subscribe(response => {
       this.domain = response;
       this.domainService.notify(this.domain);
-      this.sidenavService.notify(this.domain);
       this.snackbarService.open('Domain ' + this.domain.name + ' updated');
       this.formChanged = false;
     });
diff --git a/gravitee-am-ui/src/app/guards/auth-guard.service.ts b/gravitee-am-ui/src/app/guards/auth-guard.service.ts
index 374a365a39..bdd6e80f31 100644
--- a/gravitee-am-ui/src/app/guards/auth-guard.service.ts
+++ b/gravitee-am-ui/src/app/guards/auth-guard.service.ts
@@ -34,9 +34,10 @@ export class AuthGuard implements CanActivate {
       return true;
     }
     // check if we need to load some data
+    const requiredPerms = route.data.perms.only;
     const userResult: Observable<any> = !this.authService.user() ? this.authService.userInfo() : of(this.authService.user());
     let resourceResult: Observable<any> = of();
-    if (!state.url.startsWith('/settings')) {
+    if (!requiredPerms[0].startsWith('management')) {
       // check if the authenticated user can navigate to the next route (domain settings or application settings)
       const domainId = route.parent.paramMap.get('domainId') ? route.parent.paramMap.get('domainId') : route.parent.parent.paramMap.get('domainId') ? route.parent.parent.paramMap.get('domainId') : route.parent.parent.parent.paramMap.get('domainId');
       const appId = route.parent.paramMap.get('appId') ? route.parent.paramMap.get('appId') : route.parent.parent.paramMap.get('appId');
@@ -46,7 +47,6 @@ export class AuthGuard implements CanActivate {
       }
     }
     // check permissions
-    const requiredPerms = route.data.perms.only;
     return concat(userResult, resourceResult).pipe(
       map(() =>  this.isAuthorized(requiredPerms)),
       catchError((err) => {
diff --git a/gravitee-am-ui/src/app/home/home.component.html b/gravitee-am-ui/src/app/home/home.component.html
new file mode 100644
index 0000000000..194c13f66d
--- /dev/null
+++ b/gravitee-am-ui/src/app/home/home.component.html
@@ -0,0 +1,29 @@
+<!--
+
+    Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+            http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<div *ngIf="!isLoading" class="home-container">
+  <div class="home-content">
+    <div class="home-content-header">Integrate authentication and authorization to secure access for your applications and APIs.</div>
+    <div class="home-content-description">
+      Identity and Access Management Platform will let you create applications, register users and manage identity providers to provide centralize authentication, authorization and user management features.
+    </div>
+    <div class="home-content-actions">
+      <a mat-raised-button color="accent" [routerLink]="['/h', 'domains', 'new']" [disabled]="readonly">Create a Security Domain</a>
+      <span *ngIf="readonly">There is not enough permission, ask for support or wait for invitation to collaborate.</span>
+    </div>
+  </div>
+</div>
diff --git a/gravitee-am-ui/src/app/home/home.component.scss b/gravitee-am-ui/src/app/home/home.component.scss
new file mode 100644
index 0000000000..e1c610e3dd
--- /dev/null
+++ b/gravitee-am-ui/src/app/home/home.component.scss
@@ -0,0 +1,35 @@
+.home-container {
+  margin: 100px 0px 0px 150px;
+  .home-content {
+    .home-content-header {
+      width: 600px;
+      color: #000;
+      margin-bottom: 32px;
+      text-align: left;
+      font-weight: 500;
+      font-size: 40px;
+      line-height: 48px;
+      white-space: pre-wrap;
+    }
+    .home-content-description {
+      width: 676px;
+      font-size: 16px;
+      line-height: 26px;
+      color: #000;
+      margin-bottom: 32px;
+      text-align: left;
+      font-weight: 400;
+    }
+    .home-content-actions {
+      button {
+        min-width: 100px;
+      }
+      span {
+        color: #637282;
+        margin-left: 8px;
+        font-size: small;
+        font-style: italic;
+      }
+    }
+  }
+}
diff --git a/gravitee-am-ui/src/app/home/home.component.spec.ts b/gravitee-am-ui/src/app/home/home.component.spec.ts
new file mode 100644
index 0000000000..91672b8152
--- /dev/null
+++ b/gravitee-am-ui/src/app/home/home.component.spec.ts
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { TestBed, async } from '@angular/core/testing';
+
+import { HomeComponent } from './app.component';
+
+describe('HomeComponent', () => {
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [
+        HomeComponent
+      ],
+    }).compileComponents();
+  }));
+
+  it('should create the app', async(() => {
+    const fixture = TestBed.createComponent(HomeComponent);
+    const app = fixture.debugElement.componentInstance;
+    expect(app).toBeTruthy();
+  }));
+
+  it(`should have as title 'app works!'`, async(() => {
+    const fixture = TestBed.createComponent(HomeComponent);
+    const app = fixture.debugElement.componentInstance;
+    expect(app.title).toEqual('app works!');
+  }));
+
+  it('should render title in a h1 tag', async(() => {
+    const fixture = TestBed.createComponent(HomeComponent);
+    fixture.detectChanges();
+    const compiled = fixture.debugElement.nativeElement;
+    expect(compiled.querySelector('h1').textContent).toContain('app works!');
+  }));
+});
diff --git a/gravitee-am-ui/src/app/home/home.component.ts b/gravitee-am-ui/src/app/home/home.component.ts
new file mode 100644
index 0000000000..caee0f8d9a
--- /dev/null
+++ b/gravitee-am-ui/src/app/home/home.component.ts
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {Component, OnInit} from '@angular/core';
+import {Router} from '@angular/router';
+import {DomainService} from '../services/domain.service';
+import {AuthService} from '../services/auth.service';
+import {NavbarService} from "../components/navbar/navbar.service";
+
+@Component({
+  selector: 'app-home',
+  templateUrl: './home.component.html',
+  styleUrls: ['./home.component.scss']
+})
+export class HomeComponent implements OnInit {
+  readonly: boolean;
+  isLoading = true;
+
+  constructor(private router: Router,
+              private domainService: DomainService,
+              private authService: AuthService,
+              private navbarService: NavbarService) {}
+
+  ngOnInit() {
+    // redirect user to the its domain, if any
+    this.domainService.list().subscribe(response => {
+      if (response && response.length > 0) {
+        this.router.navigate(['/domains', response[0].id]);
+      } else {
+        this.isLoading = false;
+        this.readonly = !this.authService.isAdmin() && !this.authService.hasPermissions(['management_domain_create']);
+        this.navbarService.notify({});
+      }
+    });
+  }
+}
diff --git a/gravitee-am-ui/src/app/settings/domains/creation/domain-creation.component.html b/gravitee-am-ui/src/app/settings/domains/creation/domain-creation.component.html
deleted file mode 100644
index eb282fa26b..0000000000
--- a/gravitee-am-ui/src/app/settings/domains/creation/domain-creation.component.html
+++ /dev/null
@@ -1,49 +0,0 @@
-<!--
-
-    Copyright (C) 2015 The Gravitee team (http://gravitee.io)
-
-    Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-            http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
-
--->
-<gs-breadcrumb></gs-breadcrumb>
-
-<div class="new-domain-container">
-  <h1>New domain</h1>
-  <div class="new-domain-content">
-    <form (ngSubmit)="create()" #domainForm="ngForm" fxLayout="column" fxFlex="70">
-
-      <mat-form-field>
-        <input matInput type="text" placeholder="Name" name="name" [(ngModel)]="domain.name" required>
-        <mat-hint>A name for your security domain. The security domain's name is used to generate the security domain's path. The security domain's path will be used as a context path for the API operations.</mat-hint>
-      </mat-form-field>
-
-      <mat-form-field style="margin-top: 20px;">
-        <textarea matInput placeholder="Description" name="description" [(ngModel)]="domain.description"></textarea>
-        <mat-hint>A description for your security domain.</mat-hint>
-      </mat-form-field>
-
-      <div fxLayout="row" fxLayoutAlign="end" style="margin-top: 50px;">
-        <a mat-button [routerLink]="['']" style="margin-right: 20px;">CANCEL</a>
-        <button mat-raised-button [disabled]="!domainForm.valid || domainForm.pristine" type="submit">CREATE</button>
-      </div>
-    </form>
-    <div class="new-domain-description" fxFlex>
-      <h3>Security domain ?</h3>
-      <div class="new-domain-description-content">
-        <p>
-          Security domain will let you create clients, register users and manage identity providers to provide centralize authentication, authorization and user management.
-        </p>
-      </div>
-    </div>
-  </div>
-</div>
diff --git a/gravitee-am-ui/src/app/settings/management/roles/role/role.component.html b/gravitee-am-ui/src/app/settings/management/roles/role/role.component.html
index 9f13b3bc37..dc0c02046f 100644
--- a/gravitee-am-ui/src/app/settings/management/roles/role/role.component.html
+++ b/gravitee-am-ui/src/app/settings/management/roles/role/role.component.html
@@ -79,7 +79,7 @@
           </div>
         </div>
 
-        <div fxLayout="row" class="role-form-actions" *hasPermission="['management_role_update']">
+        <div fxLayout="row" *ngIf="!readonly">
           <button mat-raised-button [disabled]="(!roleForm.valid || roleForm.pristine) && !formChanged" type="submit">SAVE</button>
         </div>
       </form>
diff --git a/gravitee-am-ui/src/app/settings/management/roles/role/role.component.ts b/gravitee-am-ui/src/app/settings/management/roles/role/role.component.ts
index 9290b261d1..abcbccf77a 100644
--- a/gravitee-am-ui/src/app/settings/management/roles/role/role.component.ts
+++ b/gravitee-am-ui/src/app/settings/management/roles/role/role.component.ts
@@ -50,7 +50,7 @@ export class ManagementRoleComponent implements OnInit {
   ngOnInit() {
     this.role = this.route.snapshot.data['role'];
     this.role.permissions = this.role.permissions || [];
-    this.readonly = !this.authService.isAdmin() && !this.authService.hasPermissions(['management_role_update']);
+    this.readonly = this.role.system || (!this.authService.isAdmin() && !this.authService.hasPermissions(['management_role_update']));
     this.deleteMode = this.authService.isAdmin() || this.authService.hasPermissions(['management_role_delete']);
     this.initPermissions();
   }


Test patch:
diff --git a/gravitee-am-service/src/test/java/io/gravitee/am/service/RoleServiceTest.java b/gravitee-am-service/src/test/java/io/gravitee/am/service/RoleServiceTest.java
index c538c95343..b0034435ef 100644
--- a/gravitee-am-service/src/test/java/io/gravitee/am/service/RoleServiceTest.java
+++ b/gravitee-am-service/src/test/java/io/gravitee/am/service/RoleServiceTest.java
@@ -19,9 +19,7 @@ import io.gravitee.am.model.Role;
 import io.gravitee.am.model.common.event.Event;
 import io.gravitee.am.repository.exceptions.TechnicalException;
 import io.gravitee.am.repository.management.api.RoleRepository;
-import io.gravitee.am.service.exception.RoleAlreadyExistsException;
-import io.gravitee.am.service.exception.RoleNotFoundException;
-import io.gravitee.am.service.exception.TechnicalManagementException;
+import io.gravitee.am.service.exception.*;
 import io.gravitee.am.service.impl.RoleServiceImpl;
 import io.gravitee.am.service.model.NewRole;
 import io.gravitee.am.service.model.UpdateRole;
@@ -263,6 +261,24 @@ public class RoleServiceTest {
         verify(roleRepository, never()).create(any(Role.class));
     }
 
+    @Test
+    public void shouldNotUpdate_systemRole() {
+        UpdateRole updateRole = Mockito.mock(UpdateRole.class);
+        Role role = Mockito.mock(Role.class);
+        when(role.isSystem()).thenReturn(true);
+        when(roleRepository.findById("my-role")).thenReturn(Maybe.just(role));
+
+        TestObserver testObserver = roleService.update(DOMAIN,"my-role", updateRole).test();
+        testObserver.awaitTerminalEvent();
+
+        testObserver.assertNotComplete();
+        testObserver.assertError(SystemRoleUpdateException.class);
+
+        verify(roleRepository, times(1)).findById("my-role");
+        verify(roleRepository, never()).findByDomain(DOMAIN);
+        verify(roleRepository, never()).update(any(Role.class));
+    }
+
     @Test
     public void shouldDelete_notExistingRole() {
         when(roleRepository.findById("my-role")).thenReturn(Maybe.empty());
@@ -286,6 +302,21 @@ public class RoleServiceTest {
         testObserver.assertNotComplete();
     }
 
+    @Test
+    public void shouldNotDelete_systemRole() {
+        Role role = Mockito.mock(Role.class);
+        when(role.isSystem()).thenReturn(true);
+        when(roleRepository.findById("my-role")).thenReturn(Maybe.just(role));
+
+        TestObserver testObserver = roleService.delete( "my-role").test();
+        testObserver.awaitTerminalEvent();
+
+        testObserver.assertNotComplete();
+        testObserver.assertError(SystemRoleDeleteException.class);
+
+        verify(roleRepository, never()).delete("my-role");
+    }
+
     @Test
     public void shouldDelete() {
         when(roleRepository.findById("my-role")).thenReturn(Maybe.just(new Role()));