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

hshpak pushed a commit to branch 
feat/DATALAB-2781/share-one-image-with-all-project-users
in repository https://gitbox.apache.org/repos/asf/incubator-datalab.git

commit 70ae3d1f8800b1955aabe2b02db9bd9864137b93
Author: viravit <[email protected]>
AuthorDate: Tue Jun 7 19:15:52 2022 +0200

    [DATALAB-2781] implemented logic by share image to all users
---
 USER_GUIDE.md                                      |  72 +++++++++++++++------
 billing_page.png                                   | Bin 0 -> 40016 bytes
 doc/administration_section.png                     | Bin 0 -> 4551 bytes
 doc/audit_page.png                                 | Bin 100615 -> 260974 bytes
 doc/billing_page.png                               | Bin 58129 -> 40016 bytes
 doc/bucket_button.png                              | Bin 75695 -> 219991 bytes
 doc/configuration_page.png                         | Bin 48255 -> 181994 bytes
 doc/configuration_page1.png                        | Bin 55341 -> 231139 bytes
 doc/configuration_page_prov.png                    | Bin 82155 -> 275773 bytes
 doc/configuration_page_restart.png                 | Bin 83666 -> 307966 bytes
 doc/environment_management.png                     | Bin 70246 -> 226589 bytes
 doc/image_action_menu.png                          | Bin 0 -> 3542 bytes
 doc/images_main.png                                | Bin 0 -> 77357 bytes
 doc/jupyter_kernel.png                             | Bin 191741 -> 130238 bytes
 doc/library_magic_usage.png                        | Bin 0 -> 11245 bytes
 doc/main_page.png                                  | Bin 49382 -> 186860 bytes
 doc/main_page_filter.png                           | Bin 89155 -> 236408 bytes
 doc/upload_or_generate_user_key.png                | Bin 51504 -> 52499 bytes
 doc/user.png                                       | Bin 0 -> 1114 bytes
 doc/user_information.png                           | Bin 0 -> 9318 bytes
 .../src/base/scripts/create_ssh_user.py            |   2 +-
 .../src/general/lib/os/fab.py                      |  31 +++++----
 .../scripts/aws/common_create_notebook_image.py    |   7 +-
 .../terraform/azure/endpoint/main/network.tf       |   1 +
 .../service/impl/ImageExploratoryServiceImpl.java  |   4 +-
 .../src/main/resources/webapp/package-lock.json    |  32 ++++-----
 .../resources/webapp/src/app/app.routing.module.ts |   6 +-
 .../services/applicationServiceFacade.service.ts   |  31 ++++++---
 .../app/core/services/user-images-page.service.ts  |   9 ++-
 .../audit/audit-grid/audit-grid.component.ts       |  42 ++++++------
 .../src/app/resources/images/images.component.html |  35 ++++++----
 .../src/app/resources/images/images.component.scss |  31 +++++++++
 .../src/app/resources/images/images.component.ts   |  46 +++++++++++--
 .../src/app/resources/images/images.config.ts      |   4 ++
 .../src/app/resources/images/images.model.ts       |   8 ++-
 .../src/app/resources/images/images.service.ts     |  11 ++++
 .../resources-grid/resources-grid.component.html   |   4 +-
 .../webapp/src/app/resources/resources.module.ts   |   6 +-
 .../confirmation-dialog.component.scss             |  36 +++++------
 .../share-image/share-image.component.html         |  39 +++++++++++
 .../share-image/share-image.component.scss         |  40 ++++++++++++
 .../share-image/share-image.component.ts           |  60 +++++++++++++++++
 .../share-image/share-image.module.ts}             |  37 ++++-------
 .../src/app/shared/navbar/navbar.component.html    |   6 +-
 .../webapp/src/app/shared/time-picker/index.ts     |   2 +-
 .../src/app/shared/time-picker/ticker.component.ts |   8 +--
 46 files changed, 449 insertions(+), 161 deletions(-)

diff --git a/USER_GUIDE.md b/USER_GUIDE.md
index 5f558e9c6..595e358ee 100644
--- a/USER_GUIDE.md
+++ b/USER_GUIDE.md
@@ -68,21 +68,24 @@ As soon as DataLab is deployed by an infrastructure 
provisioning team and you re
 
 DataLab Web Application authenticates users against:
 
--   OpenLdap;
--   Cloud Identity and Access Management service user validation;
--   KeyCloak integration for seamless SSO experience *;
+As soon as DataLab is deployed by an infrastructure provisioning team and you 
received DataLab URL, your username and password – open DataLab login page, 
fill in your credentials and hit Login.
+
+DataLab Web Application authenticates users against:
+
+-   KeyCloak integration for seamless SSO experience;
 
     * NOTE: in case has been installed and configured to use SSO, please click 
on "Login with SSO" and use your corporate credentials
 
 | Login error messages               | Reason                                  
                                         |
 
|------------------------------------|----------------------------------------------------------------------------------|
-| Username or password is invalid |The username provided:<br>doesn’t match any 
LDAP user OR<br>there is a type in the password field |
-| Please contact AWS administrator to create corresponding IAM User | The user 
name provided:<br>exists in LDAP BUT:<br>doesn’t match any of IAM users in AWS |
-| Please contact AWS administrator to activate your Access Key      | The 
username provided:<br>exists in LDAP BUT:<br>IAM user doesn’t have a single 
Access Key\* created OR<br>IAM user’s Access Key is Inactive |
+| Invalid username or password. |The username provided: doesn’t match any LDAP 
user OR there is a type in the password field |
+
 
-\* Please refer to official documentation from Amazon to figure out how to 
manage Access Keys for your AWS Account: 
http://docs.aws.amazon.com/general/latest/gr/managing-aws-access-keys.html
+To stop working with DataLab - click on user icon <img src="doc/user.png" 
alt="user" width="20"> at the top right corner of DataLab and hit "Log out from 
account" button:
 
-To stop working with DataLab - click on Log Out link at the top right corner 
of DataLab.
+<p align="center" class="facebox-popup"> 
+    <img src="doc/user_information.png" alt="User information" width="300">
+</p>
 
 After login user sees warning in case of exceeding quota or close to this 
limit.
 
@@ -107,9 +110,9 @@ To do this click on “Upload” button on “Projects” page, 
select your pers
 
 Please note, that you need to have a key pair combination (public and private 
key) to work with DataLab. To figure out how to create public and private key, 
please click on “Where can I get public key?” on “Projects” page. DataLab 
build-in wiki page guides Windows, MasOS and Linux on how to generate SSH key 
pairs quickly.
 
-Creation of Project starts after hitting "Create" button. This process is a 
one-time operation for each Data Scientist and it might take up-to 10 minutes 
for DataLab to setup initial infrastructure for you. During this process 
project is in status "Creating".
+Creation of Project starts after hitting "Create" button. This process is a 
one-time operation for each Data Scientist and it might take up-to 25 minutes 
for DataLab to setup initial infrastructure for you. During this process 
project is in status "Creating".
 
-As soon as Project is created, Data Scientist can create  notebook server on 
“Resources” page. The message “To start working, please create new environment” 
is appeared on “Resources” page:
+As soon as Project is created, Data Scientist can create  notebook server in 
“Resources” section on "Instances" page . The message “To start working, please 
create new environment” is appeared on “Instances” page:
 
 ![Main page](doc/main_page.png)
 
@@ -119,7 +122,7 @@ As soon as Project is created, Data Scientist can create  
notebook server on “
 
 ## Create notebook server <a name="notebook_create"></a>
 
-To create new analytical environment from “Resources” page click on "Create 
new" button.
+To create new analytical environment from “Instances” page click on "Create 
new" button.
 
 The "Create analytical tool" popup shows up. Data Scientist can choose the 
preferred project, endpoint and analytical tool. Adding new analytical toolset 
is supported by architecture, so you can expect new templates to show up in 
upcoming releases.
 Currently by means of DataLab, Data Scientists can select between any of the 
following templates:
@@ -139,7 +142,7 @@ Currently by means of DataLab, Data Scientists can select 
between any of the fol
 
 After specifying desired template, you should fill in the “Name” and “Instance 
shape”.
 
-Keep in mind that "Name" field – is just for visual differentiation between 
analytical tools on “resources” dashboard.
+Keep in mind that "Name" field – is just for visual differentiation between 
analytical tools on “Instances” dashboard.
 
 Instance shape dropdown, contains configurable list of shapes, which should be 
chosen depending on the type of analytical work to be performed. Following 
groups of instance shapes are showing up with default setup configuration:
 
@@ -199,7 +202,7 @@ On every analytical tool instance you can install 
additional libraries by clicki
 
 After clicking you see the window with 4 fields:
 -   Field for selecting an active resource to install libraries
--   Field for selecting group of packages (apt/yum, Python 2, Python 3, R, 
Java, Others)
+-   Field for selecting group of packages (apt/yum, Python 3, R, Java, Others)
 -   Field for search available packages with autocomplete feature (if it's 
gained) except Java dependencies. For Java library you should enter using the 
next format: "groupID:artifactID:versionID"
 -   Field for library version. It's an optional field.
 
@@ -211,7 +214,7 @@ You need to wait for a while after resource and group 
choosing till list of all
 
 **Note:** Apt or Yum packages depend on your DataLab OS family.
 
-**Note:** In group Others you can find other Python (2/3) packages, which 
haven't classifiers of version.
+**Note:** In group Others you can find other Python (3) packages, which 
haven't classifiers of version.
 
 After selecting library, you can see it in the midle of the window and can 
delete it from this list before installation.
 
@@ -251,6 +254,18 @@ To create new analytical environment from custom image 
click on "Create new" but
 
 Before clicking "Create" button you should choose the image from "Select AMI" 
and fill in the "Name" and "Instance shape". For Deeplearning notebook on GCP 
there is also a list of predefined images.
 
+In addition, you can view the list of all images which are created by you and 
shared with you on the "Images" page:
+
+![Images](doc/images_main.png)
+
+You can share the image in Created status with all users in the project or 
terminate it if you are image creator/owner. Creator is the user who has 
created the image.
+Owner is the user who is owning the right to manage image and perform Share 
and Terminate actions. The owners are Creator and Admin. 
+To share or terminate image click on a gear icon <img src="doc/gear_icon.png" 
alt="gear" width="20"> in the "Actions" menu for a needed image and hit 
"Terminate" or "Share" button appropriately.
+
+<p align="center"> 
+    <img src="doc/image_action_menu.png" alt="Image action menu" width="150">
+</p>
+
 --------------------------
 ## Stop Notebook server <a name="notebook_stop"></a>
 
@@ -362,9 +377,15 @@ Since Computational resource is up and running - you are 
now able to leverage cl
 
 To do that open any of the analytical tools and select proper 
kernel/interpreter:
 
-**Jupyter** – go to Kernel and choose preferable interpreter between local and 
Computational resource ones. Currently we have added support of Python 2 (only 
for local kernel)/3, Spark, Scala, R in Jupyter.
+**Jupyter** – go to Kernel and choose preferable interpreter between local and 
Computational resource ones. Currently we have added support of Python 3, 
Spark, Scala, R in Jupyter.
+
+![Jupyter](doc/jupyter_kernel.png)
+
+As you know, you can install library thanks to [Manage libraries 
functionality](#manage_libraries), but in addition you are supposed to install 
library via Jupyter cell using the next command (i.e., for Python group):
 
-![Jupiter](doc/jupyter_kernel.png)
+<p align="center" class="facebox-popup"> 
+    <img src="doc/library_magic_usage.png" alt="Library magic usage" 
width="200">
+</p>
 
 **Zeppelin** – go to Interpreter Biding menu and switch between local and 
Computational resource there. Once needed interpreter is selected click on 
"Save".
 
@@ -373,7 +394,6 @@ To do that open any of the analytical tools and select 
proper kernel/interpreter
 Insert following “magics” before blocks of your code to start executing your 
analytical jobs:
 
 -   interpreter\_name.%spark – for Scala and Spark;
--   interpreter\_name.%pyspark – for Python2;
 -   interpreter\_name.%pyspark3 – for Python3;
 -   interpreter\_name.%sparkr – for R;
 
@@ -534,8 +554,8 @@ Also clicking on "Circle" button you can uncommit or revert 
changes.
 
 You are able to access to cloud buckets via DataLab Web UI.
 There are two ways to open bucket browser:
-- clicking on Notebook name on the "List of resources" page, where there is an 
"Open bucket browser" link;
-- clicking on "Bucket browser" bucket on the "List of resources" page.
+- clicking on Notebook name on the "Instances" page, where there is an "Open 
bucket browser" link;
+- clicking on "Bucket browser" bucket on the "Instances" page.
 
 ![Bucket_browser_button](doc/bucket_button.png)
 
@@ -557,6 +577,18 @@ In the bucket browser you are supposed to:
 --------------------------------
 # Administration <a name="administration"></a>
 
+There are four pages in the "Administration" panel:
+
+<p align="center"> 
+    <img src="doc/administration_section.png" alt="Administration section" 
width="150">
+</p>
+
+- "Users" page, where administrator can assign appropriate permisions for 
users;
+- "Projects" page, where administrator can manage a project;
+- "Resources" page, where administrator monitor and manage project resources;
+- "Configuration" page, where administrator can view and change configuration 
files and restart DataLab services.
+
+
 ## Manage roles <a name="manage_roles"></a>
 
 Administrator can choose what instance shape(s), notebook(s) and computational 
resource are supposed to create for certain group(s) or user(s). Administrator 
can also assign administrator per project, who is able to manage roles within 
particular project.
@@ -744,7 +776,7 @@ You are able to view:
 - who did the action
 - what the action was done
 
-Furthermore on the center of header you can choose period of report in 
datepicker.
+Furthermore, on the center of header you can choose period of report in 
datepicker.
 
 ![Audit page](doc/audit_page.png)
 
diff --git a/billing_page.png b/billing_page.png
new file mode 100644
index 000000000..a44567440
Binary files /dev/null and b/billing_page.png differ
diff --git a/doc/administration_section.png b/doc/administration_section.png
new file mode 100644
index 000000000..19e2e88d8
Binary files /dev/null and b/doc/administration_section.png differ
diff --git a/doc/audit_page.png b/doc/audit_page.png
index 62bf041a1..c1bac87ab 100644
Binary files a/doc/audit_page.png and b/doc/audit_page.png differ
diff --git a/doc/billing_page.png b/doc/billing_page.png
index df01a7eb8..a44567440 100644
Binary files a/doc/billing_page.png and b/doc/billing_page.png differ
diff --git a/doc/bucket_button.png b/doc/bucket_button.png
index 21ec452c8..68be640fe 100644
Binary files a/doc/bucket_button.png and b/doc/bucket_button.png differ
diff --git a/doc/configuration_page.png b/doc/configuration_page.png
index 7bb50f749..17c42870b 100644
Binary files a/doc/configuration_page.png and b/doc/configuration_page.png 
differ
diff --git a/doc/configuration_page1.png b/doc/configuration_page1.png
index 5f30d038a..08c97283d 100644
Binary files a/doc/configuration_page1.png and b/doc/configuration_page1.png 
differ
diff --git a/doc/configuration_page_prov.png b/doc/configuration_page_prov.png
index 0faee799b..d9cc361bc 100644
Binary files a/doc/configuration_page_prov.png and 
b/doc/configuration_page_prov.png differ
diff --git a/doc/configuration_page_restart.png 
b/doc/configuration_page_restart.png
index 479e0633a..cd6e015b6 100644
Binary files a/doc/configuration_page_restart.png and 
b/doc/configuration_page_restart.png differ
diff --git a/doc/environment_management.png b/doc/environment_management.png
index 45a8b1336..2d75cd091 100644
Binary files a/doc/environment_management.png and 
b/doc/environment_management.png differ
diff --git a/doc/image_action_menu.png b/doc/image_action_menu.png
new file mode 100644
index 000000000..314de80d6
Binary files /dev/null and b/doc/image_action_menu.png differ
diff --git a/doc/images_main.png b/doc/images_main.png
new file mode 100644
index 000000000..110042921
Binary files /dev/null and b/doc/images_main.png differ
diff --git a/doc/jupyter_kernel.png b/doc/jupyter_kernel.png
index d718824a6..d8c0f873d 100644
Binary files a/doc/jupyter_kernel.png and b/doc/jupyter_kernel.png differ
diff --git a/doc/library_magic_usage.png b/doc/library_magic_usage.png
new file mode 100644
index 000000000..29139265a
Binary files /dev/null and b/doc/library_magic_usage.png differ
diff --git a/doc/main_page.png b/doc/main_page.png
index 3c3d1e8c5..de5f64c8e 100644
Binary files a/doc/main_page.png and b/doc/main_page.png differ
diff --git a/doc/main_page_filter.png b/doc/main_page_filter.png
index bf0dc2335..4ede541ff 100644
Binary files a/doc/main_page_filter.png and b/doc/main_page_filter.png differ
diff --git a/doc/upload_or_generate_user_key.png 
b/doc/upload_or_generate_user_key.png
index b029c2807..b5d50e405 100644
Binary files a/doc/upload_or_generate_user_key.png and 
b/doc/upload_or_generate_user_key.png differ
diff --git a/doc/user.png b/doc/user.png
new file mode 100644
index 000000000..151271730
Binary files /dev/null and b/doc/user.png differ
diff --git a/doc/user_information.png b/doc/user_information.png
new file mode 100644
index 000000000..1e9ce4c75
Binary files /dev/null and b/doc/user_information.png differ
diff --git a/infrastructure-provisioning/src/base/scripts/create_ssh_user.py 
b/infrastructure-provisioning/src/base/scripts/create_ssh_user.py
index 183295cf5..3af70e5a9 100644
--- a/infrastructure-provisioning/src/base/scripts/create_ssh_user.py
+++ b/infrastructure-provisioning/src/base/scripts/create_ssh_user.py
@@ -57,7 +57,7 @@ def ensure_ssh_user(initial_user, os_user, sudo_group):
 if __name__ == "__main__":
     print("Configure connections")
     global conn
-    conn = datalab.fab.init_datalab_connection(args.hostname, 
args.initial_user, args.keyfile)
+    conn = datalab.fab.init_datalab_connection(args.hostname, 
args.initial_user, args.keyfile, args.os_user)
     print("Creating ssh user: {}".format(args.os_user))
     try:
         ensure_ssh_user(args.initial_user, args.os_user, args.sudo_group)
diff --git a/infrastructure-provisioning/src/general/lib/os/fab.py 
b/infrastructure-provisioning/src/general/lib/os/fab.py
index 33f1ef2ae..add3157c3 100644
--- a/infrastructure-provisioning/src/general/lib/os/fab.py
+++ b/infrastructure-provisioning/src/general/lib/os/fab.py
@@ -40,22 +40,27 @@ from patchwork import files
 
 
 # general functions for all resources
-def init_datalab_connection(hostname, username, keyfile):
+def init_datalab_connection(hostname, username, keyfile, reserve_user=''):
     try:
         global conn
-        attempt = 0
-        while attempt < 15:
-            logging.info('connection attempt {}'.format(attempt))
-            conn = Connection(host=hostname, user=username, 
connect_kwargs={'banner_timeout': 200,
-                                                                            
'key_filename': keyfile})
-            conn.config.run.echo = True
-            try:
-                conn.run('hostname')
+        if reserve_user:
+            users = [username, reserve_user]
+        else:
+            users = [username]
+        for user in users:
+            attempt = 0
+            while attempt < 15:
+                logging.info('connection attempt {} with user 
{}'.format(attempt, user))
+                conn = Connection(host=hostname, user=user, 
connect_kwargs={'banner_timeout': 200,
+                                                                               
 'key_filename': keyfile})
                 conn.config.run.echo = True
-                return conn
-            except:
-                attempt += 1
-                time.sleep(10)
+                try:
+                    conn.run('hostname')
+                    conn.config.run.echo = True
+                    return conn
+                except:
+                    attempt += 1
+                    time.sleep(10)
         if attempt == 15:
             logging.info('Unable to establish connection')
             raise Exception
diff --git 
a/infrastructure-provisioning/src/general/scripts/aws/common_create_notebook_image.py
 
b/infrastructure-provisioning/src/general/scripts/aws/common_create_notebook_image.py
index 3c08b5e91..824e6070b 100644
--- 
a/infrastructure-provisioning/src/general/scripts/aws/common_create_notebook_image.py
+++ 
b/infrastructure-provisioning/src/general/scripts/aws/common_create_notebook_image.py
@@ -67,6 +67,11 @@ if __name__ == "__main__":
                                                                       
instance_name=image_conf['instance_name'],
                                                                       
image_name=image_conf['full_image_name'],
                                                                       
tags=json.dumps(image_conf['tags']))
+
+            logging.info("Image id from actions.lib.py {}".format(image_id))
+            if not image_id :
+                raise Exception("Image can not be created from not running 
instances")
+
             logging.info("Image was successfully created. It's name is 
{}".format(image_conf['full_image_name']))
 
             with open("/root/result.json", 'w') as result:
@@ -79,4 +84,4 @@ if __name__ == "__main__":
                 result.write(json.dumps(res))
     except Exception as err:
         datalab.fab.append_result("Failed to create image from notebook", 
str(err))
-        sys.exit(1)
+        sys.exit(1)
\ No newline at end of file
diff --git 
a/infrastructure-provisioning/terraform/azure/endpoint/main/network.tf 
b/infrastructure-provisioning/terraform/azure/endpoint/main/network.tf
index 85c9d73de..5ef29452a 100644
--- a/infrastructure-provisioning/terraform/azure/endpoint/main/network.tf
+++ b/infrastructure-provisioning/terraform/azure/endpoint/main/network.tf
@@ -53,6 +53,7 @@ resource "azurerm_subnet" "endpoint-subnet" {
   resource_group_name  = 
data.azurerm_resource_group.data-endpoint-resource-group.name
   virtual_network_name = 
data.azurerm_virtual_network.data-endpoint-network.name
   address_prefixes     = [var.subnet_cidr]
+  service_endpoints    = ["Microsoft.Storage"]
 }
 
 data "azurerm_subnet" "data-endpoint-subnet" {
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 842e3b7ec..1f1623e3d 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
@@ -268,7 +268,6 @@ public class ImageExploratoryServiceImpl implements 
ImageExploratoryService {
                 .filter(img -> !img.getUser().equals(userInfo.getName()))
                 .filter(img ->
                         UserRoles.checkAccess(userInfo, RoleType.IMAGE, 
img.getFullName(), userInfo.getRoles()))
-                .peek(img -> img.setShared(true))
                 .collect(Collectors.toList());
         log.info("Shared with user {} images : {}", userInfo.getName(), 
sharedImages);
         return sharedImages;
@@ -280,7 +279,6 @@ public class ImageExploratoryServiceImpl implements 
ImageExploratoryService {
                 .filter(img -> !img.getUser().equals(userInfo.getName()))
                 .filter(img -> img.getDockerImage().equals(dockerImage) && 
img.getProject().equals(project) && img.getEndpoint().equals(endpoint))
                 .filter(img -> UserRoles.checkAccess(userInfo, RoleType.IMAGE, 
img.getFullName(), userInfo.getRoles()))
-                .peek(img -> img.setShared(true))
                 .collect(Collectors.toList());
         log.info("Found shared with user {} images {}", userInfo.getName(), 
sharedImages);
         return sharedImages;
@@ -292,7 +290,6 @@ public class ImageExploratoryServiceImpl implements 
ImageExploratoryService {
                 .filter(img -> !img.getUser().equals(userInfo.getName()))
                 .filter(img -> img.getProject().equals(project) )
                 .filter(img -> UserRoles.checkAccess(userInfo, RoleType.IMAGE, 
img.getFullName(), userInfo.getRoles()))
-                .peek(img -> img.setShared(true))
                 .collect(Collectors.toList());
         log.info("Found shared with user {} images {}", userInfo.getName(), 
sharedImages);
         return sharedImages;
@@ -301,6 +298,7 @@ public class ImageExploratoryServiceImpl implements 
ImageExploratoryService {
     private boolean isSharedImage(String imageFullName){
         String anyUser = "$anyuser";
         List<UserRoleDTO> imageRoles = userRoleDAO.findAll().stream()
+                .filter( r -> r.getType().equals(UserRoleDTO.Type.IMAGE))
                 .filter(r -> r.getImages().contains(imageFullName))
                 .filter( r -> (r.getGroups().contains(anyUser) && 
r.getGroups().size() >= 2)
                         || (!r.getGroups().contains(anyUser) && 
!r.getGroups().isEmpty()))
diff --git a/services/self-service/src/main/resources/webapp/package-lock.json 
b/services/self-service/src/main/resources/webapp/package-lock.json
index 2f5426228..305e75aaf 100644
--- a/services/self-service/src/main/resources/webapp/package-lock.json
+++ b/services/self-service/src/main/resources/webapp/package-lock.json
@@ -4450,9 +4450,9 @@
       }
     },
     "node_modules/codelyzer": {
-      "version": "6.0.1",
-      "resolved": "https://registry.npmjs.org/codelyzer/-/codelyzer-6.0.1.tgz";,
-      "integrity": 
"sha512-cOyGQgMdhnRYtW2xrJUNrNYDjEgwQ+BrE2y93Bwz3h4DJ6vJRLfupemU5N3pbYsUlBHJf0u1j1UGk+NLW4d97g==",
+      "version": "6.0.2",
+      "resolved": "https://registry.npmjs.org/codelyzer/-/codelyzer-6.0.2.tgz";,
+      "integrity": 
"sha512-v3+E0Ucu2xWJMOJ2fA/q9pDT/hlxHftHGPUay1/1cTgyPV5JTHFdO9hqo837Sx2s9vKBMTt5gO+lhF95PO6J+g==",
       "dev": true,
       "dependencies": {
         "@angular/compiler": "9.0.0",
@@ -4471,8 +4471,8 @@
         "zone.js": "~0.10.3"
       },
       "peerDependencies": {
-        "@angular/compiler": ">=2.3.1 <12.0.0 || ^11.0.0-next || ^11.1.0-next 
|| ^11.2.0-next",
-        "@angular/core": ">=2.3.1 <12.0.0 || ^11.0.0-next || ^11.1.0-next || 
^11.2.0-next",
+        "@angular/compiler": ">=2.3.1 <13.0.0 || ^12.0.0-next || ^12.1.0-next 
|| ^12.2.0-next",
+        "@angular/core": ">=2.3.1 <13.0.0 || ^12.0.0-next || ^12.1.0-next || 
^12.2.0-next",
         "tslint": "^5.0.0 || ^6.0.0"
       }
     },
@@ -9197,9 +9197,9 @@
       }
     },
     "node_modules/moment": {
-      "version": "2.29.1",
-      "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz";,
-      "integrity": 
"sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==",
+      "version": "2.29.3",
+      "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.3.tgz";,
+      "integrity": 
"sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw==",
       "engines": {
         "node": "*"
       }
@@ -14251,7 +14251,7 @@
     "node_modules/watchpack-chokidar2/node_modules/glob-parent": {
       "version": "3.1.0",
       "resolved": 
"https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz";,
-      "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=",
+      "integrity": 
"sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==",
       "dev": true,
       "optional": true,
       "dependencies": {
@@ -19069,9 +19069,9 @@
       "dev": true
     },
     "codelyzer": {
-      "version": "6.0.1",
-      "resolved": "https://registry.npmjs.org/codelyzer/-/codelyzer-6.0.1.tgz";,
-      "integrity": 
"sha512-cOyGQgMdhnRYtW2xrJUNrNYDjEgwQ+BrE2y93Bwz3h4DJ6vJRLfupemU5N3pbYsUlBHJf0u1j1UGk+NLW4d97g==",
+      "version": "6.0.2",
+      "resolved": "https://registry.npmjs.org/codelyzer/-/codelyzer-6.0.2.tgz";,
+      "integrity": 
"sha512-v3+E0Ucu2xWJMOJ2fA/q9pDT/hlxHftHGPUay1/1cTgyPV5JTHFdO9hqo837Sx2s9vKBMTt5gO+lhF95PO6J+g==",
       "dev": true,
       "requires": {
         "@angular/compiler": "9.0.0",
@@ -22804,9 +22804,9 @@
       }
     },
     "moment": {
-      "version": "2.29.1",
-      "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz";,
-      "integrity": 
"sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ=="
+      "version": "2.29.3",
+      "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.3.tgz";,
+      "integrity": 
"sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw=="
     },
     "moment-timezone": {
       "version": "0.5.31",
@@ -26691,7 +26691,7 @@
         "glob-parent": {
           "version": "3.1.0",
           "resolved": 
"https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz";,
-          "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=",
+          "integrity": 
"sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==",
           "dev": true,
           "optional": true,
           "requires": {
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 0239fc9e8..8cc601782 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
@@ -65,7 +65,7 @@ const routes: Routes = [
         canActivate: [AuthorizationGuard]
       },
       {
-        path: 'billing_report',
+        path: 'billing',
         component: ReportingComponent,
         canActivate: [AuthorizationGuard, CloudProviderGuard]
       },
@@ -79,12 +79,12 @@ const routes: Routes = [
       //   component: OdahuComponent,
       //   canActivate: [AuthorizationGuard, AdminGuard],
       // }, {
-        path: 'roles',
+        path: 'users',
         component: RolesComponent,
         canActivate: [AuthorizationGuard, AdminGuard],
       },
       {
-        path: 'environment_management',
+        path: 'resources',
         component: ManagementComponent,
         canActivate: [AuthorizationGuard, AdminGuard]
       },
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 cd39b0e67..f97ca1da1 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,14 @@
  * 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';
+import { ShareImageAllUsersParams } from '../../resources/images';
 
 // we can now access environment.apiUrl
 const API_URL = environment.apiUrl;
@@ -44,6 +45,7 @@ export class ApplicationServiceFacade {
   private static readonly PROVISIONED_RESOURCES = 'provisioned_resources';
   private static readonly EXPLORATORY_ENVIRONMENT = 'exploratory_environment';
   private static readonly IMAGE = 'image';
+  private static readonly SHARE_ALL = 'share_all';
   private static readonly SCHEDULER = 'scheduler';
   private static readonly TEMPLATES = 'templates';
   private static readonly COMPUTATION_TEMPLATES = 'computation_templates';
@@ -187,6 +189,13 @@ export class ApplicationServiceFacade {
       null);
   }
 
+  buildShareImageAllUsers(params: ShareImageAllUsersParams): Observable<any> {
+    return this.buildRequest(HTTPMethod.POST,
+      this.requestRegistry.Item(ApplicationServiceFacade.SHARE_ALL),
+      params
+      );
+  }
+
   public buildGetTemplatesRequest(params): Observable<any> {
     return this.buildRequest(HTTPMethod.GET,
       this.requestRegistry.Item(ApplicationServiceFacade.TEMPLATES) + params,
@@ -729,8 +738,6 @@ export class ApplicationServiceFacade {
       '/api/infrastructure_templates');
     this.requestRegistry.Add(ApplicationServiceFacade.COMPUTATION_TEMPLATES,
     '/infrastructure_provision/computational_resources');
-    this.requestRegistry.Add(ApplicationServiceFacade.IMAGE,
-      '/api/infrastructure_provision/exploratory_environment/image');
     this.requestRegistry.Add(ApplicationServiceFacade.SCHEDULER,
       '/api/infrastructure_provision/exploratory_environment/scheduler');
 
@@ -742,9 +749,17 @@ export class ApplicationServiceFacade {
     
this.requestRegistry.Add(ApplicationServiceFacade.COMPUTATIONAL_RESOURCES_DATAENGINE,
       '/infrastructure_provision/computational_resources/dataengine'); // 
spark (azure|aws)
 
+
     
this.requestRegistry.Add(ApplicationServiceFacade.COMPUTATIONAL_RESOURCES_TEMLATES,
       '/api/infrastructure_templates/computational_templates');
 
+
+    // Images
+    this.requestRegistry.Add(ApplicationServiceFacade.IMAGE,
+      '/api/infrastructure_provision/exploratory_environment/image');
+    this.requestRegistry.Add(ApplicationServiceFacade.SHARE_ALL,
+      '/api/infrastructure_provision/exploratory_environment/image/share');
+
     // Bucket browser
     this.requestRegistry.Add(ApplicationServiceFacade.BUCKET, '/api/bucket');
 
diff --git 
a/services/self-service/src/main/resources/webapp/src/app/core/services/user-images-page.service.ts
 
b/services/self-service/src/main/resources/webapp/src/app/core/services/user-images-page.service.ts
index 05e9e9077..aca404052 100644
--- 
a/services/self-service/src/main/resources/webapp/src/app/core/services/user-images-page.service.ts
+++ 
b/services/self-service/src/main/resources/webapp/src/app/core/services/user-images-page.service.ts
@@ -23,7 +23,7 @@ import {  catchError } from 'rxjs/operators';
 import {  ErrorUtils } from '../util';
 
 import { ApplicationServiceFacade } from './applicationServiceFacade.service';
-import { ProjectModel } from '../../resources/images';
+import { ProjectModel, ShareImageAllUsersParams } from 
'../../resources/images';
 
 @Injectable()
 export class UserImagesPageService {
@@ -36,4 +36,11 @@ export class UserImagesPageService {
         catchError(ErrorUtils.handleServiceError)
       );
   }
+
+  shareImageAllUsers(params: ShareImageAllUsersParams) {
+    return this.applicationServiceFacade.buildShareImageAllUsers(params)
+      .pipe(
+        catchError(ErrorUtils.handleServiceError)
+      );
+  }
 }
diff --git 
a/services/self-service/src/main/resources/webapp/src/app/reports/audit/audit-grid/audit-grid.component.ts
 
b/services/self-service/src/main/resources/webapp/src/app/reports/audit/audit-grid/audit-grid.component.ts
index 090dcb33e..866077106 100644
--- 
a/services/self-service/src/main/resources/webapp/src/app/reports/audit/audit-grid/audit-grid.component.ts
+++ 
b/services/self-service/src/main/resources/webapp/src/app/reports/audit/audit-grid/audit-grid.component.ts
@@ -126,10 +126,10 @@ export class AuditGridComponent implements OnInit {
 
   public openActionInfo(element: AuditItem): void {
     if (element.type === 'GROUP' && element.info.indexOf('role') !== -1) {
-      this.dialog.open(AuditInfoDialogComponent, 
+      this.dialog.open(AuditInfoDialogComponent,
         { data: { element, dialogSize: 'big' }, panelClass: 'modal-xl-m' });
     } else {
-      this.dialog.open(AuditInfoDialogComponent, 
+      this.dialog.open(AuditInfoDialogComponent,
         { data:  {element, dialogSize: 'small' }, panelClass: 'modal-md' });
     }
   }
@@ -149,17 +149,17 @@ export class AuditGridComponent implements OnInit {
       this.lastItem = this.showItemsPrPage;
     } else if (action === 'previous') {
       this.firstItem = this.firstItem - this.showItemsPrPage;
-      this.lastItem = this.lastItem % this.showItemsPrPage === 0 
-        ? this.lastItem - this.showItemsPrPage 
+      this.lastItem = this.lastItem % this.showItemsPrPage === 0
+        ? this.lastItem - this.showItemsPrPage
         : this.lastItem - (this.lastItem % this.showItemsPrPage);
     } else if (action === 'next') {
       this.firstItem = this.firstItem + this.showItemsPrPage;
-      this.lastItem = (this.lastItem + this.showItemsPrPage) > this.allItems 
-        ? this.allItems 
+      this.lastItem = (this.lastItem + this.showItemsPrPage) > this.allItems
+        ? this.allItems
         : this.lastItem + this.showItemsPrPage;
     } else if (action === 'last') {
-      this.firstItem = this.allItems % this.showItemsPrPage === 0 
-        ? this.allItems - this.showItemsPrPage 
+      this.firstItem = this.allItems % this.showItemsPrPage === 0
+        ? this.allItems - this.showItemsPrPage
         : this.allItems - (this.allItems % this.showItemsPrPage) + 1;
       this.lastItem = this.allItems;
     }
@@ -185,12 +185,12 @@ export class AuditGridComponent implements OnInit {
               <h4 class="modal-title">{{data.element.action | 
convertaction}}</h4>
               <button type="button" class="close" 
(click)="dialogRef.close()">&times;</button>
           </header>
-          <div 
-            mat-dialog-content 
-            class="content audit-info-content" 
+          <div
+            mat-dialog-content
+            class="content audit-info-content"
             [ngClass]="{'pb-40': actionList[0].length > 1}"
           >
-            <mat-list 
+            <mat-list
               *ngIf="actionList[0].length > 1 && data.element.action !== 
'FOLLOW_LINK'
                     || data.element.info.indexOf('Update quota') !== -1;else 
message"
             >
@@ -203,9 +203,9 @@ export class AuditGridComponent implements OnInit {
 
                 <div class="scrolling-content mat-list-wrapper" id="scrolling">
                   <mat-list-item class="list-item" *ngFor="let action of 
actionList">
-                    <div 
-                      *ngIf="(data.element.action === 'upload' && action[0] 
=== 'File(s)') 
-                          || (data.element.action === 'download' && action[0] 
=== 'File(s)');else multiAction" 
+                    <div
+                      *ngIf="(data.element.action === 'upload' && action[0] 
=== 'File(s)')
+                          || (data.element.action === 'download' && action[0] 
=== 'File(s)');else multiAction"
                       class="info-item-title"
                     >
                       File
@@ -213,12 +213,12 @@ export class AuditGridComponent implements OnInit {
                     <ng-template #multiAction>
                        <div class="info-item-title" 
[ngClass]="{'same-column-width': data.dialogSize === 
'small'}">{{action[0]}}</div>
                     </ng-template>
-                    <div 
-                      class="info-item-data" 
-                      [ngClass]="{'same-column-width': data.dialogSize === 
'small'}" 
+                    <div
+                      class="info-item-data"
+                      [ngClass]="{'same-column-width': data.dialogSize === 
'small'}"
                       *ngIf="action[0] === 'File(s)'"
                     >
-                      <div 
+                      <div
                         class="file-description ellipsis"
                         *ngFor="let description of action[1]?.split(',')"
                         [matTooltip]="description"
@@ -228,7 +228,7 @@ export class AuditGridComponent implements OnInit {
                       </div>
                     </div>
                     <div class="info-item-data" 
[ngClass]="{'same-column-width': data.dialogSize === 'small'}" *ngIf="action[0] 
!== 'File(s)'">
-                       <div 
+                       <div
                           *ngFor="let description of action[1]?.split(',')"
                           [matTooltip]="description"
                           class="ellipsis"
@@ -353,4 +353,4 @@ export class AuditInfoDialogComponent {
     }
     this.actionList = data.element.info.split('\n').map(v => 
v.split(':')).filter(v => v[0] !== '');
   }
-}
\ No newline at end of file
+}
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
index 16f7bf728..e239fe1e7 100644
--- 
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
@@ -25,16 +25,18 @@
              [ngClass]="{'disabled-select': !isProjectsMoreThanOne}"
         >
           <mat-form-field>
-            <mat-label>Select project</mat-label>
+            <mat-label>{{placeholder}}</mat-label>
 
             <mat-select
               disableOptionCentering
+              name="project"
+              [(value)]="activeProjectName"
               panelClass="top-select scrolling"
-              [disabled]="!projectList.length"
+              [disabled]="!isProjectsMoreThanOne"
             >
               <mat-option
                 *ngIf="isProjectsMoreThanOne"
-                (click)="onSelectClick('')"
+                (click)="onSelectClick()"
               >
                 Show all
               </mat-option>
@@ -49,7 +51,7 @@
                 Projects list is empty
               </mat-option>
             </mat-select>
-            <button class="caret" [disabled]="false">
+            <button class="caret" [disabled]="!isProjectsMoreThanOne">
               <i class="material-icons">keyboard_arrow_down</i>
             </button>
           </mat-form-field>
@@ -90,7 +92,7 @@
         </div>
       </span>
       <span>
-        <button mat-raised-button class="butt">
+        <button mat-raised-button class="butt" (click)="onRefresh()">
           <i class="material-icons highlight">autorenew</i>
           Refresh
         </button>
@@ -104,7 +106,7 @@
     <ng-container matColumnDef="checkbox">
       <th mat-header-cell *matHeaderCellDef class="image-checkbox--wrapper">
         <div class="header-cell--wrapper">
-          <span>
+          <span  *ngIf="dataSource.length" >
             <datalab-checkbox
               (click)="allCheckboxToggle()"
               [checked]="checkboxSelected"
@@ -147,8 +149,7 @@
         </div>
       </th>
       <td mat-cell *matCellDef="let element">
-        <span class="date-item"> {{element.timestamp | date: 'yyyy-MM-dd'}} 
</span>
-        <span> {{element.timestamp | date: 'HH:mm:ss'}} </span>
+        <span> {{element.timestamp | localDate : 'short'}} </span>
       </td>
     </ng-container>
 
@@ -189,7 +190,7 @@
       </th>
       <td mat-cell *matCellDef="let element">
         <div class="shared-status--wrapper">
-          <span class="shared-status"> {{element.isShader ? 
sharedStatus.shared : sharedStatus.private}} </span>
+          <span class="shared-status"> {{element.shared ? sharedStatus.shared 
: sharedStatus.private}} </span>
           <span class="currency_details" >
                 <i class="material-icons">help_outline</i>
               </span>
@@ -237,16 +238,22 @@
               matTooltip="Unable to terminate notebook because at least one 
compute is in progress"
               matTooltipPosition="above"
             >
-              <div>
+              <button class="action-button__share">
                 <i class="material-icons">phonelink_off</i>
                 <span>Terminate</span>
-              </div>
+              </button>
             </li>
-            <li>
-              <div>
+            <li [matTooltip]="'This image cannot be shared by you. Contact 
Admin user'"
+                matTooltipPosition="above"
+                [matTooltipDisabled]="userName === element.user && 
element.status === 'CREATED'">
+              <button
+                class="action-button__share"
+                (click)="onShare(element)"
+                [disabled]="userName !== element.user || element.status !== 
'CREATED'"
+              >
                 <i class="material-icons">create</i>
                 <span>Share</span>
-              </div>
+              </button>
             </li>
           </ul>
         </bubble-up>
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
index 85487f421..c032060ce 100644
--- 
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
@@ -85,3 +85,34 @@
   background-color: white;
   opacity: 1;
 }
+
+.action-button__share {
+  display: flex;
+  align-items: start;
+  width: 100%;
+  padding: 10px 15px;
+  background-color: transparent;
+  color: rgb(87, 114, 137);
+  border: none;
+  outline: none;
+
+  &:hover {
+    color: #35afd5;
+    cursor: pointer;
+  }
+
+  &:disabled {
+    background-color: rgba(0, 0, 0, 0.12);
+    color: rgba(0, 0, 0, 0.26);
+    cursor: not-allowed;
+    transition: all 0.45s ease-in-out;
+  }
+}
+
+.list-menu {
+  padding: 0 !important;
+}
+
+.list-unstyled > li {
+  margin: 0 !important;
+}
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
index 7e0e449d3..b5dcc7826 100644
--- 
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
@@ -24,7 +24,10 @@ 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, Image_Table_Titles, Shared_Status } from 
'./images.config';
+import { Image_Table_Column_Headers, Image_Table_Titles, Localstorage_Key, 
Shared_Status } from './images.config';
+import { MatDialog } from '@angular/material/dialog';
+import { ShareImageComponent } from 
'../../shared/modal-dialog/share-image/share-image.component';
+import { ImagesService } from './images.service';
 
 @Component({
   selector: 'datalab-images',
@@ -44,18 +47,26 @@ export class ImagesComponent implements OnInit {
   dataSource: ImageModel[] = [];
   checkboxSelected: boolean = false;
   projectList: string[] = [];
+  activeProjectName: string = '';
+  userName!: string;
+
+  readonly placeholder: string = 'Select project';
   readonly sharedStatus: typeof Shared_Status = Shared_Status;
+
   private cashedImageListData: ProjectModel[] = [];
 
   constructor(
     private healthStatusService: HealthStatusService,
     public toastr: ToastrService,
-    private userImagesPageService: UserImagesPageService
+    private userImagesPageService: UserImagesPageService,
+    private dialog: MatDialog,
+    private imagesService: ImagesService
   ) { }
 
   ngOnInit(): void {
     this.getEnvironmentHealthStatus();
     this.getUserImagePageInfo();
+    this.getUserName();
   }
 
   onCheckboxClick(element: ImageModel): void {
@@ -76,12 +87,28 @@ export class ImagesComponent implements OnInit {
     this.isActionsOpen = !this.isActionsOpen;
   }
 
-  onSelectClick(projectName: string): void {
+  onSelectClick(projectName: string = ''): void {
     if (!projectName) {
       this.dataSource = this.getImageList();
+      return;
     }
-    const { images } = this.cashedImageListData.find(({project}) => project 
=== projectName);
-    this.dataSource = [...images];
+    const currentProject = this.cashedImageListData.find(({project}) => 
project === projectName);
+    this.dataSource = [...currentProject.images];
+    this.activeProjectName = currentProject.project;
+  }
+
+  onRefresh(): void {
+    this.getUserImagePageInfo();
+    this.activeProjectName = '';
+  }
+
+  onShare(image: ImageModel): void {
+    this.dialog.open(ShareImageComponent, {
+      data: {
+        image
+      },
+      panelClass: 'modal-sm'
+    }).afterClosed().subscribe(() => 
this.initImageTable(this.imagesService.projectList));
   }
 
   private getImageList(): ImageModel[] {
@@ -105,15 +132,24 @@ export class ImagesComponent implements OnInit {
     this.cashedImageListData = imagePageList;
     this.getProjectList(imagePageList);
     this.dataSource = this.getImageList();
+
+    if (imagePageList.length === 1) {
+      this.activeProjectName = imagePageList[0].project;
+    }
   }
 
   private getProjectList(imagePageList: ProjectModel[]): void {
     if (!imagePageList) {
       return;
     }
+    this.projectList = [];
     imagePageList.forEach(({project}) => this.projectList.push(project));
   }
 
+  private getUserName(): void {
+    this.userName = localStorage.getItem(Localstorage_Key.userName);
+  }
+
   get isProjectsMoreThanOne(): boolean {
     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
index b57fbda4d..e6f4d5ec9 100644
--- 
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
@@ -44,3 +44,7 @@ export const Image_Table_Titles = <const>[
   'instanceName',
   'actions'
 ];
+
+export enum Localstorage_Key {
+  userName = 'user_name'
+}
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
index c1c5bf64d..8c0735950 100644
--- 
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
@@ -13,8 +13,14 @@ export interface ImageModel {
   instanceName: string;
   name: string;
   project: string;
-  isShared: boolean;
+  shared: boolean;
   status: 'created' | 'creating' | 'terminated' | 'terminating' | 'failed';
   user: string;
   isSelected?: boolean;
 }
+
+export interface ShareImageAllUsersParams {
+  imageName: string;
+  projectName: string;
+  endpoint: string;
+}
diff --git 
a/services/self-service/src/main/resources/webapp/src/app/resources/images/images.service.ts
 
b/services/self-service/src/main/resources/webapp/src/app/resources/images/images.service.ts
new file mode 100644
index 000000000..36a9e5963
--- /dev/null
+++ 
b/services/self-service/src/main/resources/webapp/src/app/resources/images/images.service.ts
@@ -0,0 +1,11 @@
+import { Injectable } from '@angular/core';
+import { ProjectModel } from './images.model';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class ImagesService {
+  projectList!: ProjectModel[];
+
+  constructor() { }
+}
diff --git 
a/services/self-service/src/main/resources/webapp/src/app/resources/resources-grid/resources-grid.component.html
 
b/services/self-service/src/main/resources/webapp/src/app/resources/resources-grid/resources-grid.component.html
index 9dcd2e8e8..c80417e07 100644
--- 
a/services/self-service/src/main/resources/webapp/src/app/resources/resources-grid/resources-grid.component.html
+++ 
b/services/self-service/src/main/resources/webapp/src/app/resources/resources-grid/resources-grid.component.html
@@ -232,8 +232,8 @@
                     <li
                       *ngIf="element.status.toLowerCase() === 'stopped' || 
element.status.toLowerCase() === 'stopping'"
                       matTooltip="{{element.edgeNodeStatus !== 'running'
-                        ? 'Unable to run notebook if edge node is not running.'
-                        : 'Unable to run notebook until it will be stopped.'}}"
+                        ? 'Unable to start notebook if edge node is not 
running.'
+                        : 'Unable to start notebook until it will be 
stopped.'}}"
                       matTooltipPosition="above"
                       [matTooltipDisabled]="!isResourcesInProgress(element) && 
element.status.toLowerCase() !== 'stopping'
                         && element.edgeNodeStatus === 'running'"
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 996c347d4..885328fc2 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
@@ -35,6 +35,8 @@ import { ImagesComponent } from './images/images.component';
 import {CheckboxModule} from '../shared/checkbox';
 import {BubbleModule} from '../shared';
 import { CapitalizeFirstLetterPipeModule } from '../core/pipes';
+import { LocalDatePipeModule } from '../core/pipes/local-date-pipe';
+import { ShareImageModule } from 
'../shared/modal-dialog/share-image/share-image.module';
 
 @NgModule({
   imports: [
@@ -49,7 +51,9 @@ import { CapitalizeFirstLetterPipeModule } from 
'../core/pipes';
     BucketBrowserModule,
     CheckboxModule,
     BubbleModule,
-    CapitalizeFirstLetterPipeModule
+    CapitalizeFirstLetterPipeModule,
+    LocalDatePipeModule,
+    ShareImageModule
   ],
   declarations: [
     ResourcesComponent,
diff --git 
a/services/self-service/src/main/resources/webapp/src/app/shared/modal-dialog/confirmation-dialog/confirmation-dialog.component.scss
 
b/services/self-service/src/main/resources/webapp/src/app/shared/modal-dialog/confirmation-dialog/confirmation-dialog.component.scss
index 10228887b..dd85fe64a 100644
--- 
a/services/self-service/src/main/resources/webapp/src/app/shared/modal-dialog/confirmation-dialog/confirmation-dialog.component.scss
+++ 
b/services/self-service/src/main/resources/webapp/src/app/shared/modal-dialog/confirmation-dialog/confirmation-dialog.component.scss
@@ -1,21 +1,21 @@
-/*!
- * 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.
- */
+  /*!
+  * 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.
+  */
 
 .confirmation-dialog {
   .content-box{
diff --git 
a/services/self-service/src/main/resources/webapp/src/app/shared/modal-dialog/share-image/share-image.component.html
 
b/services/self-service/src/main/resources/webapp/src/app/shared/modal-dialog/share-image/share-image.component.html
new file mode 100644
index 000000000..dff6919bf
--- /dev/null
+++ 
b/services/self-service/src/main/resources/webapp/src/app/shared/modal-dialog/share-image/share-image.component.html
@@ -0,0 +1,39 @@
+<!--
+  ~ 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.
+  -->
+
+<div id="dialog-box">
+  <header class="dialog-header">
+    <h4 class="modal-title">Share Image: <span>{{imageName}}</span></h4>
+    <button type="button" class="close" 
(click)="dialogRef.close()">&times;</button>
+  </header>
+  <section class="content">
+    <p class="description">
+      The image will be shared with the rest of Regular Users on the project 
with all the data and code.
+    </p>
+    <p class="question center">
+      Do you want proceed?
+    </p>
+    <div class="text-center m-top-30 m-bott-10">
+      <button type="button" class="butt mat-raised-button" 
(click)="dialogRef.close()">No</button>
+      <button type="button" class="butt butt-success mat-raised-button"
+              (click)="onYesClick()">Yes
+      </button>
+    </div>
+  </section>
+</div>
diff --git 
a/services/self-service/src/main/resources/webapp/src/app/shared/modal-dialog/share-image/share-image.component.scss
 
b/services/self-service/src/main/resources/webapp/src/app/shared/modal-dialog/share-image/share-image.component.scss
new file mode 100644
index 000000000..8f765c333
--- /dev/null
+++ 
b/services/self-service/src/main/resources/webapp/src/app/shared/modal-dialog/share-image/share-image.component.scss
@@ -0,0 +1,40 @@
+/*!
+* 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.
+*/
+
+.content {
+  max-height: 75vh;
+  margin: 0;
+  padding: 20px 50px;
+  font-weight: 600;
+}
+
+.question {
+  color: #718ba6;
+}
+
+.center {
+  text-align: center;
+}
+
+.description {
+  margin-bottom: 20px;
+  color: #35afd5;
+  font-size: 14px;
+  text-align: center;
+}
diff --git 
a/services/self-service/src/main/resources/webapp/src/app/shared/modal-dialog/share-image/share-image.component.ts
 
b/services/self-service/src/main/resources/webapp/src/app/shared/modal-dialog/share-image/share-image.component.ts
new file mode 100644
index 000000000..e034b10ec
--- /dev/null
+++ 
b/services/self-service/src/main/resources/webapp/src/app/shared/modal-dialog/share-image/share-image.component.ts
@@ -0,0 +1,60 @@
+/*
+ * 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, Inject, OnInit } from '@angular/core';
+import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
+import { UserImagesPageService } from '../../../core/services';
+import { ShareImageAllUsersParams } from '../../../resources/images';
+import { ImagesService } from '../../../resources/images/images.service';
+
+@Component({
+  selector: 'datalab-share-image',
+  templateUrl: './share-image.component.html',
+  styleUrls: ['./share-image.component.scss']
+})
+export class ShareImageComponent implements OnInit {
+  imageName!: string;
+
+  constructor(
+    public dialogRef: MatDialogRef<ShareImageComponent>,
+    @Inject(MAT_DIALOG_DATA) public data: any,
+    private userImagesPageService: UserImagesPageService,
+    private imagesService: ImagesService
+  ) { }
+
+  ngOnInit(): void {
+    this.getImageName();
+  }
+
+  getImageName(): void {
+    this.imageName = this.data.image.name;
+  }
+
+  onYesClick(): void {
+    const shareParams: ShareImageAllUsersParams = {
+      imageName: this.data.image.name,
+      projectName: this.data.image.project,
+      endpoint: this.data.image.endpoint
+    };
+
+    this.userImagesPageService.shareImageAllUsers(shareParams)
+      .subscribe(projectList => this.imagesService.projectList = projectList);
+    this.dialogRef.close();
+  }
+}
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/shared/modal-dialog/share-image/share-image.module.ts
similarity index 59%
copy from 
services/self-service/src/main/resources/webapp/src/app/resources/images/images.config.ts
copy to 
services/self-service/src/main/resources/webapp/src/app/shared/modal-dialog/share-image/share-image.module.ts
index b57fbda4d..bd7a99d1d 100644
--- 
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/shared/modal-dialog/share-image/share-image.module.ts
@@ -17,30 +17,17 @@
  * under the License.
  */
 
-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',
-}
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { ShareImageComponent } from './share-image.component';
+import { NotificationDialogComponent } from '../notification-dialog';
 
-export enum Shared_Status {
-  shared = 'Shared',
-  private = 'Private'
-}
 
-export const Image_Table_Titles = <const>[
-  'checkbox',
-  'imageName',
-  'creationDate',
-  'provider',
-  'imageStatus',
-  'sharedStatus',
-  'templateName',
-  'instanceName',
-  'actions'
-];
+
+@NgModule({
+  declarations: [ ShareImageComponent ],
+  imports: [ CommonModule ],
+  entryComponents: [ShareImageComponent],
+  exports: [ ShareImageComponent ]
+})
+export class ShareImageModule { }
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 60e635d99..4ec0819a6 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
@@ -146,7 +146,7 @@
             <a
               *ngIf="healthStatus?.billingEnabled"
               class="sub-nav-item"
-              [routerLink]="['/billing_report']"
+              [routerLink]="['/billing']"
               [routerLinkActive]="['active']"
               [routerLinkActiveOptions]="{exact:true}"
               [style.margin-left.px]="isExpanded ? '30' : '0'"
@@ -161,7 +161,7 @@
             <a
               class="sub-nav-item"
               [style.margin-left.px]="isExpanded ? '30' : '0'"
-              [routerLink]="['/roles']"
+              [routerLink]="['/users']"
               [routerLinkActive]="['active']"
               [routerLinkActiveOptions]="{exact:true}"
             >
@@ -186,7 +186,7 @@
             <a
               class="sub-nav-item"
               [style.margin-left.px]="isExpanded ? '30' : '0'"
-              [routerLink]="['/environment_management']"
+              [routerLink]="['/resources']"
               [routerLinkActive]="['active']"
               [routerLinkActiveOptions]="{exact:true}"
             >
diff --git 
a/services/self-service/src/main/resources/webapp/src/app/shared/time-picker/index.ts
 
b/services/self-service/src/main/resources/webapp/src/app/shared/time-picker/index.ts
index 4133045d1..0fe6caab5 100644
--- 
a/services/self-service/src/main/resources/webapp/src/app/shared/time-picker/index.ts
+++ 
b/services/self-service/src/main/resources/webapp/src/app/shared/time-picker/index.ts
@@ -33,7 +33,7 @@ import {LocalDatePipeModule} from 
'../../core/pipes/local-date-pipe';
 export * from './time-picker.component';
 
 @NgModule({
-    imports: [CommonModule, FormsModule, ReactiveFormsModule, MaterialModule, 
LocalDatePipeModule],
+  imports: [CommonModule, FormsModule, ReactiveFormsModule, MaterialModule, 
LocalDatePipeModule],
   declarations: [TimePickerComponent, TimePickerDialogComponent, 
TimeCoverComponent, TickerComponent],
   entryComponents: [TimePickerDialogComponent],
   exports: [TimePickerComponent]
diff --git 
a/services/self-service/src/main/resources/webapp/src/app/shared/time-picker/ticker.component.ts
 
b/services/self-service/src/main/resources/webapp/src/app/shared/time-picker/ticker.component.ts
index 45931f1df..623fc18ea 100644
--- 
a/services/self-service/src/main/resources/webapp/src/app/shared/time-picker/ticker.component.ts
+++ 
b/services/self-service/src/main/resources/webapp/src/app/shared/time-picker/ticker.component.ts
@@ -38,12 +38,12 @@ export interface TimeFormat {
           <button mat-mini-fab class="ticker-selected"></button>
         </mat-toolbar>
         <div *ngFor="let step of steps; let i = index" 
[class]="getTimeValueClass(step, i)" >
-          <button 
-            mat-mini-fab 
+          <button
+            mat-mini-fab
             [color]="selectedTimePart === step ? color : ''"
             (click)="changeTimeValue(step)"
-          > 
-            {{ step }} 
+          >
+            {{ step }}
           </button>
         </div>
       </div>


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to