diff --git a/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/service/NodesService.java b/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/service/NodesService.java
index 2edbb77063bbe38af44bdf6a4b6d5006af615d79..04beff899a6c774dffaceb72a6af006a9cfdc614 100644
--- a/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/service/NodesService.java
+++ b/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/service/NodesService.java
@@ -32,7 +32,7 @@ public class NodesService {
             if (node instanceof ContainerNode) {
                 ContainerNode folder = (ContainerNode) node;
                 sw.write("<tbody id=\"nodes\">");
-                for (Node child : folder.getNodes().getNode()) {
+                for (Node child : folder.getNodes()) {
                     sw.write(getNodeHtml(child));
                 }
                 sw.write("</tbody>");
diff --git a/vospace-ui-frontend/.env.development b/vospace-ui-frontend/.env.development
new file mode 100644
index 0000000000000000000000000000000000000000..b4b1787f8fc876c82213c6344e414c0329d867d2
--- /dev/null
+++ b/vospace-ui-frontend/.env.development
@@ -0,0 +1,2 @@
+VUE_APP_API_CLIENT = 'mock'
+VUE_APP_API_BASE_URL = 'http://localhost:8085/'
diff --git a/vospace-ui-frontend/package-lock.json b/vospace-ui-frontend/package-lock.json
index 80ecd14f2baab7fa25a6b2b9535978bf8550446d..94330cbb4d77b06fe24c042adb03efca63a36f5e 100644
--- a/vospace-ui-frontend/package-lock.json
+++ b/vospace-ui-frontend/package-lock.json
@@ -9039,6 +9039,40 @@
         "unpipe": "1.0.0"
       }
     },
+    "raw-loader": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-4.0.2.tgz",
+      "integrity": "sha512-ZnScIV3ag9A4wPX/ZayxL/jZH+euYb6FcUinPcgiQW0+UBtEv0O6Q3lGd3cqJ+GHH+rksEv3Pj99oxJ3u3VIKA==",
+      "dev": true,
+      "requires": {
+        "loader-utils": "^2.0.0",
+        "schema-utils": "^3.0.0"
+      },
+      "dependencies": {
+        "loader-utils": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
+          "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
+          "dev": true,
+          "requires": {
+            "big.js": "^5.2.2",
+            "emojis-list": "^3.0.0",
+            "json5": "^2.1.2"
+          }
+        },
+        "schema-utils": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz",
+          "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==",
+          "dev": true,
+          "requires": {
+            "@types/json-schema": "^7.0.6",
+            "ajv": "^6.12.5",
+            "ajv-keywords": "^3.5.2"
+          }
+        }
+      }
+    },
     "read-pkg": {
       "version": "5.2.0",
       "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz",
diff --git a/vospace-ui-frontend/package.json b/vospace-ui-frontend/package.json
index c452bb8b40c5640b7f051e973b68f50f1bb4a4cf..d171beb47fbc164c5429949e64f3b3714a206123 100644
--- a/vospace-ui-frontend/package.json
+++ b/vospace-ui-frontend/package.json
@@ -22,6 +22,7 @@
     "babel-eslint": "^10.1.0",
     "eslint": "^6.7.2",
     "eslint-plugin-vue": "^6.2.2",
+    "raw-loader": "^4.0.2",
     "vue-template-compiler": "^2.6.11"
   },
   "eslintConfig": {
diff --git a/vospace-ui-frontend/src/App.vue b/vospace-ui-frontend/src/App.vue
index b9a76bea0c22ec31f09d35f0f43135b2eea6c5b3..1aced9b4e5ba226a94d09686a6d0efa0fa6069cc 100644
--- a/vospace-ui-frontend/src/App.vue
+++ b/vospace-ui-frontend/src/App.vue
@@ -28,19 +28,6 @@ export default {
   }),
   components: {
     TopMenu
-  },
-  mounted: function() {
-    var self = this;
-    document.addEventListener('apiError', function(event) {
-      self.$bvToast.toast(event.message.body, {
-        title: event.message.title,
-        variant: 'danger',
-        solid: true
-      });
-    });
-    document.addEventListener('loading', function(event) {
-      self.$store.commit('setLoading', event.value);
-    });
   }
 }
 </script>
@@ -50,7 +37,6 @@ export default {
   font-family: Avenir, Helvetica, Arial, sans-serif;
   -webkit-font-smoothing: antialiased;
   -moz-osx-font-smoothing: grayscale;
-  text-align: center;
   color: #2c3e50;
 }
 
diff --git a/vospace-ui-frontend/src/api/mock/data/job.json b/vospace-ui-frontend/src/api/mock/data/job.json
new file mode 100644
index 0000000000000000000000000000000000000000..633abc9aaca6432168df723153c54d934763c4f3
--- /dev/null
+++ b/vospace-ui-frontend/src/api/mock/data/job.json
@@ -0,0 +1,4 @@
+{
+  "id": "1234",
+  "status": "EXECUTING"
+}
diff --git a/vospace-ui-frontend/src/api/mock/data/nodes/folder1.html b/vospace-ui-frontend/src/api/mock/data/nodes/folder1.html
new file mode 100644
index 0000000000000000000000000000000000000000..ee2b379d89422fb314d982e9994b2cbf500cb2fb
--- /dev/null
+++ b/vospace-ui-frontend/src/api/mock/data/nodes/folder1.html
@@ -0,0 +1,32 @@
+<tbody id="nodes">
+  <tr>
+    <td><input type="checkbox" class="tape" data-node="/folder1/folder2" /></td>
+    <td>
+      <span class="icon folder-x-icon"></span>
+      <a href="#/nodes/folder1/folder2">folder2</a>
+    </td>
+    <td>0 B</td>
+    <td>group1</td>
+    <td>group2</td>
+  </tr>
+  <tr>
+    <td><input type="checkbox" data-node="/folder1/file2" /></td>
+    <td>
+      <span class="icon file-icon"></span>
+      <a href="download/file2">file2</a>
+    </td>
+    <td>30 KB</td>
+    <td>group1</td>
+    <td>group2</td>
+  </tr>
+  <tr>
+    <td><input type="checkbox" class="tape" data-node="/folder1/file3" /></td>
+    <td>
+      <span class="icon file-x-icon"></span>
+      file3
+    </td>
+    <td>12 MB</td>
+    <td>group3</td>
+    <td>group4</td>
+  </tr>
+</tbody>
diff --git a/vospace-ui-frontend/src/api/mock/data/nodes/folder2.html b/vospace-ui-frontend/src/api/mock/data/nodes/folder2.html
new file mode 100644
index 0000000000000000000000000000000000000000..b861b45707302c0210ff5dc45b595d7922f8ee36
--- /dev/null
+++ b/vospace-ui-frontend/src/api/mock/data/nodes/folder2.html
@@ -0,0 +1,22 @@
+<tbody id="nodes">
+  <tr>
+    <td><input type="checkbox" class="tape" data-node="/folder1/folder2/file4" /></td>
+    <td>
+      <span class="icon file-x-icon"></span>
+      file4
+    </td>
+    <td>10 KB</td>
+    <td>group1</td>
+    <td>group2</td>
+  </tr>
+  <tr>
+    <td><input type="checkbox" class="tape" data-node="/folder1/folder2/file5" /></td>
+    <td>
+      <span class="icon file-x-icon"></span>
+      file5
+    </td>
+    <td>15 MB</td>
+    <td>group3</td>
+    <td>group4</td>
+  </tr>
+</tbody>
diff --git a/vospace-ui-frontend/src/api/mock/data/nodes/root.html b/vospace-ui-frontend/src/api/mock/data/nodes/root.html
new file mode 100644
index 0000000000000000000000000000000000000000..f26856fff011b92a62a52417370a788e598e3d24
--- /dev/null
+++ b/vospace-ui-frontend/src/api/mock/data/nodes/root.html
@@ -0,0 +1,22 @@
+<tbody id="nodes">
+  <tr>
+    <td><input type="checkbox" data-node="/folder1" /></td>
+    <td>
+      <span class="icon folder-icon"></span>
+      <a href="#/nodes/folder1">folder1</a>
+    </td>
+    <td>0 B</td>
+    <td>group1</td>
+    <td>group2</td>
+  </tr>
+  <tr>
+    <td><input type="checkbox" data-node="/file1" /></td>
+    <td>
+      <span class="icon file-icon"></span>
+      <a href="download/file1">file1</a>
+    </td>
+    <td>12 MB</td>
+    <td>group1</td>
+    <td>group2</td>
+  </tr>
+</tbody>
diff --git a/vospace-ui-frontend/src/api/mock/index.js b/vospace-ui-frontend/src/api/mock/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..45c0226b944d9a2062a5c3c22511da896bbb4992
--- /dev/null
+++ b/vospace-ui-frontend/src/api/mock/index.js
@@ -0,0 +1,40 @@
+import root from 'raw-loader!./data/nodes/root.html';
+import folder1 from 'raw-loader!./data/nodes/folder1.html';
+import folder2 from 'raw-loader!./data/nodes/folder2.html';
+import job from './data/job';
+import store from '../../store';
+
+const fetch = (mockData, showLoading = true, time = 500) => {
+  return new Promise((resolve) => {
+    if (showLoading) {
+      store.commit('setLoading', true);
+    }
+    setTimeout(() => {
+      resolve(mockData)
+      if (showLoading) {
+        store.commit('setLoading', false);
+      }
+    }, time);
+  });
+};
+
+export default {
+  getNode(path) {
+    let response;
+    switch (path) {
+      case '':
+        response = root;
+        break;
+      case 'folder1':
+        response = folder1;
+        break;
+      case 'folder1/folder2':
+        response = folder2;
+        break;
+    }
+    return fetch(response);
+  },
+  startRecallFromTapeJob() {
+    return fetch(job);
+  }
+}
diff --git a/vospace-ui-frontend/src/api/server/index.js b/vospace-ui-frontend/src/api/server/index.js
index a86c032ddd5f00dc7bc2d10b2afb7d44647df831..6162815c32fb0916150088678cf2ca694ff4906b 100644
--- a/vospace-ui-frontend/src/api/server/index.js
+++ b/vospace-ui-frontend/src/api/server/index.js
@@ -1,10 +1,12 @@
 const BASE_API_URL = process.env.VUE_APP_API_BASE_URL;
 
 import axios from 'axios';
+import store from '../../store';
+import main from '../../main';
 
 function apiRequest(options, showLoading = true, handleValidationErrors = false) {
   if (showLoading) {
-    loading(true);
+    store.commit('setLoading', true);
   }
   return new Promise((resolve, reject) => {
     axios(options)
@@ -14,41 +16,27 @@ function apiRequest(options, showLoading = true, handleValidationErrors = false)
         } else {
           resolve(response.data);
         }
-        loading(false);
+        store.commit('setLoading', false);
       })
       .catch(error => {
-        loading(false);
+        store.commit('setLoading', false);
         if (handleValidationErrors && error.response && error.response.status === 400) {
           reject(error.response.data);
         } else {
-          dispatchApiErrorEvent(error);
+          main.showError(getErrorMessage(error));
         }
       });
   });
 }
 
-function dispatchApiErrorEvent(error) {
-  let event = new CustomEvent('apiError');
-  let errorMessage;
+function getErrorMessage(error) {
   if (error.response && error.response.data && error.response.data.message) {
-    errorMessage = error.response.data.message;
+    return error.response.data.message;
   } else if (error.message) {
-    errorMessage = error.message;
+    return error.message;
   } else {
-    errorMessage = 'Unknown error';
+    return 'Unknown error';
   }
-  event.message = {
-    title: error.error || 'Error',
-    body: errorMessage
-  };
-  document.dispatchEvent(event);
-}
-
-/* For loading animation */
-function loading(value) {
-  let event = new CustomEvent('loading');
-  event.value = value;
-  document.dispatchEvent(event);
 }
 
 export default {
diff --git a/vospace-ui-frontend/src/assets/css/fonts.css b/vospace-ui-frontend/src/assets/css/fonts.css
index c59766b1687a6a4eab3c1b54de0f9968aa116578..06fbcfffbe7b17491b61ab90e1248f2615f81756 100644
--- a/vospace-ui-frontend/src/assets/css/fonts.css
+++ b/vospace-ui-frontend/src/assets/css/fonts.css
@@ -9,6 +9,14 @@
   background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 16 16' width='1em' height='1em' focusable='false' role='img' aria-label='folder' xmlns='http://www.w3.org/2000/svg' fill='currentColor' %3E%3Cg%3E%3Cpath fill-rule='evenodd' d='M1 3.5A1.5 1.5 0 0 1 2.5 2h2.764c.958 0 1.76.56 2.311 1.184C7.985 3.648 8.48 4 9 4h4.5A1.5 1.5 0 0 1 15 5.5v.64c.57.265.94.876.856 1.546l-.64 5.124A2.5 2.5 0 0 1 12.733 15H3.266a2.5 2.5 0 0 1-2.481-2.19l-.64-5.124A1.5 1.5 0 0 1 1 6.14V3.5zM2 6h12v-.5a.5.5 0 0 0-.5-.5H9c-.964 0-1.71-.629-2.174-1.154C6.374 3.334 5.82 3 5.264 3H2.5a.5.5 0 0 0-.5.5V6zm-.367 1a.5.5 0 0 0-.496.562l.64 5.124A1.5 1.5 0 0 0 3.266 14h9.468a1.5 1.5 0 0 0 1.489-1.314l.64-5.124A.5.5 0 0 0 14.367 7H1.633z'%3E%3C/path%3E%3C/g%3E%3C/svg%3E");
 }
 
+.folder-x-icon {
+  background-image: url("data:image/svg+xml,%3Csvg data-v-41be6633='' viewBox='0 0 16 16' width='1em' height='1em' focusable='false' role='img' aria-label='folder x' xmlns='http://www.w3.org/2000/svg' fill='currentColor' class='bi-folder-x mx-auto b-icon bi'%3E%3Cg data-v-41be6633=''%3E%3Cpath fill-rule='evenodd' d='M9.828 4H2.19a1 1 0 0 0-.996 1.09l.637 7a1 1 0 0 0 .995.91H9v1H2.826a2 2 0 0 1-1.991-1.819l-.637-7a1.99 1.99 0 0 1 .342-1.31L.5 3a2 2 0 0 1 2-2h3.672a2 2 0 0 1 1.414.586l.828.828A2 2 0 0 0 9.828 3h3.982a2 2 0 0 1 1.992 2.181L15.546 8H14.54l.265-2.91A1 1 0 0 0 13.81 4H9.828zm-2.95-1.707L7.587 3H2.19c-.24 0-.47.042-.684.12L1.5 2.98a1 1 0 0 1 1-.98h3.672a1 1 0 0 1 .707.293z'%3E%3C/path%3E%3Cpath fill-rule='evenodd' d='M11.146 10.146a.5.5 0 0 1 .708 0L13 11.293l1.146-1.147a.5.5 0 0 1 .708.708L13.707 12l1.147 1.146a.5.5 0 0 1-.708.708L13 12.707l-1.146 1.147a.5.5 0 0 1-.708-.708L12.293 12l-1.147-1.146a.5.5 0 0 1 0-.708z'%3E%3C/path%3E%3C/g%3E%3C/svg%3E");
+}
+
 .file-icon {
   background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 16 16' width='1em' height='1em' focusable='false' role='img' aria-label='file earmark' xmlns='http://www.w3.org/2000/svg' fill='currentColor' class='bi-file-earmark b-icon bi'%3E%3Cg%3E%3Cpath d='M4 0h5.5v1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4.5h1V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2z'%3E%3C/path%3E%3Cpath d='M9.5 3V0L14 4.5h-3A1.5 1.5 0 0 1 9.5 3z'%3E%3C/path%3E%3C/g%3E%3C/svg%3E");
 }
+
+.file-x-icon {
+  background-image: url("data:image/svg+xml,%3Csvg data-v-41be6633='' viewBox='0 0 16 16' width='1em' height='1em' focusable='false' role='img' aria-label='file earmark x' xmlns='http://www.w3.org/2000/svg' fill='currentColor' class='bi-file-earmark-x mx-auto b-icon bi'%3E%3Cg data-v-41be6633=''%3E%3Cpath d='M4 0h5.5v1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4.5h1V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2z'%3E%3C/path%3E%3Cpath d='M9.5 3V0L14 4.5h-3A1.5 1.5 0 0 1 9.5 3z'%3E%3C/path%3E%3Cpath fill-rule='evenodd' d='M6.146 7.146a.5.5 0 0 1 .708 0L8 8.293l1.146-1.147a.5.5 0 1 1 .708.708L8.707 9l1.147 1.146a.5.5 0 0 1-.708.708L8 9.707l-1.146 1.147a.5.5 0 0 1-.708-.708L7.293 9 6.146 7.854a.5.5 0 0 1 0-.708z'%3E%3C/path%3E%3C/g%3E%3C/svg%3E");
+}
diff --git a/vospace-ui-frontend/src/components/Main.vue b/vospace-ui-frontend/src/components/Main.vue
index 5263ff410915dd1e46b859fd7d3859e9612c8e38..99455ce8e06b6278aa070c3346a215bcb068ef22 100644
--- a/vospace-ui-frontend/src/components/Main.vue
+++ b/vospace-ui-frontend/src/components/Main.vue
@@ -1,6 +1,11 @@
 <template>
 <div class="container">
   <b-breadcrumb :items="breadcrumbs"></b-breadcrumb>
+  <div class="mb-3">
+    <b-button variant="success" class="mr-2" :disabled="true">New folder</b-button>
+    <b-button variant="success" class="mr-2" :disabled="true">Upload files</b-button>
+    <b-button variant="primary" class="mr-2" v-if="tapeButtonEnabled" @click="startRecallFromTapeJob">Recall from tape</b-button>
+  </div>
   <b-card>
     <table class="table b-table table-striped table-hover">
       <thead>
@@ -50,6 +55,9 @@ export default {
         }
       }
       return items;
+    },
+    tapeButtonEnabled() {
+      return this.$store.state.tapeButtonEnabled;
     }
   },
   created() {
@@ -69,7 +77,13 @@ export default {
       this.selectInputs(false);
     },
     selectInputs(value) {
-      document.querySelectorAll('#nodes input').forEach(input => input.checked = value);
+      document.querySelectorAll('#nodes input').forEach(input => {
+        input.checked = value;
+        this.$store.dispatch('computeButtonVisibility');
+      });
+    },
+    startRecallFromTapeJob() {
+      this.$store.dispatch('startRecallFromTapeJob');
     }
   }
 }
diff --git a/vospace-ui-frontend/src/main.js b/vospace-ui-frontend/src/main.js
index 9324cf322260b752aa4ad03f43c26bb27a4823f8..857bdf950bd2956263b8a947daf4c8c4727175d8 100644
--- a/vospace-ui-frontend/src/main.js
+++ b/vospace-ui-frontend/src/main.js
@@ -12,8 +12,25 @@ import VueRouter from 'vue-router'
 Vue.use(VueRouter)
 import router from './router.js'
 
-new Vue({
+let vm = new Vue({
   render: h => h(App),
   store,
   router
 }).$mount('#app')
+
+export default {
+  showError(message) {
+    vm.$bvToast.toast(message, {
+      title: 'Error',
+      variant: 'danger',
+      solid: true
+    });
+  },
+  showInfo(message) {
+    vm.$bvToast.toast(message, {
+      title: 'Info',
+      variant: 'info',
+      solid: true
+    });
+  }
+}
diff --git a/vospace-ui-frontend/src/store.js b/vospace-ui-frontend/src/store.js
index 6e4ed2cea1121cd173cfc150622c88d0f9a4e405..f4b4a331d95c9bd753e5b293507ca1538090ccda 100644
--- a/vospace-ui-frontend/src/store.js
+++ b/vospace-ui-frontend/src/store.js
@@ -3,14 +3,15 @@
 import Vue from 'vue';
 import Vuex from 'vuex';
 import client from 'api-client';
+import main from './main';
 
 Vue.use(Vuex);
 
 export default new Vuex.Store({
   state: {
-    //breadcrumbs: [],
     path: '',
-    loading: true
+    loading: true,
+    tapeButtonEnabled: false
   },
   mutations: {
     setLoading(state, loading) {
@@ -21,15 +22,38 @@ export default new Vuex.Store({
         value = '';
       }
       state.path = value;
-      //state.breadcrumbs = value.split('/')
+    },
+    setTapeButtonEnabled(state, value) {
+      state.tapeButtonEnabled = value;
     }
   },
   actions: {
-    setPath({ state, commit }, path) {
+    setPath({ state, commit, dispatch }, path) {
       commit('setPath', path);
       client.getNode(state.path)
         .then(res => {
-          document.getElementById('nodes').outerHTML = res
+          document.getElementById('nodes').outerHTML = res;
+          let checkboxes = document.querySelectorAll('#nodes input[type="checkbox"]');
+          for (let i = 0; i < checkboxes.length; i++) {
+            checkboxes[i].addEventListener('change', function() {
+              dispatch('computeButtonVisibility');
+            });
+          }
+          dispatch('computeButtonVisibility');
+        });
+    },
+    computeButtonVisibility({ commit }) {
+      commit('setTapeButtonEnabled', document.querySelectorAll('#nodes input.tape:checked').length > 0);
+    },
+    startRecallFromTapeJob() {
+      let tapeCheckboxes = document.querySelectorAll('#nodes input.tape:checked');
+      let paths = [];
+      for (let i = 0; i < tapeCheckboxes.length; i++) {
+        paths.push(tapeCheckboxes[i].getAttribute('data-node'));
+      }
+      client.startRecallFromTapeJob(paths)
+        .then(() => {
+          main.showInfo('Job started');
         });
     }
   }