From a4fd95a8046f4a86f0f716bab42d1871f4c1c1d6 Mon Sep 17 00:00:00 2001 From: Sonia Zorba <sonia.zorba@inaf.it> Date: Wed, 3 Feb 2021 16:17:14 +0100 Subject: [PATCH] Handled permissions on UI --- .../it/inaf/ia2/vospace/ui/UserFilter.java | 48 ++++++++++++ .../ia2/vospace/ui/VOSpaceUiApplication.java | 8 ++ .../ui/controller/NodesController.java | 16 ++-- .../java/it/inaf/ia2/vospace/ui/data/Job.java | 3 + .../ia2/vospace/ui/data/ListNodeData.java | 23 ++++++ .../inaf/ia2/vospace/ui/service/NodeInfo.java | 51 ++++++++++++- .../ia2/vospace/ui/service/NodesService.java | 76 ++++++++++++++----- .../ui/controller/NodesControllerTest.java | 9 ++- .../src/api/mock/data/nodes/folder1.html | 35 --------- .../src/api/mock/data/nodes/folder1.json | 4 + .../src/api/mock/data/nodes/folder2.html | 24 ------ .../src/api/mock/data/nodes/folder2.json | 4 + .../src/api/mock/data/nodes/root.html | 26 ------- .../src/api/mock/data/nodes/root.json | 4 + vospace-ui-frontend/src/api/mock/index.js | 6 +- vospace-ui-frontend/src/components/Main.vue | 7 +- vospace-ui-frontend/src/store.js | 9 ++- 17 files changed, 222 insertions(+), 131 deletions(-) create mode 100644 vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/UserFilter.java create mode 100644 vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/data/ListNodeData.java delete mode 100644 vospace-ui-frontend/src/api/mock/data/nodes/folder1.html create mode 100644 vospace-ui-frontend/src/api/mock/data/nodes/folder1.json delete mode 100644 vospace-ui-frontend/src/api/mock/data/nodes/folder2.html create mode 100644 vospace-ui-frontend/src/api/mock/data/nodes/folder2.json delete mode 100644 vospace-ui-frontend/src/api/mock/data/nodes/root.html create mode 100644 vospace-ui-frontend/src/api/mock/data/nodes/root.json diff --git a/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/UserFilter.java b/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/UserFilter.java new file mode 100644 index 0000000..40e7293 --- /dev/null +++ b/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/UserFilter.java @@ -0,0 +1,48 @@ +package it.inaf.ia2.vospace.ui; + +import it.inaf.ia2.aa.data.User; +import java.io.IOException; +import java.security.Principal; +import java.util.ArrayList; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import javax.servlet.http.HttpSession; + +/** + * Extracts user from the session and set it as request Principal. + */ +public class UserFilter implements Filter { + + @Override + public void doFilter(ServletRequest req, ServletResponse res, FilterChain fc) throws IOException, ServletException { + HttpServletRequestWrapper requestWithPrincipal = new RequestWithPrincipal((HttpServletRequest) req); + fc.doFilter(requestWithPrincipal, res); + } + + private static class RequestWithPrincipal extends HttpServletRequestWrapper { + + private final User user; + + public RequestWithPrincipal(HttpServletRequest request) { + super(request); + HttpSession session = request.getSession(false); + if (session == null || session.getAttribute("user_data") == null) { + this.user = new User() + .setUserId("anonymous").setUserLabel("Anonymous") + .setGroups(new ArrayList<>()); + } else { + this.user = (User) session.getAttribute("user_data"); + } + } + + @Override + public Principal getUserPrincipal() { + return user; + } + } +} diff --git a/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/VOSpaceUiApplication.java b/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/VOSpaceUiApplication.java index a015518..8d16a8a 100644 --- a/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/VOSpaceUiApplication.java +++ b/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/VOSpaceUiApplication.java @@ -35,6 +35,14 @@ public class VOSpaceUiApplication { return registration; } + @Bean + public FilterRegistrationBean userFilterRegistration() { + FilterRegistrationBean registration = new FilterRegistrationBean(); + registration.setFilter(new UserFilter()); + registration.addUrlPatterns("/*"); + return registration; + } + @Bean public UserManager userManager() { return ServiceLocator.getInstance().getUserManager(); diff --git a/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/controller/NodesController.java b/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/controller/NodesController.java index 73f1299..4e12e12 100644 --- a/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/controller/NodesController.java +++ b/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/controller/NodesController.java @@ -1,6 +1,8 @@ package it.inaf.ia2.vospace.ui.controller; +import it.inaf.ia2.aa.data.User; import it.inaf.ia2.vospace.ui.client.VOSpaceClient; +import it.inaf.ia2.vospace.ui.data.ListNodeData; import it.inaf.ia2.vospace.ui.service.NodesService; import java.util.Map; import javax.servlet.http.HttpServletRequest; @@ -35,20 +37,12 @@ public class NodesController extends BaseController { @Autowired private HttpServletRequest servletRequest; - /** - * This is the only API endpoint that returns HTML code instead of JSON. The - * reason is that JavaScript frameworks are not very efficient in handling - * very long lists and tables, so this part of the code is generated - * server-side. The content type is set to text/plain even if it is an HTML - * fragment to avoid browser parsing issues since it is not a complete HTML - * document. - */ - @GetMapping(value = {"/nodes", "/nodes/**"}, produces = MediaType.TEXT_PLAIN_VALUE) - public String listNodes() throws Exception { + @GetMapping(value = {"/nodes", "/nodes/**"}, produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity<ListNodeData> listNodes(User principal) throws Exception { String path = getPath("/nodes/"); - return nodesService.generateNodesHtml(path); + return ResponseEntity.ok(nodesService.generateNodesHtml(path, principal)); } @DeleteMapping(value = {"/nodes", "/nodes/**"}) diff --git a/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/data/Job.java b/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/data/Job.java index 180fc32..6515a50 100644 --- a/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/data/Job.java +++ b/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/data/Job.java @@ -29,6 +29,9 @@ public class Job { } private String formatCreationTime(XMLGregorianCalendar calendar) { + if (calendar == null) { + return null; + } SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); return sdf.format(calendar.toGregorianCalendar().getTime()); } diff --git a/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/data/ListNodeData.java b/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/data/ListNodeData.java new file mode 100644 index 0000000..2692ef4 --- /dev/null +++ b/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/data/ListNodeData.java @@ -0,0 +1,23 @@ +package it.inaf.ia2.vospace.ui.data; + +public class ListNodeData { + + private String htmlTable; + private boolean writable; + + public String getHtmlTable() { + return htmlTable; + } + + public void setHtmlTable(String htmlTable) { + this.htmlTable = htmlTable; + } + + public boolean isWritable() { + return writable; + } + + public void setWritable(boolean writable) { + this.writable = writable; + } +} diff --git a/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/service/NodeInfo.java b/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/service/NodeInfo.java index 82d66fd..987886a 100644 --- a/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/service/NodeInfo.java +++ b/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/service/NodeInfo.java @@ -1,9 +1,13 @@ package it.inaf.ia2.vospace.ui.service; import it.inaf.ia2.vospace.ui.exception.VOSpaceException; +import it.inaf.oats.vospace.datamodel.NodeProperties; +import java.util.List; import java.util.Optional; +import net.ivoa.xml.vospace.v2.DataNode; import net.ivoa.xml.vospace.v2.Node; import net.ivoa.xml.vospace.v2.Property; +import net.ivoa.xml.vospace.v2.View; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -17,10 +21,13 @@ public class NodeInfo { private final String name; private final String size; private final String type; + private final String creator; private final String groupRead; private final String groupWrite; private final boolean isPublic; private final boolean asyncTrans; + private final boolean busy; + private final boolean listOfFiles; public NodeInfo(Node node, String authority) { this.authority = authority; @@ -28,10 +35,13 @@ public class NodeInfo { this.name = path.substring(path.lastIndexOf("/") + 1); this.size = getSize(node); this.type = node.getType(); + this.creator = getCreator(node); this.groupRead = getGroupRead(node); this.groupWrite = getGroupWrite(node); this.isPublic = isPublic(node); this.asyncTrans = isAsyncTrans(node); + this.busy = isBusy(node); + this.listOfFiles = isListOfFiles(node); } private String getPath(Node node) { @@ -46,22 +56,45 @@ public class NodeInfo { return uri.substring(prefix.length()); } + + private String getCreator(Node node) { + return getProperty(node, NodeProperties.CREATOR_URI).orElse(""); + } private String getGroupRead(Node node) { - return getProperty(node, "ivo://ivoa.net/vospace/core#groupread").orElse(""); + return getProperty(node, NodeProperties.GROUP_READ_URI).orElse(""); } private String getGroupWrite(Node node) { - return getProperty(node, "ivo://ivoa.net/vospace/core#groupwrite").orElse(""); + return getProperty(node, NodeProperties.GROUP_WRITE_URI).orElse(""); } private boolean isPublic(Node node) { - return getProperty(node, "ivo://ivoa.net/vospace/core#ispublic").map(value -> "t".equals(value)).orElse(false); + return getProperty(node, NodeProperties.PUBLIC_READ_URI).map(value -> "t".equals(value)).orElse(false); } private boolean isAsyncTrans(Node node) { return getProperty(node, "urn:async_trans").map(value -> "t".equals(value)).orElse(false); } + + private boolean isBusy(Node node) { + return node instanceof DataNode && ((DataNode) node).isBusy(); + } + + private boolean isListOfFiles(Node node) { + if (node instanceof DataNode) { + DataNode dataNode = (DataNode) node; + List<View> provides = dataNode.getProvides(); + if (provides != null) { + for (View provide : provides) { + if ("urn:list-of-files".equals(provide.getUri())) { + return true; + } + } + } + } + return false; + } private Optional<String> getProperty(Node node, String uri) { if (node.getProperties() != null && node.getProperties() != null) { @@ -122,6 +155,10 @@ public class NodeInfo { return size; } + public String getCreator() { + return creator; + } + public String getGroupRead() { return groupRead; } @@ -137,4 +174,12 @@ public class NodeInfo { public boolean isAsyncTrans() { return asyncTrans; } + + public boolean isBusy() { + return busy; + } + + public boolean isListOfFiles() { + return listOfFiles; + } } 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 5d347db..4c3b4bf 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 @@ -1,9 +1,14 @@ package it.inaf.ia2.vospace.ui.service; +import it.inaf.ia2.aa.data.User; import it.inaf.ia2.vospace.ui.client.VOSpaceClient; +import it.inaf.ia2.vospace.ui.data.ListNodeData; +import it.inaf.oats.vospace.datamodel.NodeUtils; import java.io.IOException; import java.io.StringWriter; import java.io.UncheckedIOException; +import java.util.Arrays; +import java.util.List; import net.ivoa.xml.vospace.v2.ContainerNode; import net.ivoa.xml.vospace.v2.Node; import org.slf4j.Logger; @@ -23,32 +28,38 @@ public class NodesService { @Value("${vospace-authority}") private String authority; - public String generateNodesHtml(String path) { + public ListNodeData generateNodesHtml(String path, User user) { + ListNodeData listNodeData = new ListNodeData(); + Node node = client.getNode(path); + + listNodeData.setWritable(NodeUtils.checkIfWritable(node, user.getName(), user.getGroups())); - try ( StringWriter sw = new StringWriter()) { + try (StringWriter sw = new StringWriter()) { if (node instanceof ContainerNode) { ContainerNode folder = (ContainerNode) node; sw.write("<tbody id=\"nodes\">"); for (Node child : folder.getNodes()) { - sw.write(getNodeHtml(child)); + sw.write(getNodeHtml(child, user)); } sw.write("</tbody>"); } - return sw.toString(); + listNodeData.setHtmlTable(sw.toString()); + + return listNodeData; } catch (IOException ex) { throw new UncheckedIOException(ex); } } - private String getNodeHtml(Node node) { + private String getNodeHtml(Node node, User user) { NodeInfo nodeInfo = new NodeInfo(node, authority); - if (nodeInfo.getName().startsWith(".")) { + if (nodeInfo.isListOfFiles()) { // hidden file return ""; } @@ -59,31 +70,39 @@ public class NodesService { html += "class=\"async\""; } html += "/></td>"; - html += "<td>" + getIcon(nodeInfo) + getLink(nodeInfo) + "</td>"; + html += "<td>" + getIcon(nodeInfo) + getLink(nodeInfo, user) + "</td>"; html += "<td>" + nodeInfo.getSize() + "</td>"; html += "<td>" + nodeInfo.getGroupRead() + "</td>"; html += "<td>" + nodeInfo.getGroupWrite() + "</td>"; - html += "<td><span class=\"icon trash-icon pointer\" onclick=\"deleteNode('" + nodeInfo.getPath() + "')\"></span></td>"; + html += "<td>"; + if (NodeUtils.checkIfWritable(node, user.getName(), user.getGroups())) { + html += "<span class=\"icon trash-icon pointer\" onclick=\"deleteNode('" + nodeInfo.getPath() + "')\"></span>"; + } + html += "</td>"; html += "</tr>"; return html; } private String getIcon(NodeInfo nodeInfo) { String html = "<span class=\"icon "; - if (nodeInfo.isFolder()) { - html += "folder"; + if (nodeInfo.isFile() && nodeInfo.isBusy()) { + html += "gear"; } else { - html += "file"; - } - if (nodeInfo.isAsyncTrans()) { - html += "-x"; + if (nodeInfo.isFolder()) { + html += "folder"; + } else { + html += "file"; + } + if (nodeInfo.isAsyncTrans()) { + html += "-x"; + } } html += "-icon\"></span> "; return html; } - private String getLink(NodeInfo nodeInfo) { - if (isDownloadable(nodeInfo)) { + private String getLink(NodeInfo nodeInfo, User user) { + if (isDownloadable(nodeInfo, user)) { if (nodeInfo.isFolder()) { return "<a href=\"#/nodes" + nodeInfo.getPath() + "\">" + nodeInfo.getName() + "</a>"; } else { @@ -93,14 +112,29 @@ public class NodesService { return nodeInfo.getName(); } - private boolean isDownloadable(NodeInfo nodeInfo) { - if (nodeInfo.isFile() && nodeInfo.isAsyncTrans()) { - return false; + private boolean isDownloadable(NodeInfo nodeInfo, User user) { + if (nodeInfo.isFile()) { + if (nodeInfo.isAsyncTrans() || nodeInfo.isBusy()) { + return false; + } } if (nodeInfo.isPublic()) { return true; } - // TODO: check user group - return true; // temporary always true + + if (nodeInfo.getCreator().equals(user.getName())) { + return true; + } + + if (user.getGroups() != null && !user.getGroups().isEmpty() && !nodeInfo.getGroupRead().isEmpty()) { + List<String> groupRead = Arrays.asList(nodeInfo.getGroupRead().split(" ")); + for (String group : groupRead) { + if (user.getGroups().contains(group)) { + return true; + } + } + } + + return false; } } diff --git a/vospace-ui-backend/src/test/java/it/inaf/ia2/vospace/ui/controller/NodesControllerTest.java b/vospace-ui-backend/src/test/java/it/inaf/ia2/vospace/ui/controller/NodesControllerTest.java index d4291fb..085b3f2 100644 --- a/vospace-ui-backend/src/test/java/it/inaf/ia2/vospace/ui/controller/NodesControllerTest.java +++ b/vospace-ui-backend/src/test/java/it/inaf/ia2/vospace/ui/controller/NodesControllerTest.java @@ -2,6 +2,7 @@ package it.inaf.ia2.vospace.ui.controller; import it.inaf.ia2.vospace.ui.service.NodesService; import org.junit.jupiter.api.Test; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import org.springframework.beans.factory.annotation.Autowired; @@ -21,14 +22,14 @@ public class NodesControllerTest { @Autowired private MockMvc mockMvc; - + @Test public void testListNodesEmpty() throws Exception { mockMvc.perform(get("/nodes")) .andExpect(status().isOk()); - verify(nodesService).generateNodesHtml(eq("/")); + verify(nodesService).generateNodesHtml(eq("/"), any()); } @Test @@ -37,7 +38,7 @@ public class NodesControllerTest { mockMvc.perform(get("/nodes/")) .andExpect(status().isOk()); - verify(nodesService).generateNodesHtml(eq("/")); + verify(nodesService).generateNodesHtml(eq("/"), any()); } @Test @@ -46,6 +47,6 @@ public class NodesControllerTest { mockMvc.perform(get("/nodes/a/b/c")) .andExpect(status().isOk()); - verify(nodesService).generateNodesHtml(eq("/a/b/c")); + verify(nodesService).generateNodesHtml(eq("/a/b/c"), any()); } } diff --git a/vospace-ui-frontend/src/api/mock/data/nodes/folder1.html b/vospace-ui-frontend/src/api/mock/data/nodes/folder1.html deleted file mode 100644 index cacb3e1..0000000 --- a/vospace-ui-frontend/src/api/mock/data/nodes/folder1.html +++ /dev/null @@ -1,35 +0,0 @@ -<tbody id="nodes"> - <tr> - <td><input type="checkbox" class="async" 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> - <td></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> - <td></td> - </tr> - <tr> - <td><input type="checkbox" class="async" 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> - <td></td> - </tr> -</tbody> diff --git a/vospace-ui-frontend/src/api/mock/data/nodes/folder1.json b/vospace-ui-frontend/src/api/mock/data/nodes/folder1.json new file mode 100644 index 0000000..2527b19 --- /dev/null +++ b/vospace-ui-frontend/src/api/mock/data/nodes/folder1.json @@ -0,0 +1,4 @@ +{ + "writable": true, + "htmlTable": "<tbody id=\"nodes\"> <tr> <td><input type=\"checkbox\" class=\"async\" 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> <td></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> <td></td> </tr> <tr> <td><input type=\"checkbox\" class=\"async\" 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> <td></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 deleted file mode 100644 index 2c52dd9..0000000 --- a/vospace-ui-frontend/src/api/mock/data/nodes/folder2.html +++ /dev/null @@ -1,24 +0,0 @@ -<tbody id="nodes"> - <tr> - <td><input type="checkbox" class="async" 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> - <td></td> - </tr> - <tr> - <td><input type="checkbox" class="async" 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> - <td></td> - </tr> -</tbody> diff --git a/vospace-ui-frontend/src/api/mock/data/nodes/folder2.json b/vospace-ui-frontend/src/api/mock/data/nodes/folder2.json new file mode 100644 index 0000000..be52e82 --- /dev/null +++ b/vospace-ui-frontend/src/api/mock/data/nodes/folder2.json @@ -0,0 +1,4 @@ +{ + "writable": false, + "htmlTable": "<tbody id=\"nodes\"> <tr> <td><input type=\"checkbox\" class=\"async\" 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> <td></td> </tr> <tr> <td><input type=\"checkbox\" class=\"async\" 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> <td></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 deleted file mode 100644 index d6143ad..0000000 --- a/vospace-ui-frontend/src/api/mock/data/nodes/root.html +++ /dev/null @@ -1,26 +0,0 @@ -<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> - <td> - <span class="icon trash-icon pointer" onclick="deleteNode('/folder1')"></span> - </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> - <td></td> - </tr> -</tbody> diff --git a/vospace-ui-frontend/src/api/mock/data/nodes/root.json b/vospace-ui-frontend/src/api/mock/data/nodes/root.json new file mode 100644 index 0000000..dcd4c04 --- /dev/null +++ b/vospace-ui-frontend/src/api/mock/data/nodes/root.json @@ -0,0 +1,4 @@ +{ + "writable": true, + "htmlTable": "<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> <td> <span class=\"icon trash-icon pointer\" onclick=\"deleteNode('/folder1')\"></span> </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> <td></td> </tr></tbody>" +} diff --git a/vospace-ui-frontend/src/api/mock/index.js b/vospace-ui-frontend/src/api/mock/index.js index 9de8cec..9924ec9 100644 --- a/vospace-ui-frontend/src/api/mock/index.js +++ b/vospace-ui-frontend/src/api/mock/index.js @@ -1,6 +1,6 @@ -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 root from './data/nodes/root'; +import folder1 from './data/nodes/folder1'; +import folder2 from './data/nodes/folder2'; import job from './data/job'; import jobs from './data/jobs'; import user from './data/user'; diff --git a/vospace-ui-frontend/src/components/Main.vue b/vospace-ui-frontend/src/components/Main.vue index c1ce55f..e9147d7 100644 --- a/vospace-ui-frontend/src/components/Main.vue +++ b/vospace-ui-frontend/src/components/Main.vue @@ -2,8 +2,8 @@ <div class="container"> <b-breadcrumb :items="breadcrumbs"></b-breadcrumb> <div class="mb-3"> - <b-button variant="success" class="mr-2" :disabled="false" v-b-modal.create-folder-modal>New folder</b-button> - <b-button variant="success" class="mr-2" :disabled="false" v-b-modal.upload-files-modal>Upload files</b-button> + <b-button variant="success" class="mr-2" :disabled="!writable" v-b-modal.create-folder-modal>New folder</b-button> + <b-button variant="success" class="mr-2" :disabled="!writable" v-b-modal.upload-files-modal>Upload files</b-button> <b-button variant="primary" class="mr-2" v-if="asyncButtonEnabled" @click="startAsyncRecallJob">Async recall</b-button> </div> <b-card> @@ -68,6 +68,9 @@ export default { }, asyncButtonEnabled() { return this.$store.state.asyncButtonEnabled; + }, + writable() { + return this.$store.state.writable; } }, created() { diff --git a/vospace-ui-frontend/src/store.js b/vospace-ui-frontend/src/store.js index 628d91c..8cd9bd8 100644 --- a/vospace-ui-frontend/src/store.js +++ b/vospace-ui-frontend/src/store.js @@ -14,7 +14,8 @@ export default new Vuex.Store({ asyncButtonEnabled: false, jobs: [], user: 'anonymous', - nodeToDelete: null + nodeToDelete: null, + writable: false }, mutations: { setLoading(state, loading) { @@ -45,6 +46,9 @@ export default new Vuex.Store({ }, setNodeToDelete(state, path) { state.nodeToDelete = path; + }, + setWritable(state, value) { + state.writable = value; } }, actions: { @@ -52,7 +56,8 @@ export default new Vuex.Store({ commit('setPath', path); client.getNode(state.path) .then(res => { - document.getElementById('nodes').outerHTML = res; + commit('setWritable', res.writable); + document.getElementById('nodes').outerHTML = res.htmlTable; let checkboxes = document.querySelectorAll('#nodes input[type="checkbox"]'); for (let i = 0; i < checkboxes.length; i++) { checkboxes[i].addEventListener('change', function() { -- GitLab