You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@datalab.apache.org by hs...@apache.org on 2022/06/02 09:58:40 UTC

[incubator-datalab] 01/03: initial commit

This is an automated email from the ASF dual-hosted git repository.

hshpak pushed a commit to branch feat/DATALAB-2811/view-list-of-all-images
in repository https://gitbox.apache.org/repos/asf/incubator-datalab.git

commit 7017c1f7ceb4c28faf38f55c6361861afb3ae87b
Author: Hennadii_Shpak <bo...@gmail.com>
AuthorDate: Tue May 24 12:11:44 2022 +0300

    initial commit
---
 .../com/epam/datalab/model/exploratory/Image.java  |   3 +
 .../backendapi/dao/ImageExploratoryDAO.java        |   2 +
 .../backendapi/dao/ImageExploratoryDAOImpl.java    |  12 ++
 .../resources/ImageExploratoryResource.java        |  20 ++
 .../dto/{ImageInfoRecord.java => ImageFilter.java} |  38 +++-
 .../backendapi/resources/dto/ImageInfoRecord.java  |   7 +
 .../resources/dto/ProjectImagesInfo.java}          |  32 +--
 .../service/ImageExploratoryService.java           |   5 +
 .../service/impl/ImageExploratoryServiceImpl.java  |  35 +++
 .../resources/webapp/src/app/app.routing.module.ts |  32 +--
 .../resources/webapp/src/app/core/core.module.ts   |   7 +-
 .../capitalize-first-letter.pipe.ts}               |  26 ++-
 .../{ => capitalize-first-letter-pipe}/index.ts    |  17 +-
 .../resources/webapp/src/app/core/pipes/index.ts   |   1 +
 .../src/app/core/services/appRouting.service.ts    |   2 +-
 .../services/applicationServiceFacade.service.ts   |  21 +-
 .../webapp/src/app/core/services/index.ts          |   1 +
 .../app/core/services/user-images-page.service.ts} |  32 +--
 .../src/app/core/services/userResource.service.ts  |  15 +-
 .../reporting-grid/reporting-grid.component.ts     |  15 +-
 .../bucket-browser/bucket-browser.component.ts     |  17 +-
 .../src/app/resources/images/images.component.html | 240 +++++++++++++++++++++
 .../src/app/resources/images/images.component.scss | 102 +++++++++
 .../src/app/resources/images/images.component.ts   | 135 ++++++++++++
 .../src/app/resources/images/images.config.ts      |  10 +
 .../src/app/resources/images/images.model.ts       |  20 ++
 .../webapp/src/app/resources/images/index.ts       |   2 +
 .../resources-grid/resources-grid.component.ts     |   9 +-
 .../resources-grid/resources-grid.model.ts         |   7 -
 .../src/app/resources/resources.component.html     |  28 +--
 .../src/app/resources/resources.component.ts       |   2 +-
 .../webapp/src/app/resources/resources.module.ts   |  10 +-
 .../src/app/shared/bubble/bubble.component.ts      |  29 +--
 .../src/app/shared/navbar/navbar.component.html    | 113 +++++-----
 .../src/app/shared/navbar/navbar.component.ts      |   5 +-
 .../webapp/src/app/shared/navbar/navbar.config.ts  |  25 +--
 .../src/main/resources/webapp/src/styles.scss      |   1 +
 .../resources/ImageExploratoryResourceTest.java    |  16 +-
 .../service/impl/BillingServiceImplTest.java       |  15 +-
 .../impl/ImageExploratoryServiceImplTest.java      |   5 +-
 40 files changed, 900 insertions(+), 214 deletions(-)

diff --git a/services/datalab-model/src/main/java/com/epam/datalab/model/exploratory/Image.java b/services/datalab-model/src/main/java/com/epam/datalab/model/exploratory/Image.java
index c8d3b2c35..322d5511a 100644
--- a/services/datalab-model/src/main/java/com/epam/datalab/model/exploratory/Image.java
+++ b/services/datalab-model/src/main/java/com/epam/datalab/model/exploratory/Image.java
@@ -24,6 +24,7 @@ import com.epam.datalab.model.library.Library;
 import lombok.Builder;
 import lombok.Data;
 
+import java.time.Instant;
 import java.util.List;
 import java.util.Map;
 
@@ -40,6 +41,8 @@ public class Image {
     private final String fullName;
     private final String externalName;
     private final String application;
+    private final String instanceName;
+    private final String cloudProvider;
     private final String dockerImage;
     private final List<Library> libraries;
     private final Map<String, List<Library>> computationalLibraries;
diff --git a/services/self-service/src/main/java/com/epam/datalab/backendapi/dao/ImageExploratoryDAO.java b/services/self-service/src/main/java/com/epam/datalab/backendapi/dao/ImageExploratoryDAO.java
index d8e89874a..4d97437e6 100644
--- a/services/self-service/src/main/java/com/epam/datalab/backendapi/dao/ImageExploratoryDAO.java
+++ b/services/self-service/src/main/java/com/epam/datalab/backendapi/dao/ImageExploratoryDAO.java
@@ -38,6 +38,8 @@ public interface ImageExploratoryDAO {
 
     List<ImageInfoRecord> getImages(String user, String dockerImage, String project, String endpoint, ImageStatus... statuses);
 
+    List<ImageInfoRecord> getImagesOfUser(String user, String project);
+
     List<ImageInfoRecord> getImagesForProject(String project);
 
     Optional<ImageInfoRecord> getImage(String user, String name, String project, String endpoint);
diff --git a/services/self-service/src/main/java/com/epam/datalab/backendapi/dao/ImageExploratoryDAOImpl.java b/services/self-service/src/main/java/com/epam/datalab/backendapi/dao/ImageExploratoryDAOImpl.java
index 234a15e52..95f4e0a1a 100644
--- a/services/self-service/src/main/java/com/epam/datalab/backendapi/dao/ImageExploratoryDAOImpl.java
+++ b/services/self-service/src/main/java/com/epam/datalab/backendapi/dao/ImageExploratoryDAOImpl.java
@@ -54,6 +54,7 @@ public class ImageExploratoryDAOImpl extends BaseDAO implements ImageExploratory
     private static final String DOCKER_IMAGE = "dockerImage";
     private static final String PROJECT = "project";
     private static final String ENDPOINT = "endpoint";
+    private static final String CREATION_DATE = "creationDate";
 
     @Override
     public boolean exist(String image, String project) {
@@ -79,6 +80,13 @@ public class ImageExploratoryDAOImpl extends BaseDAO implements ImageExploratory
                 ImageInfoRecord.class);
     }
 
+    @Override
+    public List<ImageInfoRecord> getImagesOfUser(String user, String project) {
+        return find(MongoCollections.IMAGES,
+                imageUserProjectCondition(user, project),
+                ImageInfoRecord.class);
+    }
+
     @Override
     public List<ImageInfoRecord> getImagesForProject(String project) {
         return find(MongoCollections.IMAGES,
@@ -146,6 +154,10 @@ public class ImageExploratoryDAOImpl extends BaseDAO implements ImageExploratory
         return and(eq(IMAGE_NAME, image), eq(PROJECT, project));
     }
 
+    private Bson imageUserProjectCondition(String user, String project) {
+        return and(eq(USER, user), eq(PROJECT, project));
+    }
+
     private Document getUpdatedFields(Image image) {
         return new Document(STATUS, image.getStatus().toString())
                 .append(IMAGE_FULL_NAME, image.getFullName())
diff --git a/services/self-service/src/main/java/com/epam/datalab/backendapi/resources/ImageExploratoryResource.java b/services/self-service/src/main/java/com/epam/datalab/backendapi/resources/ImageExploratoryResource.java
index 95dcdb469..8c5c76861 100644
--- a/services/self-service/src/main/java/com/epam/datalab/backendapi/resources/ImageExploratoryResource.java
+++ b/services/self-service/src/main/java/com/epam/datalab/backendapi/resources/ImageExploratoryResource.java
@@ -22,7 +22,9 @@ package com.epam.datalab.backendapi.resources;
 import com.epam.datalab.auth.UserInfo;
 import com.epam.datalab.backendapi.domain.RequestId;
 import com.epam.datalab.backendapi.resources.dto.ExploratoryImageCreateFormDTO;
+import com.epam.datalab.backendapi.resources.dto.ImageFilter;
 import com.epam.datalab.backendapi.resources.dto.ImageInfoRecord;
+import com.epam.datalab.backendapi.resources.dto.ProjectImagesInfo;
 import com.epam.datalab.backendapi.service.ImageExploratoryService;
 import com.google.inject.Inject;
 import io.dropwizard.auth.Auth;
@@ -98,6 +100,24 @@ public class ImageExploratoryResource {
         return Response.ok(images).build();
     }
 
+
+    @GET
+    @Path("user")
+    public Response getImagesForUser(@Auth UserInfo ui) {
+        log.debug("Getting images for user {}", ui.getName());
+        final List<ProjectImagesInfo> images = imageExploratoryService.getImagesOfUser(ui);
+        return Response.ok(images).build();
+    }
+
+    @POST
+    @Path("user")
+    public Response getImagesForUser(@Auth UserInfo ui, @Valid @NotNull ImageFilter imageFilter) {
+        log.debug("Getting images for user {} with filter {}", ui.getName(), imageFilter);
+        final List<ProjectImagesInfo> images = imageExploratoryService.getImagesOfUserWithFilter(ui ,imageFilter);
+        return Response.ok(images).build();
+    }
+
+
     @GET
     @Path("{name}")
     public Response getImage(@Auth UserInfo ui,
diff --git a/services/self-service/src/main/java/com/epam/datalab/backendapi/resources/dto/ImageInfoRecord.java b/services/self-service/src/main/java/com/epam/datalab/backendapi/resources/dto/ImageFilter.java
similarity index 58%
copy from services/self-service/src/main/java/com/epam/datalab/backendapi/resources/dto/ImageInfoRecord.java
copy to services/self-service/src/main/java/com/epam/datalab/backendapi/resources/dto/ImageFilter.java
index 18692c34c..09c50ef58 100644
--- a/services/self-service/src/main/java/com/epam/datalab/backendapi/resources/dto/ImageInfoRecord.java
+++ b/services/self-service/src/main/java/com/epam/datalab/backendapi/resources/dto/ImageFilter.java
@@ -19,19 +19,39 @@
 
 package com.epam.datalab.backendapi.resources.dto;
 
+import com.epam.datalab.cloud.CloudProvider;
 import com.epam.datalab.dto.exploratory.ImageStatus;
 import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
 import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.NonNull;
+
+import java.util.List;
 
 @Data
+@NoArgsConstructor
 @JsonIgnoreProperties(ignoreUnknown = true)
-public class ImageInfoRecord {
-    private final String name;
-    private final String description;
-    private final String project;
-    private final String endpoint;
-    private final String user;
-    private final String application;
-    private final String fullName;
-    private final ImageStatus status;
+public class ImageFilter {
+    @NonNull
+    private String imageName;
+    @NonNull
+    @JsonProperty("date_start")
+    private String dateStart;
+    @NonNull
+    @JsonProperty("date_end")
+    private String dateEnd;
+    @NonNull
+    private List<CloudProvider> cloudProviders;
+    @NonNull
+    private List<ImageStatus> statuses;
+//    @NonNull
+//    private List<> sharingStatuses;
+    @NonNull
+    private List<String> templateNames;
+    @NonNull
+    private List<String> instanceNames;
+    @NonNull
+    private List<String> projects;
+
 }
diff --git a/services/self-service/src/main/java/com/epam/datalab/backendapi/resources/dto/ImageInfoRecord.java b/services/self-service/src/main/java/com/epam/datalab/backendapi/resources/dto/ImageInfoRecord.java
index 18692c34c..00e5c94c6 100644
--- a/services/self-service/src/main/java/com/epam/datalab/backendapi/resources/dto/ImageInfoRecord.java
+++ b/services/self-service/src/main/java/com/epam/datalab/backendapi/resources/dto/ImageInfoRecord.java
@@ -19,19 +19,26 @@
 
 package com.epam.datalab.backendapi.resources.dto;
 
+import com.epam.datalab.cloud.CloudProvider;
 import com.epam.datalab.dto.exploratory.ImageStatus;
 import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 import lombok.Data;
 
+import java.time.LocalDateTime;
+
 @Data
 @JsonIgnoreProperties(ignoreUnknown = true)
 public class ImageInfoRecord {
     private final String name;
+    private final String creationDate;
     private final String description;
     private final String project;
     private final String endpoint;
     private final String user;
     private final String application;
+    private final String instanceName;
+    private final CloudProvider cloudProvider;
     private final String fullName;
     private final ImageStatus status;
+    private final String sharedStatus;
 }
diff --git a/services/self-service/src/main/resources/webapp/src/app/shared/navbar/navbar.config.ts b/services/self-service/src/main/java/com/epam/datalab/backendapi/resources/dto/ProjectImagesInfo.java
similarity index 64%
copy from services/self-service/src/main/resources/webapp/src/app/shared/navbar/navbar.config.ts
copy to services/self-service/src/main/java/com/epam/datalab/backendapi/resources/dto/ProjectImagesInfo.java
index 38df6d2c5..06da51f28 100644
--- a/services/self-service/src/main/resources/webapp/src/app/shared/navbar/navbar.config.ts
+++ b/services/self-service/src/main/java/com/epam/datalab/backendapi/resources/dto/ProjectImagesInfo.java
@@ -17,19 +17,23 @@
  * under the License.
  */
 
-export const sideBarNamesConfig: Record<string, string> = {
-    resourses: 'Resources',
-    reports: 'Reports',
-    audit: 'Audit',
-    billing: 'Billing',
-    administration: 'Administration',
-    users: 'Users',
-    projects: 'Projects',
-    resources: 'Resources',
-    configuration: 'Configuration'
-}
+package com.epam.datalab.backendapi.resources.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.util.List;
 
-export interface UserInfo {
-    email: string;
-    name: string;
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode
+@ToString
+public class ProjectImagesInfo {
+    @JsonProperty
+    private String project;
+    @JsonProperty
+    private List<ImageInfoRecord> images;
 }
diff --git a/services/self-service/src/main/java/com/epam/datalab/backendapi/service/ImageExploratoryService.java b/services/self-service/src/main/java/com/epam/datalab/backendapi/service/ImageExploratoryService.java
index fae72a33f..99973f1a6 100644
--- a/services/self-service/src/main/java/com/epam/datalab/backendapi/service/ImageExploratoryService.java
+++ b/services/self-service/src/main/java/com/epam/datalab/backendapi/service/ImageExploratoryService.java
@@ -20,7 +20,9 @@
 package com.epam.datalab.backendapi.service;
 
 import com.epam.datalab.auth.UserInfo;
+import com.epam.datalab.backendapi.resources.dto.ImageFilter;
 import com.epam.datalab.backendapi.resources.dto.ImageInfoRecord;
+import com.epam.datalab.backendapi.resources.dto.ProjectImagesInfo;
 import com.epam.datalab.model.exploratory.Image;
 
 import java.util.List;
@@ -36,4 +38,7 @@ public interface ImageExploratoryService {
     ImageInfoRecord getImage(String user, String name, String project, String endpoint);
 
     List<ImageInfoRecord> getImagesForProject(String project);
+
+    List<ProjectImagesInfo> getImagesOfUser(UserInfo user);
+    List<ProjectImagesInfo> getImagesOfUserWithFilter(UserInfo user, ImageFilter imageFilter);
 }
diff --git a/services/self-service/src/main/java/com/epam/datalab/backendapi/service/impl/ImageExploratoryServiceImpl.java b/services/self-service/src/main/java/com/epam/datalab/backendapi/service/impl/ImageExploratoryServiceImpl.java
index 8c6802167..c0cd6e54d 100644
--- a/services/self-service/src/main/java/com/epam/datalab/backendapi/service/impl/ImageExploratoryServiceImpl.java
+++ b/services/self-service/src/main/java/com/epam/datalab/backendapi/service/impl/ImageExploratoryServiceImpl.java
@@ -30,7 +30,9 @@ import com.epam.datalab.backendapi.dao.ExploratoryLibDAO;
 import com.epam.datalab.backendapi.dao.ImageExploratoryDAO;
 import com.epam.datalab.backendapi.domain.EndpointDTO;
 import com.epam.datalab.backendapi.domain.ProjectDTO;
+import com.epam.datalab.backendapi.resources.dto.ImageFilter;
 import com.epam.datalab.backendapi.resources.dto.ImageInfoRecord;
+import com.epam.datalab.backendapi.resources.dto.ProjectImagesInfo;
 import com.epam.datalab.backendapi.service.EndpointService;
 import com.epam.datalab.backendapi.service.ImageExploratoryService;
 import com.epam.datalab.backendapi.service.ProjectService;
@@ -53,6 +55,7 @@ import com.google.inject.Singleton;
 import com.google.inject.name.Named;
 import lombok.extern.slf4j.Slf4j;
 
+import java.time.LocalDateTime;
 import java.util.List;
 import java.util.Map;
 import java.util.function.Predicate;
@@ -104,6 +107,8 @@ public class ImageExploratoryServiceImpl implements ImageExploratoryService {
                 .computationalLibraries(fetchComputationalLibs(libraries))
                 .dockerImage(userInstance.getImageName())
                 .exploratoryId(userInstance.getId())
+                .instanceName(userInstance.getExploratoryName())
+                .cloudProvider(userInstance.getCloudProvider())
                 .project(userInstance.getProject())
                 .endpoint(userInstance.getEndpoint())
                 .build());
@@ -154,6 +159,36 @@ public class ImageExploratoryServiceImpl implements ImageExploratoryService {
         return imageExploratoryDao.getImagesForProject(project);
     }
 
+    @Override
+    public List<ProjectImagesInfo> getImagesOfUser(UserInfo user) {
+        log.debug("Loading list of images for user {}", user.getName());
+        return projectService.getUserProjects(user, Boolean.FALSE)
+                .stream()
+                .map( p-> {
+                    List<ImageInfoRecord> images =  imageExploratoryDao.getImagesOfUser(user.getName(), p.getName());
+                    return ProjectImagesInfo.builder()
+                            .project(p.getName())
+                            .images(images)
+                            .build();
+                })
+                .collect(Collectors.toList());
+    }
+
+    @Override
+    public List<ProjectImagesInfo> getImagesOfUserWithFilter(UserInfo user, ImageFilter imageFilter) {
+        log.debug("Loading list of images for user {}", user.getName());
+        return projectService.getUserProjects(user, Boolean.FALSE)
+                .stream()
+                .map( p-> {
+                    List<ImageInfoRecord> images =  imageExploratoryDao.getImagesOfUser(user.getName(), p.getName());
+                    return ProjectImagesInfo.builder()
+                            .project(p.getName())
+                            .images(images)
+                            .build();
+                })
+                .collect(Collectors.toList());
+    }
+
     private Map<String, List<Library>> fetchComputationalLibs(List<Library> libraries) {
         return libraries.stream()
                 .filter(resourceTypePredicate(ResourceType.COMPUTATIONAL))
diff --git a/services/self-service/src/main/resources/webapp/src/app/app.routing.module.ts b/services/self-service/src/main/resources/webapp/src/app/app.routing.module.ts
index d166b7d40..af905a56e 100644
--- a/services/self-service/src/main/resources/webapp/src/app/app.routing.module.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/app.routing.module.ts
@@ -37,12 +37,13 @@ import {ProjectAdminGuard} from './core/services/projectAdmin.guard';
 import {ReportingComponent} from './reports/reporting/reporting.component';
 import {OdahuComponent} from './administration/odahu/odahu.component';
 import {AuditComponent} from './reports/audit/audit.component';
+import {ImagesComponent} from './resources/images/images.component';
 
 const routes: Routes = [
   {
     path: 'login',
     component: LoginComponent
-  }, 
+  },
   {
     path: '',
     canActivate: [CheckParamsGuard],
@@ -50,19 +51,24 @@ const routes: Routes = [
     children: [
       {
         path: '',
-        redirectTo: 'resources_list',
+        redirectTo: 'instances',
         pathMatch: 'full'
-      }, 
+      },
       {
-        path: 'resources_list',
+        path: 'instances',
         component: ResourcesComponent,
         canActivate: [AuthorizationGuard]
-      }, 
+      },
+      {
+        path: 'images',
+        component: ImagesComponent,
+        canActivate: [AuthorizationGuard]
+      },
       {
         path: 'billing_report',
         component: ReportingComponent,
         canActivate: [AuthorizationGuard, CloudProviderGuard]
-      }, 
+      },
       {
         path: 'projects',
         component: ProjectComponent,
@@ -76,12 +82,12 @@ const routes: Routes = [
         path: 'roles',
         component: RolesComponent,
         canActivate: [AuthorizationGuard, AdminGuard],
-      }, 
+      },
       {
         path: 'environment_management',
         component: ManagementComponent,
         canActivate: [AuthorizationGuard, AdminGuard]
-      }, 
+      },
       {
         path: 'configuration',
         component: ConfigurationComponent,
@@ -91,12 +97,12 @@ const routes: Routes = [
         path: 'swagger',
         component: SwaggerComponent,
         canActivate: [AuthorizationGuard]
-      }, 
+      },
       {
         path: 'help/publickeyguide',
         component: PublicKeyGuideComponent,
         canActivate: [AuthorizationGuard]
-      }, 
+      },
       {
         path: 'help/accessnotebookguide',
         component: AccessNotebookGuideComponent,
@@ -108,16 +114,16 @@ const routes: Routes = [
         canActivate: [AuthorizationGuard, AuditGuard],
       },
     ]
-  }, 
+  },
   {
     path: 'terminal/:id/:endpoint',
     component: WebterminalComponent
-  }, 
+  },
   {
     path: '403',
     component: AccessDeniedComponent,
     canActivate: [AuthorizationGuard]
-  }, 
+  },
   {
   path: '**',
   component: NotFoundComponent
diff --git a/services/self-service/src/main/resources/webapp/src/app/core/core.module.ts b/services/self-service/src/main/resources/webapp/src/app/core/core.module.ts
index 20a45126c..33dc379fd 100644
--- a/services/self-service/src/main/resources/webapp/src/app/core/core.module.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/core/core.module.ts
@@ -48,9 +48,9 @@ import { NoCacheInterceptor } from './interceptors/nocache.interceptor';
 import { ErrorInterceptor } from './interceptors/error.interceptor';
 
 import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
-import {ConfigurationService} from './services/configutration.service';
-import {AuditGuard, OdahuDeploymentService} from './services';
-import {ProjectAdminGuard} from './services/projectAdmin.guard';
+import {  ConfigurationService } from './services/configutration.service';
+import {  AuditGuard, OdahuDeploymentService, UserImagesPageService } from './services';
+import {  ProjectAdminGuard } from './services/projectAdmin.guard';
 
 @NgModule({
   imports: [CommonModule],
@@ -90,6 +90,7 @@ export class CoreModule {
         UserAccessKeyService,
         ConfigurationService,
         OdahuDeploymentService,
+        UserImagesPageService,
 
         { provide: MatDialogRef, useValue: {} },
         { provide: MAT_DIALOG_DATA, useValue: [] },
diff --git a/services/self-service/src/main/resources/webapp/src/app/shared/navbar/navbar.config.ts b/services/self-service/src/main/resources/webapp/src/app/core/pipes/capitalize-first-letter-pipe/capitalize-first-letter.pipe.ts
similarity index 68%
copy from services/self-service/src/main/resources/webapp/src/app/shared/navbar/navbar.config.ts
copy to services/self-service/src/main/resources/webapp/src/app/core/pipes/capitalize-first-letter-pipe/capitalize-first-letter.pipe.ts
index 38df6d2c5..df57fee83 100644
--- a/services/self-service/src/main/resources/webapp/src/app/shared/navbar/navbar.config.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/core/pipes/capitalize-first-letter-pipe/capitalize-first-letter.pipe.ts
@@ -17,19 +17,17 @@
  * under the License.
  */
 
-export const sideBarNamesConfig: Record<string, string> = {
-    resourses: 'Resources',
-    reports: 'Reports',
-    audit: 'Audit',
-    billing: 'Billing',
-    administration: 'Administration',
-    users: 'Users',
-    projects: 'Projects',
-    resources: 'Resources',
-    configuration: 'Configuration'
-}
 
-export interface UserInfo {
-    email: string;
-    name: string;
+import { Pipe, PipeTransform } from '@angular/core';
+
+@Pipe({ name: 'capitalizeFirstLetter' })
+
+export class CapitalizeFirstLetterPipe implements PipeTransform {
+  transform(value: string): string {
+    if (!value) {
+      return '';
+    }
+    const firstLetter = value. substring(0, 1). toUpperCase();
+    return `${firstLetter}${value.substring(1).toLowerCase()}`;
+  }
 }
diff --git a/services/self-service/src/main/resources/webapp/src/app/core/pipes/index.ts b/services/self-service/src/main/resources/webapp/src/app/core/pipes/capitalize-first-letter-pipe/index.ts
similarity index 70%
copy from services/self-service/src/main/resources/webapp/src/app/core/pipes/index.ts
copy to services/self-service/src/main/resources/webapp/src/app/core/pipes/capitalize-first-letter-pipe/index.ts
index 399ce1d31..d333ad504 100644
--- a/services/self-service/src/main/resources/webapp/src/app/core/pipes/index.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/core/pipes/capitalize-first-letter-pipe/index.ts
@@ -17,9 +17,14 @@
  * under the License.
  */
 
-export * from './keys-pipe';
-export * from './underscoreless-pipe';
-export * from './lib-sort-pipe';
-export * from './replace-breaks-pipe';
-export * from './highlight.pipe';
-export * from './convert-action-pipe';
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { CapitalizeFirstLetterPipe } from './capitalize-first-letter.pipe';
+
+@NgModule({
+  imports: [CommonModule],
+  declarations: [CapitalizeFirstLetterPipe],
+  exports: [CapitalizeFirstLetterPipe]
+})
+
+export class CapitalizeFirstLetterPipeModule { }
diff --git a/services/self-service/src/main/resources/webapp/src/app/core/pipes/index.ts b/services/self-service/src/main/resources/webapp/src/app/core/pipes/index.ts
index 399ce1d31..bc2e2c7b6 100644
--- a/services/self-service/src/main/resources/webapp/src/app/core/pipes/index.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/core/pipes/index.ts
@@ -23,3 +23,4 @@ export * from './lib-sort-pipe';
 export * from './replace-breaks-pipe';
 export * from './highlight.pipe';
 export * from './convert-action-pipe';
+export * from './capitalize-first-letter-pipe';
diff --git a/services/self-service/src/main/resources/webapp/src/app/core/services/appRouting.service.ts b/services/self-service/src/main/resources/webapp/src/app/core/services/appRouting.service.ts
index 1a8075973..3dd07cbd7 100644
--- a/services/self-service/src/main/resources/webapp/src/app/core/services/appRouting.service.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/core/services/appRouting.service.ts
@@ -34,7 +34,7 @@ export class AppRoutingService {
   }
 
   redirectToHomePage(): void {
-    this.router.navigate(['/resources_list']);
+    this.router.navigate(['/instances']);
   }
 
   redirectToHealthStatusPage(): void {
diff --git a/services/self-service/src/main/resources/webapp/src/app/core/services/applicationServiceFacade.service.ts b/services/self-service/src/main/resources/webapp/src/app/core/services/applicationServiceFacade.service.ts
index c4fb49bd7..cd39b0e67 100644
--- a/services/self-service/src/main/resources/webapp/src/app/core/services/applicationServiceFacade.service.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/core/services/applicationServiceFacade.service.ts
@@ -17,13 +17,13 @@
  * under the License.
  */
 
-import { Injectable } from '@angular/core';
-import { Observable } from 'rxjs';
-import { HttpClient } from '@angular/common/http';
+import {Injectable} from '@angular/core';
+import {Observable} from 'rxjs';
+import {HttpClient} from '@angular/common/http';
 
-import { Dictionary } from '../collections';
-import { environment } from '../../../environments/environment';
-import { HTTPMethod } from '../util';
+import {Dictionary} from '../collections';
+import {environment} from '../../../environments/environment';
+import {HTTPMethod} from '../util';
 
 // we can now access environment.apiUrl
 const API_URL = environment.apiUrl;
@@ -81,6 +81,7 @@ export class ApplicationServiceFacade {
   private static readonly AUDIT = 'audit';
   private static readonly CONFIG = 'config';
   private static readonly QUOTA = 'quota';
+  private static readonly IMAGE_PAGE = 'image_page';
 
   private requestRegistry: Dictionary<string>;
 
@@ -180,6 +181,12 @@ export class ApplicationServiceFacade {
       null);
   }
 
+  buildGetUserImagePage(): Observable<any> {
+    return this.buildRequest(HTTPMethod.GET,
+      this.requestRegistry.Item(ApplicationServiceFacade.IMAGE_PAGE),
+      null);
+  }
+
   public buildGetTemplatesRequest(params): Observable<any> {
     return this.buildRequest(HTTPMethod.GET,
       this.requestRegistry.Item(ApplicationServiceFacade.TEMPLATES) + params,
@@ -714,6 +721,8 @@ export class ApplicationServiceFacade {
     // Exploratory Environment
     this.requestRegistry.Add(ApplicationServiceFacade.PROVISIONED_RESOURCES,
       '/api/infrastructure/info');
+    this.requestRegistry.Add(ApplicationServiceFacade.IMAGE_PAGE,
+      '/api/infrastructure_provision/exploratory_environment/image/user');
     this.requestRegistry.Add(ApplicationServiceFacade.EXPLORATORY_ENVIRONMENT,
       '/api/infrastructure_provision/exploratory_environment');
     this.requestRegistry.Add(ApplicationServiceFacade.TEMPLATES,
diff --git a/services/self-service/src/main/resources/webapp/src/app/core/services/index.ts b/services/self-service/src/main/resources/webapp/src/app/core/services/index.ts
index d84741540..68ce7e151 100644
--- a/services/self-service/src/main/resources/webapp/src/app/core/services/index.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/core/services/index.ts
@@ -40,3 +40,4 @@ export * from './storage.service';
 export * from './project.service';
 export * from './odahu-deployment.service';
 export * from './endpoint.service';
+export * from './user-images-page.service';
diff --git a/services/self-service/src/main/java/com/epam/datalab/backendapi/resources/dto/ImageInfoRecord.java b/services/self-service/src/main/resources/webapp/src/app/core/services/user-images-page.service.ts
similarity index 56%
copy from services/self-service/src/main/java/com/epam/datalab/backendapi/resources/dto/ImageInfoRecord.java
copy to services/self-service/src/main/resources/webapp/src/app/core/services/user-images-page.service.ts
index 18692c34c..05e9e9077 100644
--- a/services/self-service/src/main/java/com/epam/datalab/backendapi/resources/dto/ImageInfoRecord.java
+++ b/services/self-service/src/main/resources/webapp/src/app/core/services/user-images-page.service.ts
@@ -17,21 +17,23 @@
  * under the License.
  */
 
-package com.epam.datalab.backendapi.resources.dto;
+import {  Injectable } from '@angular/core';
+import {  Observable } from 'rxjs';
+import {  catchError } from 'rxjs/operators';
+import {  ErrorUtils } from '../util';
 
-import com.epam.datalab.dto.exploratory.ImageStatus;
-import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
-import lombok.Data;
+import { ApplicationServiceFacade } from './applicationServiceFacade.service';
+import { ProjectModel } from '../../resources/images';
 
-@Data
-@JsonIgnoreProperties(ignoreUnknown = true)
-public class ImageInfoRecord {
-    private final String name;
-    private final String description;
-    private final String project;
-    private final String endpoint;
-    private final String user;
-    private final String application;
-    private final String fullName;
-    private final ImageStatus status;
+@Injectable()
+export class UserImagesPageService {
+  constructor(private applicationServiceFacade: ApplicationServiceFacade) { }
+
+
+  getUserImagePageInfo(): Observable<ProjectModel[]> {
+    return this.applicationServiceFacade.buildGetUserImagePage()
+      .pipe(
+        catchError(ErrorUtils.handleServiceError)
+      );
+  }
 }
diff --git a/services/self-service/src/main/resources/webapp/src/app/core/services/userResource.service.ts b/services/self-service/src/main/resources/webapp/src/app/core/services/userResource.service.ts
index 3f2d5f8d6..05061c533 100644
--- a/services/self-service/src/main/resources/webapp/src/app/core/services/userResource.service.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/core/services/userResource.service.ts
@@ -23,6 +23,7 @@ import { catchError, map } from 'rxjs/operators';
 
 import { ErrorUtils } from '../util/';
 import { ApplicationServiceFacade } from './applicationServiceFacade.service';
+import {ProjectModel} from '../../resources/images/images.model';
 
 @Injectable()
 export class UserResourceService {
@@ -109,9 +110,9 @@ export class UserResourceService {
   }
 
   public suspendComputationalResource(
-    projectName: string, 
-    notebookName: string, 
-    computationalResourceName: string, 
+    projectName: string,
+    notebookName: string,
+    computationalResourceName: string,
     provider: string
   ): Observable<{}> {
     const body = JSON.stringify('/' + projectName + '/' + notebookName + '/' + computationalResourceName + '/terminate');
@@ -123,10 +124,10 @@ export class UserResourceService {
   }
 
   public toggleStopStartAction(
-    project: string, 
-    notebook: string, 
-    resource: string, 
-    action, 
+    project: string,
+    notebook: string,
+    resource: string,
+    action,
     provider: string
   ): Observable<{}> {
     const url = `/${project}/${notebook}/${resource}/${action}`;
diff --git a/services/self-service/src/main/resources/webapp/src/app/reports/reporting/reporting-grid/reporting-grid.component.ts b/services/self-service/src/main/resources/webapp/src/app/reports/reporting/reporting-grid/reporting-grid.component.ts
index 513ac45c1..9e45d9133 100644
--- a/services/self-service/src/main/resources/webapp/src/app/reports/reporting/reporting-grid/reporting-grid.component.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/reports/reporting/reporting-grid/reporting-grid.component.ts
@@ -62,13 +62,13 @@ export class ReportingGridComponent implements OnInit {
   isFiltered: boolean = false;
   active: object = {};
   displayedColumns: string[] = [
-    'name', 'user', 'project', 
-    'type', 'status', 'shape', 
+    'name', 'user', 'project',
+    'type', 'status', 'shape',
     'service', 'empty', 'charge'
   ];
   displayedFilterColumns: string[] = [
-    'name-filter', 'user-filter', 'project-filter', 
-    'type-filter', 'status-filter', 'shape-filter',  
+    'name-filter', 'user-filter', 'project-filter',
+    'type-filter', 'status-filter', 'shape-filter',
     'service-filter', 'empty-filter', 'actions'
   ];
   filtered: any;
@@ -106,7 +106,7 @@ export class ReportingGridComponent implements OnInit {
 
   ngOnInit() {
     this.userAgentIndex = window.navigator.userAgent.indexOf('Firefox');
-    
+
     window.setTimeout(() => {
       this.isScrollButtonsVisible = this.tableWrapper.nativeElement.offsetWidth - this.table._elementRef.nativeElement.offsetWidth < 0;
       this.checkMaxRight();
@@ -128,7 +128,6 @@ export class ReportingGridComponent implements OnInit {
 
   refreshData(fullReport, report) {
     this.reportData = [...report];
-    console.log(fullReport);
     this.fullReport = fullReport;
     this.checkFilters();
   }
@@ -168,7 +167,7 @@ export class ReportingGridComponent implements OnInit {
         return 0;
       });
     }
-    
+
     this.refreshData(this.fullReport, report);
     this.removeSorting();
     this.active[sortItem + direction] = true;
@@ -223,7 +222,7 @@ export class ReportingGridComponent implements OnInit {
     const arg = this.tableWrapper.nativeElement.offsetWidth +
       this.tableWrapper.nativeElement.scrollLeft + 2 <= this.table._elementRef.nativeElement.offsetWidth;
     return this.isMaxRight.next(arg);
-    
+
   }
 
   public onFilterNameUpdate(targetElement: any) {
diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/bucket-browser.component.ts b/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/bucket-browser.component.ts
index 010e5d22e..05f822f4f 100644
--- a/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/bucket-browser.component.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/bucket-browser.component.ts
@@ -127,9 +127,8 @@ export class BucketBrowserComponent implements OnInit, OnDestroy {
   }
 
   public toggleSelectedFile(file, type): void {
-    console.log(file, type);
-    type === 'file' 
-      ? file.isSelected = !file.isSelected 
+    type === 'file'
+      ? file.isSelected = !file.isSelected
       : file.isFolderSelected = !file.isFolderSelected;
     this.selected = this.folderItems.filter(item => item.isSelected);
     this.selectedFolderForAction = this.folderItems.filter(item => item.isFolderSelected);
@@ -260,8 +259,8 @@ export class BucketBrowserComponent implements OnInit, OnDestroy {
       this.objectPath = event.pathObject;
       this.path = event.path;
       this.originFolderItems = this.folderItems.map(v => v);
-      this.pathInsideBucket = this.path.indexOf('/') !== -1 
-        ? this.path.slice(this.path.indexOf('/') + 1) + '/' 
+      this.pathInsideBucket = this.path.indexOf('/') !== -1
+        ? this.path.slice(this.path.indexOf('/') + 1) + '/'
         : '';
       this.folderItems.forEach(item => item.isSelected = false);
     }
@@ -327,7 +326,7 @@ export class BucketBrowserComponent implements OnInit, OnDestroy {
       if (!file) {
         file = waitUploading[0];
       }
-      
+
       file.status = 'uploading';
       this.isFileUploading = this.addedFiles.some(v => v.status === 'uploading');
       this.isQueueFull = this.addedFiles.some(v => v.status === 'waiting');
@@ -350,7 +349,7 @@ export class BucketBrowserComponent implements OnInit, OnDestroy {
               this.sendFile(this.addedFiles.find(v => v.status === 'waiting'));
               this.bucketDataService.refreshBucketdata(this.bucketName, this.endpoint);
             }
-          }, 
+          },
           error => {
             window.clearInterval(file.interval);
             file.status = 'failed';
@@ -412,7 +411,7 @@ export class BucketBrowserComponent implements OnInit, OnDestroy {
                 selected[0].progress = 0;
               }, 1000);
             }
-          }, 
+          },
           error => {
             this.toastr.error(error.message || 'File downloading error!', 'Oops!');
             selected[0]['isDownloading'] = false;
@@ -461,7 +460,7 @@ export class BucketBrowserComponent implements OnInit, OnDestroy {
   public copyPath(): void {
     const selected = this.folderItems.filter(item => item.isSelected || item.isFolderSelected)[0];
     const pathToItem = `${this.pathInsideBucket}${selected.item}${selected.isFolderSelected ? '/' : ''}`;
-    
+
     const cloud = this.getCloud();
     const protocol = HelpUtils.getBucketProtocol(cloud);
     if (cloud !== 'azure') {
diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/images/images.component.html b/services/self-service/src/main/resources/webapp/src/app/resources/images/images.component.html
new file mode 100644
index 000000000..595cd2893
--- /dev/null
+++ b/services/self-service/src/main/resources/webapp/src/app/resources/images/images.component.html
@@ -0,0 +1,240 @@
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you 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.
+  -->
+
+<section class="image-list--wrapper">
+  <nav class="image-list__nav-bar">
+    <div class="selection">
+      <div class="mat-reset">
+        <div class="control selector-wrapper"
+             [ngClass]="{'disabled-select': !isProjectsMoreThanOne}"
+        >
+          <mat-form-field>
+            <mat-label>Select project</mat-label>
+
+            <mat-select
+              disableOptionCentering
+              panelClass="top-select scrolling"
+              [disabled]="!projectList.length"
+            >
+              <mat-option
+                *ngIf="isProjectsMoreThanOne"
+                (click)="onSelectClick('')"
+              >
+                Show all
+              </mat-option>
+              <mat-option
+                *ngFor="let project of projectList"
+                [value]="project"
+                (click)="onSelectClick(project)"
+              >
+                {{ project }}
+              </mat-option>
+              <mat-option *ngIf="!projectList?.length" class="multiple-select ml-10" disabled>
+                Projects list is empty
+              </mat-option>
+            </mat-select>
+            <button class="caret" [disabled]="false">
+              <i class="material-icons">keyboard_arrow_down</i>
+            </button>
+          </mat-form-field>
+        </div>
+      </div>
+    </div>
+
+    <div class="button--wrapper">
+      <span class="action-button--wrapper">
+          <button
+            type="button"
+            class="butt action-button"
+            mat-raised-button
+            [disabled]="true"
+            (click)="onActionClick()"
+          >
+            Actions
+            <i class="material-icons" >{{ !isActionsOpen ?  'expand_more' : 'expand_less' }}</i>
+          </button>
+          </span>
+      <span>
+        <button mat-raised-button class="butt">
+          <i class="material-icons highlight">autorenew</i>
+          Refresh
+        </button>
+      </span>
+    </div>
+  </nav>
+  <mat-divider></mat-divider>
+
+  <table mat-table [dataSource]="dataSource" class="mat-elevation-z8 demo-table data-grid">
+    <!-- Position Column -->
+    <ng-container matColumnDef="checkbox">
+      <th mat-header-cell *matHeaderCellDef class="image-checkbox--wrapper">
+        <div class="header-cell--wrapper">
+          <span>
+            <datalab-checkbox
+              (click)="allCheckboxToggle()"
+              [checked]="checkboxSelected"
+              class="image-checkbox"
+            ></datalab-checkbox>
+          </span>
+          <i class="material-icons header-cell__dots">
+            <span>more_vert</span>
+          </i>
+        </div>
+      </th>
+      <td mat-cell *matCellDef="let element" class="image-checkbox--wrapper">
+        <datalab-checkbox
+          (click)="onCheckboxClick(element)"
+          class="image-checkbox"
+          [checked]="element.isSelected"
+        ></datalab-checkbox>
+      </td>
+    </ng-container>
+
+    <ng-container matColumnDef="imageName">
+      <th mat-header-cell *matHeaderCellDef>
+        <div class="header-cell--wrapper">
+          <span>{{tableHeaderCellTitles.imageName}}</span>
+          <i class="material-icons header-cell__dots">
+            <span>more_vert</span>
+          </i>
+        </div>
+      </th>
+      <td mat-cell *matCellDef="let element"> {{element.name}} </td>
+    </ng-container>
+
+    <ng-container matColumnDef="creationDate">
+      <th mat-header-cell *matHeaderCellDef>
+        <div class="header-cell--wrapper">
+          <span>{{tableHeaderCellTitles.creationDate}}</span>
+          <i class="material-icons header-cell__dots">
+            <span>more_vert</span>
+          </i>
+        </div>
+      </th>
+      <td mat-cell *matCellDef="let element">
+        <span class="date-item"> {{element.creationDate | date: 'yyyy-MM-dd'}} </span>
+        <span> {{element.creationDate | date: 'HH:mm:ss'}} </span>
+      </td>
+    </ng-container>
+
+    <ng-container matColumnDef="provider">
+      <th mat-header-cell *matHeaderCellDef>
+        <div class="header-cell--wrapper">
+          <span>{{tableHeaderCellTitles.provider}}</span>
+          <i class="material-icons header-cell__dots">
+            <span>more_vert</span>
+          </i>
+        </div>
+      </th>
+      <td mat-cell *matCellDef="let element"> {{element.cloudProvider}} </td>
+    </ng-container>
+
+    <ng-container matColumnDef="imageStatus">
+      <th mat-header-cell *matHeaderCellDef>
+        <div class="header-cell--wrapper">
+          <span>{{tableHeaderCellTitles.imageStatus}}</span>
+          <i class="material-icons header-cell__dots">
+            <span>more_vert</span>
+          </i>
+        </div>
+      </th>
+      <td mat-cell *matCellDef="let element" ngClass="{{ element.status.toLowerCase() || ''}}">
+        {{element.status | capitalizeFirstLetter}}
+      </td>
+    </ng-container>
+
+    <ng-container matColumnDef="sharedStatus">
+      <th mat-header-cell *matHeaderCellDef>
+        <div class="header-cell--wrapper">
+          <span>{{tableHeaderCellTitles.sharedStatus}}</span>
+          <i class="material-icons header-cell__dots">
+            <span>more_vert</span>
+          </i>
+        </div>
+      </th>
+      <td mat-cell *matCellDef="let element">
+        <div class="shared-status--wrapper">
+          <span class="shared-status"> {{element.shared}} </span>
+          <span class="currency_details" >
+                <i class="material-icons">help_outline</i>
+              </span>
+        </div>
+      </td>
+    </ng-container>
+
+    <ng-container matColumnDef="templateName">
+      <th mat-header-cell *matHeaderCellDef>
+        <div class="header-cell--wrapper">
+          <span>{{tableHeaderCellTitles.templateName}}</span>
+          <i class="material-icons header-cell__dots">
+            <span>more_vert</span>
+          </i>
+        </div>
+      </th>
+      <td mat-cell *matCellDef="let element"> {{element.application}} </td>
+    </ng-container>
+
+    <ng-container matColumnDef="instanceName">
+      <th mat-header-cell *matHeaderCellDef>
+        <div class="header-cell--wrapper">
+          <span>{{tableHeaderCellTitles.instanceName}}</span>
+          <i class="material-icons header-cell__dots">
+            <span>more_vert</span>
+          </i>
+        </div>
+      </th>
+      <td mat-cell *matCellDef="let element"> {{element.instanceName}} </td>
+    </ng-container>
+
+    <ng-container matColumnDef="actions">
+      <th mat-header-cell *matHeaderCellDef> {{tableHeaderCellTitles.actions}} </th>
+      <td mat-cell *matCellDef="let element" class="settings actions-col">
+
+        <div class="button--wrapper">
+          <span class="currency_details" >
+                <i class="material-icons">help_outline</i>
+              </span>
+          <span #settings class="actions" (click)="actions.toggle($event, settings)"></span>
+        </div>
+        <bubble-up #actions class="list-menu" position="bottom-left" alternative="top-left">
+          <ul class="list-unstyled">
+            <li
+              matTooltip="Unable to terminate notebook because at least one compute is in progress"
+              matTooltipPosition="above"
+            >
+              <div>
+                <i class="material-icons">phonelink_off</i>
+                <span>Terminate</span>
+              </div>
+            </li>
+            <li>
+              <div>
+                <i class="material-icons">create</i>
+                <span>Share</span>
+              </div>
+            </li>
+          </ul>
+        </bubble-up>
+      </td>
+    </ng-container>
+
+    <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
+    <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
+  </table>
+</section>
diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/images/images.component.scss b/services/self-service/src/main/resources/webapp/src/app/resources/images/images.component.scss
new file mode 100644
index 000000000..e203266ef
--- /dev/null
+++ b/services/self-service/src/main/resources/webapp/src/app/resources/images/images.component.scss
@@ -0,0 +1,102 @@
+/*!
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+.image-list {
+  &--wrapper {
+    padding: 12px 15px;
+  }
+
+  &__nav-bar {
+    display: flex;
+    justify-content: space-between;
+  }
+}
+
+.action-button {
+  padding: 0 38px 0 45px;
+
+  &--wrapper {
+    margin-right: 10px;
+  }
+}
+
+.demo-table {
+  width: 100%;
+}
+
+.mat-column-demo-position {
+  width: 32px;
+  border-right: 1px solid currentColor;
+  padding-right: 24px;
+  text-align: center;
+}
+
+.mat-column-demo-name {
+  padding-left: 16px;
+  font-size: 20px;
+}
+
+.mat-column-demo-weight {
+  font-style: italic;
+}
+
+.mat-column-demo-symbol {
+  width: 32px;
+  text-align: center;
+  font-weight: bold;
+}
+
+.image-checkbox {
+  &--wrapper {
+    padding-left: 10px !important;
+  }
+}
+
+.currency_details {
+  display: flex;
+  color: #35afd5;
+  cursor: pointer;
+  transition: all 0.45s ease-in-out;
+}
+
+.material-icons {
+  font-size: 18px;
+}
+
+.button--wrapper,
+.header-cell--wrapper {
+  display: flex;
+  justify-content: space-between;
+}
+
+.shared-status {
+  padding-right: 16px;
+
+  &--wrapper {
+    display: flex;
+  }
+}
+
+.header-cell__dots {
+  margin-right: 10px;
+}
+
+.date-item {
+  margin-right: 10px;
+}
diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/images/images.component.ts b/services/self-service/src/main/resources/webapp/src/app/resources/images/images.component.ts
new file mode 100644
index 000000000..cd39142fa
--- /dev/null
+++ b/services/self-service/src/main/resources/webapp/src/app/resources/images/images.component.ts
@@ -0,0 +1,135 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 { ToastrService } from 'ngx-toastr';
+
+import { GeneralEnvironmentStatus } from '../../administration/management/management.model';
+import { HealthStatusService, UserImagesPageService } from '../../core/services';
+import { ImageModel, ProjectModel } from './images.model';
+import { Image_Table_Column_Headers } from './images.config';
+
+
+interface Aaa {
+  isSelected?: boolean;
+  imageName: string;
+  creationDate: string;
+  provider: string;
+  imageStatus: string;
+  sharedStatus: 'Private' | 'Shared';
+  templateName: string;
+  instanceName: string;
+  actions: object;
+}
+
+
+const tableTitles = <const>['checkbox', 'imageName', 'creationDate', 'provider', 'imageStatus', 'sharedStatus', 'templateName', 'instanceName', 'actions'];
+
+@Component({
+  selector: 'datalab-images',
+  templateUrl: './images.component.html',
+  styleUrls: [
+    './images.component.scss',
+    '../resources-grid/resources-grid.component.scss',
+    '../resources.component.scss'
+  ]
+})
+
+export class ImagesComponent implements OnInit {
+  isActionsOpen: boolean = false;
+  healthStatus: GeneralEnvironmentStatus;
+  tableHeaderCellTitles: typeof Image_Table_Column_Headers = Image_Table_Column_Headers;
+  displayedColumns: typeof tableTitles = tableTitles;
+  dataSource: ImageModel[] = [];
+  checkboxSelected: boolean = false;
+  projectList: string[] = [];
+  private cashedImageListData: ProjectModel[] = [];
+
+  constructor(
+    private healthStatusService: HealthStatusService,
+    public toastr: ToastrService,
+    private userImagesPageService: UserImagesPageService
+  ) { }
+
+  ngOnInit(): void {
+    this.getEnvironmentHealthStatus();
+    this.getUserImagePageInfo();
+  }
+
+  onCheckboxClick(element: ImageModel) {
+    element.isSelected = !element.isSelected;
+  }
+
+  allCheckboxToggle(): void {
+    this.checkboxSelected = !this.checkboxSelected;
+
+    if (this.checkboxSelected) {
+      this.dataSource.forEach(image => image.isSelected = true);
+    } else {
+      this.dataSource.forEach(image => image.isSelected = false);
+    }
+  }
+
+  onActionClick(): void {
+    this.isActionsOpen = !this.isActionsOpen;
+  }
+
+  onSelectClick(projectName: string): void {
+    if (!projectName) {
+      this.dataSource = this.getImageList();
+    }
+    const { images } = this.cashedImageListData.find(({project}) => project === projectName);
+    this.dataSource = [...images];
+  }
+
+  private getImageList() {
+    return this.cashedImageListData.reduce((acc, {images}) => [...acc, ...images], []);
+  }
+
+  private getEnvironmentHealthStatus() {
+    this.healthStatusService.getEnvironmentHealthStatus().subscribe(
+      (result: GeneralEnvironmentStatus) => {
+        this.healthStatus = result;
+      },
+      error => this.toastr.error(error.message, 'Oops!')
+    );
+  }
+
+  private getUserImagePageInfo(): void {
+    this.userImagesPageService.getUserImagePageInfo().subscribe(imageListData => this.initImageTable(imageListData));
+  }
+
+  private initImageTable(imagePageList: ProjectModel[]) {
+    this.cashedImageListData = imagePageList;
+    this.getProjectList(imagePageList);
+    this.dataSource = this.getImageList();
+  }
+
+  private getProjectList(imagePageList: ProjectModel[]): void {
+    if (!imagePageList) {
+      return;
+    }
+    imagePageList.forEach(({project}) => this.projectList.push(project));
+  }
+
+  get isProjectsMoreThanOne () {
+    return this.projectList.length > 1;
+  }
+}
diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/images/images.config.ts b/services/self-service/src/main/resources/webapp/src/app/resources/images/images.config.ts
new file mode 100644
index 000000000..f2ac9f810
--- /dev/null
+++ b/services/self-service/src/main/resources/webapp/src/app/resources/images/images.config.ts
@@ -0,0 +1,10 @@
+export enum Image_Table_Column_Headers {
+  imageName = 'Image name',
+  creationDate = 'Creation date',
+  provider = 'Provider',
+  imageStatus = 'Image status',
+  sharedStatus = 'Shared status',
+  templateName = 'Template name',
+  instanceName = 'Instance name',
+  actions = 'Actions',
+}
diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/images/images.model.ts b/services/self-service/src/main/resources/webapp/src/app/resources/images/images.model.ts
new file mode 100644
index 000000000..b5b725a96
--- /dev/null
+++ b/services/self-service/src/main/resources/webapp/src/app/resources/images/images.model.ts
@@ -0,0 +1,20 @@
+export interface ProjectModel {
+  project: string;
+  images: ImageModel[];
+}
+
+export interface ImageModel {
+  application: string;
+  cloudProvider: 'AWS' | 'GCP' | 'Azure';
+  creationDate: string;
+  description: string;
+  endpoint: string;
+  fullName: string;
+  instanceName: string;
+  name: string;
+  project: string;
+  shared: 'private' | 'shared';
+  status: 'created' | 'creating' | 'terminated' | 'terminating' | 'failed';
+  user: string;
+  isSelected?: boolean;
+}
diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/images/index.ts b/services/self-service/src/main/resources/webapp/src/app/resources/images/index.ts
new file mode 100644
index 000000000..365e9989d
--- /dev/null
+++ b/services/self-service/src/main/resources/webapp/src/app/resources/images/index.ts
@@ -0,0 +1,2 @@
+export * from './images.config';
+export * from './images.model';
diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/resources-grid/resources-grid.component.ts b/services/self-service/src/main/resources/webapp/src/app/resources/resources-grid/resources-grid.component.ts
index 55c824d95..28b79ecd8 100644
--- a/services/self-service/src/main/resources/webapp/src/app/resources/resources-grid/resources-grid.component.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/resources/resources-grid/resources-grid.component.ts
@@ -32,14 +32,13 @@ import { ProjectService, UserResourceService, OdahuDeploymentService } from '../
 import { ExploratoryModel } from './resources-grid.model';
 import { FilterConfigurationModel } from './filter-configuration.model';
 import { GeneralEnvironmentStatus } from '../../administration/management/management.model';
-import { ConfirmationDialogType } from '../../shared';
+import { ConfirmationDialogComponent, ConfirmationDialogType } from '../../shared';
 import { SortUtils, CheckUtils } from '../../core/util';
 import { DetailDialogComponent } from '../exploratory/detail-dialog';
 import { AmiCreateDialogComponent } from '../exploratory/ami-create-dialog';
 import { InstallLibrariesComponent } from '../exploratory/install-libraries';
 import { ComputationalResourceCreateDialogComponent } from '../computational/computational-resource-create-dialog/computational-resource-create-dialog.component';
 import { CostDetailsDialogComponent } from '../exploratory/cost-details-dialog';
-import { ConfirmationDialogComponent } from '../../shared/modal-dialog/confirmation-dialog';
 import { SchedulerComponent } from '../scheduler';
 import { DICTIONARY } from '../../../dictionary/global.dictionary';
 import { ProgressBarService } from '../../core/services/progress-bar.service';
@@ -195,9 +194,9 @@ export class ResourcesGridComponent implements OnInit {
     this.buildGrid();
   }
 
-  public containsNotebook(notebook_name: string, envoirmentNames: Array<string>): boolean {
-    if (notebook_name && envoirmentNames.length ) {
-      return envoirmentNames
+  public containsNotebook(notebook_name: string, environmentNames: Array<string>): boolean {
+    if (notebook_name && environmentNames.length ) {
+      return environmentNames
         .some(item => CheckUtils.delimitersFiltering(notebook_name) === CheckUtils.delimitersFiltering(item));
     }
       return false;
diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/resources-grid/resources-grid.model.ts b/services/self-service/src/main/resources/webapp/src/app/resources/resources-grid/resources-grid.model.ts
index 8c8d8d7d8..64e5855a4 100644
--- a/services/self-service/src/main/resources/webapp/src/app/resources/resources-grid/resources-grid.model.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/resources/resources-grid/resources-grid.model.ts
@@ -148,10 +148,3 @@ export class ExploratoryModel {
     }
   }
 }
-
-// export interface Exploratory {
-//   project: string;
-//   endpoints: [];
-//   projectEndpoints: [];
-//   exploratory: ExploratoryModel[];
-// }
diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/resources.component.html b/services/self-service/src/main/resources/webapp/src/app/resources/resources.component.html
index 131aa638d..23117f7f2 100644
--- a/services/self-service/src/main/resources/webapp/src/app/resources/resources.component.html
+++ b/services/self-service/src/main/resources/webapp/src/app/resources/resources.component.html
@@ -26,9 +26,9 @@
         [matTooltipClass]="'full-size-tooltip'"
         [matTooltipDisabled]="healthStatus?.projectAssigned && resourcesGrid.activeProjectsList?.length !== 0"
       >
-        <button 
-          mat-raised-button 
-          class="butt butt-create" 
+        <button
+          mat-raised-button
+          class="butt butt-create"
           (click)="createEnvironment()"
           [disabled]="!healthStatus?.projectAssigned || !resourcesGrid.activeProjectsList?.length"
         >
@@ -36,26 +36,26 @@
         </button>
       </span>
       <div class="mat-reset">
-        <div class="control selector-wrapper" *ngIf="projects?.length" 
+        <div class="control selector-wrapper" *ngIf="projects?.length"
         [ngClass]="{'disabled-select': !isProjectsMoreThanOne}"
         >
           <mat-form-field>
             <mat-label>Select project</mat-label>
 
-            <mat-select 
+            <mat-select
               disableOptionCentering
-              [(value)]="resourcesGrid.activeProject" 
+              [(value)]="resourcesGrid.activeProject"
               panelClass="top-select scrolling"
               [disabled]="!isProjectsMoreThanOne"
             >
-              <mat-option 
-                *ngIf="projects?.length > 1" 
+              <mat-option
+                *ngIf="projects?.length > 1"
                 (click)="setActiveProject('')"
               >
                 Show all
               </mat-option>
-              <mat-option 
-                *ngFor="let project of projects" 
+              <mat-option
+                *ngFor="let project of projects"
                 [value]="project"
                 (click)="setActiveProject(project)"
               >
@@ -72,15 +72,15 @@
     </div>
 
     <div>
-      <span  
+      <span
         matTooltip="{{!this.bucketStatus?.view ? 'You have not permission to open bucket browser' : 'You have not any bucket'}}"
         matTooltipPosition="above"
         matTooltipDisabled="{{resourcesGrid.bucketsList?.length > 0 && this.bucketStatus?.view}}"
         [matTooltipClass]="'full-size-tooltip'"
       >
-        <button 
-          mat-raised-button 
-          class="butt butt-tool" 
+        <button
+          mat-raised-button
+          class="butt butt-tool"
           (click)="bucketBrowser(this.bucketStatus?.view)"
           [disabled]="!this.bucketStatus?.view || resourcesGrid.bucketsList?.length === 0"
         >
diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/resources.component.ts b/services/self-service/src/main/resources/webapp/src/app/resources/resources.component.ts
index e876331bf..6b0a1b764 100644
--- a/services/self-service/src/main/resources/webapp/src/app/resources/resources.component.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/resources/resources.component.ts
@@ -86,7 +86,7 @@ export class ResourcesComponent implements OnInit {
           bucketStatus: this.bucketStatus,
           buckets: this.resourcesGrid.bucketsList
         },
-        panelClass: 'modal-fullscreen' 
+        panelClass: 'modal-fullscreen'
       })
       .afterClosed().subscribe();
   }
diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/resources.module.ts b/services/self-service/src/main/resources/webapp/src/app/resources/resources.module.ts
index 47aea4c70..996c347d4 100644
--- a/services/self-service/src/main/resources/webapp/src/app/resources/resources.module.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/resources/resources.module.ts
@@ -31,6 +31,10 @@ import { MatTreeModule } from '@angular/material/tree';
 import { BucketDataService } from './bucket-browser/bucket-data.service';
 import { ConvertFileSizePipeModule } from '../core/pipes/convert-file-size';
 import { BucketBrowserModule } from './bucket-browser/bucket-browser.module';
+import { ImagesComponent } from './images/images.component';
+import {CheckboxModule} from '../shared/checkbox';
+import {BubbleModule} from '../shared';
+import { CapitalizeFirstLetterPipeModule } from '../core/pipes';
 
 @NgModule({
   imports: [
@@ -42,12 +46,16 @@ import { BucketBrowserModule } from './bucket-browser/bucket-browser.module';
     MaterialModule,
     MatTreeModule,
     ConvertFileSizePipeModule,
-    BucketBrowserModule
+    BucketBrowserModule,
+    CheckboxModule,
+    BubbleModule,
+    CapitalizeFirstLetterPipeModule
   ],
   declarations: [
     ResourcesComponent,
     ManageUngitComponent,
     ConfirmDeleteAccountDialogComponent,
+    ImagesComponent,
 
   ],
   entryComponents: [ManageUngitComponent, ConfirmDeleteAccountDialogComponent],
diff --git a/services/self-service/src/main/resources/webapp/src/app/shared/bubble/bubble.component.ts b/services/self-service/src/main/resources/webapp/src/app/shared/bubble/bubble.component.ts
index 02e8ddef8..86bcfaaeb 100644
--- a/services/self-service/src/main/resources/webapp/src/app/shared/bubble/bubble.component.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/shared/bubble/bubble.component.ts
@@ -17,17 +17,17 @@
  * under the License.
  */
 
-import { 
-  Component, 
-  Input, 
-  Output, 
-  EventEmitter, 
+import {
+  Component,
+  Input,
+  Output,
+  EventEmitter,
   HostBinding,
-  ChangeDetectorRef, 
-  ElementRef, 
+  ChangeDetectorRef,
+  ElementRef,
   OnDestroy,
-  ViewEncapsulation, 
-  HostListener 
+  ViewEncapsulation,
+  HostListener
 } from '@angular/core';
 import { BubblesCollector, BubbleService } from './bubble.service';
 
@@ -101,11 +101,14 @@ export class BubbleComponent implements OnDestroy {
           this.changeDirection = !this.isInViewport(bubbleElem);
 
           let isBubbleOutOfWrapper;
-          
-          if(document.querySelector('.wrapper')) {
-            isBubbleOutOfWrapper = bubbleElem.getBoundingClientRect().bottom > document.querySelector('.wrapper').getBoundingClientRect().bottom;
+
+          if (document.querySelector('.wrapper')) {
+            isBubbleOutOfWrapper = bubbleElem.getBoundingClientRect()
+              .bottom > document.querySelector('.wrapper')
+              .getBoundingClientRect()
+              .bottom;
           }
-          
+
           (this.changeDirection || isBubbleOutOfWrapper) && this.bubbleService.updatePosition(element, bubbleElem, this.alternative);
         }
 
diff --git a/services/self-service/src/main/resources/webapp/src/app/shared/navbar/navbar.component.html b/services/self-service/src/main/resources/webapp/src/app/shared/navbar/navbar.component.html
index 8c52c46f9..60e635d99 100644
--- a/services/self-service/src/main/resources/webapp/src/app/shared/navbar/navbar.component.html
+++ b/services/self-service/src/main/resources/webapp/src/app/shared/navbar/navbar.component.html
@@ -37,11 +37,11 @@
     <!-- <a *ngIf="healthStatus.status" [routerLink]="['/environment_management']" class="statusbar">
       <span class="material-icons" ngClass="{{healthStatus.status || ''}}">radio_button_checked</span>
     </a> -->
-    
-    <a 
-      *ngIf="metadata" 
-      class="statusbar about-btn--wrapper" 
-      #info 
+
+    <a
+      *ngIf="metadata"
+      class="statusbar about-btn--wrapper"
+      #info
       (click)="actions.toggle($event, info)">
         <span class="about-btn">About</span>
     </a>
@@ -72,7 +72,7 @@
       <a class="help-link" href="https://github.com/apache/incubator-datalab/blob/master/USER_GUIDE.md" target="_blank">Help</a>
     </span>
 
-    <span 
+    <span
       class="material-icons account-icon--nav-bar account-icon" #login (click)="loginInfo.toggle($event, login)">
         account_circle
     </span>
@@ -85,53 +85,70 @@
         <span class="user-mail">{{userData.email}}</span>
         <button type="button" class="logout-btn" (click)="logout_btnClick()">
           Log out from account
-        </button> 
+        </button>
       </div>
     </bubble-up>
   </div>
 </div>
 
 <mat-sidenav-container class="example-container" autosize >
-  <mat-sidenav 
-    #drawer 
-    mode="side" 
-    opened 
-    role="navigation" 
-    [style.width]="isExpanded ? '220px' : '60px'" 
-    disableClose 
+  <mat-sidenav
+    #drawer
+    mode="side"
+    opened
+    role="navigation"
+    [style.width]="isExpanded ? '220px' : '60px'"
+    disableClose
     *ngIf="healthStatus"
   >
     <mat-nav-list >
       <nav>
         <div>
-          <a 
-            class="nav-item" 
-            [routerLink]="['/resources_list']" 
-            [routerLinkActive]="['active']"
-            [routerLinkActiveOptions]="{exact:true}"
-          >
-            <span *ngIf="isExpanded; else resources">{{sideBarNames.resourses}}</span>
-            <ng-template #resources><i class="material-icons">dashboard</i></ng-template>
+          <a class="nav-item has-children">
+            <span *ngIf="isExpanded">{{sideBarNames.resources}}</span>
+            <a
+              class="sub-nav-item"
+              [style.margin-left.px]="isExpanded ? '30' : '0'"
+              [routerLink]="['/instances']"
+              [routerLinkActive]="['active']"
+              [routerLinkActiveOptions]="{exact:true}"
+            >
+              <span *ngIf="isExpanded; else instances">{{sideBarNames.instances}}</span>
+              <ng-template #instances><i class="material-icons">laptop</i></ng-template>
+            </a>
+
+            <a
+              class="sub-nav-item"
+              [style.margin-left.px]="isExpanded ? '30' : '0'"
+              [routerLink]="['/images']"
+              [routerLinkActive]="['active']"
+              [routerLinkActiveOptions]="{exact:true}"
+            >
+              <span *ngIf="isExpanded; else images">{{sideBarNames.images}}</span>
+              <ng-template #images><i class="material-icons">photo</i></ng-template>
+            </a>
+
           </a>
+
           <a class="nav-item has-children" *ngIf="healthStatus?.billingEnabled || healthStatus?.auditEnabled">
             <span *ngIf="isExpanded">{{sideBarNames.reports}}</span>
-            <a 
-              *ngIf="healthStatus?.auditEnabled" 
-              class="sub-nav-item" 
-              [routerLink]="['/audit']" 
+            <a
+              *ngIf="healthStatus?.auditEnabled"
+              class="sub-nav-item"
+              [routerLink]="['/audit']"
               [style.margin-left.px]="isExpanded ? '30' : '0'"
-              [routerLinkActive]="['active']" 
+              [routerLinkActive]="['active']"
               [routerLinkActiveOptions]="{exact:true}"
             >
               <span *ngIf="isExpanded; else audit">{{sideBarNames.audit}}</span>
               <ng-template #audit><i class="material-icons">library_books</i></ng-template>
             </a>
-            <a 
-              *ngIf="healthStatus?.billingEnabled" 
-              class="sub-nav-item" 
+            <a
+              *ngIf="healthStatus?.billingEnabled"
+              class="sub-nav-item"
               [routerLink]="['/billing_report']"
-              [routerLinkActive]="['active']" 
-              [routerLinkActiveOptions]="{exact:true}" 
+              [routerLinkActive]="['active']"
+              [routerLinkActiveOptions]="{exact:true}"
               [style.margin-left.px]="isExpanded ? '30' : '0'"
             >
               <span *ngIf="isExpanded; else billing">{{sideBarNames.billing}}</span>
@@ -141,21 +158,21 @@
           <a class="nav-item has-children" *ngIf="healthStatus?.admin || healthStatus?.projectAdmin">
             <span *ngIf="isExpanded">{{sideBarNames.administration}}</span>
 
-            <a 
-              class="sub-nav-item" 
-              [style.margin-left.px]="isExpanded ? '30' : '0'" 
+            <a
+              class="sub-nav-item"
+              [style.margin-left.px]="isExpanded ? '30' : '0'"
               [routerLink]="['/roles']"
-              [routerLinkActive]="['active']" 
+              [routerLinkActive]="['active']"
               [routerLinkActiveOptions]="{exact:true}"
             >
               <span *ngIf="isExpanded; else roles">{{sideBarNames.users}}</span>
               <ng-template #roles><i class="material-icons">account_box</i></ng-template>
             </a>
-            <a 
-              class="sub-nav-item" 
-              [style.margin-left.px]="isExpanded ? '30' : '0'" 
+            <a
+              class="sub-nav-item"
+              [style.margin-left.px]="isExpanded ? '30' : '0'"
               [routerLink]="['/projects']"
-              [routerLinkActive]="['active']" 
+              [routerLinkActive]="['active']"
               [routerLinkActiveOptions]="{exact:true}"
             >
               <span *ngIf="isExpanded; else projects">{{sideBarNames.projects}}</span>
@@ -166,21 +183,21 @@
             <!--              <span *ngIf="isExpanded; else odahu">Odahu deployment</span>-->
             <!--              <ng-template #odahu><i class="material-icons">get_app</i></ng-template>-->
             <!--            </a>-->
-            <a 
-              class="sub-nav-item" 
+            <a
+              class="sub-nav-item"
               [style.margin-left.px]="isExpanded ? '30' : '0'"
-              [routerLink]="['/environment_management']" 
+              [routerLink]="['/environment_management']"
               [routerLinkActive]="['active']"
               [routerLinkActiveOptions]="{exact:true}"
             >
               <span *ngIf="isExpanded; else env">{{sideBarNames.resources}}</span>
               <ng-template #env><i class="material-icons">settings</i></ng-template>
             </a>
-            <a 
-              *ngIf="healthStatus?.admin" 
-              class="sub-nav-item" 
+            <a
+              *ngIf="healthStatus?.admin"
+              class="sub-nav-item"
               [style.margin-left.px]="isExpanded ? '30' : '0'"
-              [routerLink]="['/configuration']" 
+              [routerLink]="['/configuration']"
               [routerLinkActive]="['active']"
               [routerLinkActiveOptions]="{exact:true}"
             >
@@ -188,7 +205,7 @@
               <ng-template #env><i class="material-icons">build_circle</i></ng-template>
             </a>
           </a>
-          
+
         </div>
         <!--        <div>-->
         <!--          <a class="nav-item" [routerLink]="['/swagger']" [routerLinkActive]="['active']"-->
diff --git a/services/self-service/src/main/resources/webapp/src/app/shared/navbar/navbar.component.ts b/services/self-service/src/main/resources/webapp/src/app/shared/navbar/navbar.component.ts
index c88094060..4c906b3ef 100644
--- a/services/self-service/src/main/resources/webapp/src/app/shared/navbar/navbar.component.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/shared/navbar/navbar.component.ts
@@ -42,7 +42,7 @@ import {
 } from '@angular/animations';
 import {skip, take} from 'rxjs/operators';
 import {ProgressBarService} from '../../core/services/progress-bar.service';
-import { sideBarNamesConfig, UserInfo } from './navbar.config';
+import {Sidebar_Names_Config, UserInfo} from './navbar.config';
 
 interface Quota {
   projectQuotas: {};
@@ -93,7 +93,7 @@ export class NavbarComponent implements OnInit, OnDestroy {
   isExpanded: boolean = true;
   healthStatus: GeneralEnvironmentStatus;
   subscriptions: Subscription = new Subscription();
-  sideBarNames!: Record<string, string>;
+  sideBarNames: typeof Sidebar_Names_Config = Sidebar_Names_Config;
   userData!: UserInfo;
   commitMaxLength: number = 22;
 
@@ -109,7 +109,6 @@ export class NavbarComponent implements OnInit, OnDestroy {
   ) { }
 
   ngOnInit() {
-    this.sideBarNames = sideBarNamesConfig;
     this.applicationSecurityService.loggedInStatus.subscribe(response => {
       this.subscriptions.unsubscribe();
       this.subscriptions.closed = false;
diff --git a/services/self-service/src/main/resources/webapp/src/app/shared/navbar/navbar.config.ts b/services/self-service/src/main/resources/webapp/src/app/shared/navbar/navbar.config.ts
index 38df6d2c5..44102f602 100644
--- a/services/self-service/src/main/resources/webapp/src/app/shared/navbar/navbar.config.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/shared/navbar/navbar.config.ts
@@ -17,19 +17,20 @@
  * under the License.
  */
 
-export const sideBarNamesConfig: Record<string, string> = {
-    resourses: 'Resources',
-    reports: 'Reports',
-    audit: 'Audit',
-    billing: 'Billing',
-    administration: 'Administration',
-    users: 'Users',
-    projects: 'Projects',
-    resources: 'Resources',
-    configuration: 'Configuration'
+export enum Sidebar_Names_Config {
+  reports = 'Reports',
+  audit = 'Audit',
+  billing = 'Billing',
+  administration = 'Administration',
+  users = 'Users',
+  projects = 'Projects',
+  resources = 'Resources',
+  configuration = 'Configuration',
+  instances = 'Instances',
+  images = 'Images'
 }
 
 export interface UserInfo {
-    email: string;
-    name: string;
+  email: string;
+  name: string;
 }
diff --git a/services/self-service/src/main/resources/webapp/src/styles.scss b/services/self-service/src/main/resources/webapp/src/styles.scss
index 00a27bbe2..5bc005390 100644
--- a/services/self-service/src/main/resources/webapp/src/styles.scss
+++ b/services/self-service/src/main/resources/webapp/src/styles.scss
@@ -121,6 +121,7 @@ mat-chip.mat-chip strong {
   text-align: left;
 }
 
+.created,
 .running,
 .starting,
 .installed,
diff --git a/services/self-service/src/test/java/com/epam/datalab/backendapi/resources/ImageExploratoryResourceTest.java b/services/self-service/src/test/java/com/epam/datalab/backendapi/resources/ImageExploratoryResourceTest.java
index f62b1e3c6..6f7a0e224 100644
--- a/services/self-service/src/test/java/com/epam/datalab/backendapi/resources/ImageExploratoryResourceTest.java
+++ b/services/self-service/src/test/java/com/epam/datalab/backendapi/resources/ImageExploratoryResourceTest.java
@@ -24,6 +24,7 @@ import com.epam.datalab.backendapi.domain.RequestId;
 import com.epam.datalab.backendapi.resources.dto.ExploratoryImageCreateFormDTO;
 import com.epam.datalab.backendapi.resources.dto.ImageInfoRecord;
 import com.epam.datalab.backendapi.service.ImageExploratoryService;
+import com.epam.datalab.cloud.CloudProvider;
 import com.epam.datalab.dto.exploratory.ImageStatus;
 import com.epam.datalab.exceptions.ResourceAlreadyExistException;
 import com.epam.datalab.exceptions.ResourceNotFoundException;
@@ -39,6 +40,7 @@ import javax.ws.rs.core.GenericType;
 import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
+import java.time.LocalDateTime;
 import java.util.Collections;
 import java.util.List;
 
@@ -275,8 +277,18 @@ public class ImageExploratoryResourceTest extends TestBase {
     }
 
     private List<ImageInfoRecord> getImageList() {
-        ImageInfoRecord imageInfoRecord = new ImageInfoRecord("someName", "someDescription", "someProject", "someEndpoint", "someUser", "someApp",
-                "someFullName", ImageStatus.CREATED);
+        ImageInfoRecord imageInfoRecord = new ImageInfoRecord("someName",
+                "2020-02-02",
+                "someDescription",
+                "someProject",
+                "someEndpoint",
+                "someUser",
+                "someApp",
+                "someInstance",
+                CloudProvider.AWS,
+                "someFullName",
+                ImageStatus.CREATED,
+                "private");
         return Collections.singletonList(imageInfoRecord);
     }
 }
diff --git a/services/self-service/src/test/java/com/epam/datalab/backendapi/service/impl/BillingServiceImplTest.java b/services/self-service/src/test/java/com/epam/datalab/backendapi/service/impl/BillingServiceImplTest.java
index 5c9ec461b..b2345a8c8 100644
--- a/services/self-service/src/test/java/com/epam/datalab/backendapi/service/impl/BillingServiceImplTest.java
+++ b/services/self-service/src/test/java/com/epam/datalab/backendapi/service/impl/BillingServiceImplTest.java
@@ -56,6 +56,7 @@ import org.mockito.runners.MockitoJUnitRunner;
 
 import javax.ws.rs.core.GenericType;
 import java.time.LocalDate;
+import java.time.LocalDateTime;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
@@ -745,7 +746,19 @@ public class BillingServiceImplTest extends TestBase {
 
     private List<ImageInfoRecord> getImageInfoRecords() {
         return Collections.singletonList(
-                new ImageInfoRecord(IMAGE_NAME, IMAGE_DESCRIPTION, PROJECT, ENDPOINT, USER, IMAGE_APPLICATION, IMAGE_FULL_NAME, ImageStatus.CREATED)
+                new ImageInfoRecord(
+                        IMAGE_NAME,
+                        "2020-02-02",
+                        IMAGE_DESCRIPTION,
+                        PROJECT,
+                        ENDPOINT,
+                        USER,
+                        IMAGE_APPLICATION,
+                        EXPLORATORY_NAME,
+                        CloudProvider.GENERAL,
+                        IMAGE_FULL_NAME,
+                        ImageStatus.CREATED,
+                        "private")
         );
     }
 }
\ No newline at end of file
diff --git a/services/self-service/src/test/java/com/epam/datalab/backendapi/service/impl/ImageExploratoryServiceImplTest.java b/services/self-service/src/test/java/com/epam/datalab/backendapi/service/impl/ImageExploratoryServiceImplTest.java
index 78eb81f55..1af955aa8 100644
--- a/services/self-service/src/test/java/com/epam/datalab/backendapi/service/impl/ImageExploratoryServiceImplTest.java
+++ b/services/self-service/src/test/java/com/epam/datalab/backendapi/service/impl/ImageExploratoryServiceImplTest.java
@@ -52,6 +52,7 @@ import org.mockito.InjectMocks;
 import org.mockito.Mock;
 import org.mockito.runners.MockitoJUnitRunner;
 
+import java.time.LocalDateTime;
 import java.util.Collections;
 import java.util.List;
 import java.util.Optional;
@@ -308,8 +309,8 @@ public class ImageExploratoryServiceImplTest {
     }
 
     private ImageInfoRecord getImageInfoRecord() {
-        return new ImageInfoRecord("someName", "someDescription", "someProject", "someEndpoint", "someUser", "someApp",
-                "someFullName", ImageStatus.CREATED);
+        return new ImageInfoRecord("someName", "2020-02-02","someDescription", "someProject", "someEndpoint", "someUser", "someApp",
+                "someInstance",CloudProvider.GENERAL,"someFullName", ImageStatus.CREATED, "private");
     }
 
     private Image fetchImage() {


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@datalab.apache.org
For additional commands, e-mail: commits-help@datalab.apache.org