Skip to content
Snippets Groups Projects
Commit a4fd95a8 authored by Sonia Zorba's avatar Sonia Zorba
Browse files

Handled permissions on UI

parent 93f1d1f0
No related branches found
No related tags found
No related merge requests found
Pipeline #963 passed
Showing
with 222 additions and 131 deletions
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;
}
}
}
...@@ -35,6 +35,14 @@ public class VOSpaceUiApplication { ...@@ -35,6 +35,14 @@ public class VOSpaceUiApplication {
return registration; return registration;
} }
@Bean
public FilterRegistrationBean userFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new UserFilter());
registration.addUrlPatterns("/*");
return registration;
}
@Bean @Bean
public UserManager userManager() { public UserManager userManager() {
return ServiceLocator.getInstance().getUserManager(); return ServiceLocator.getInstance().getUserManager();
......
package it.inaf.ia2.vospace.ui.controller; 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.client.VOSpaceClient;
import it.inaf.ia2.vospace.ui.data.ListNodeData;
import it.inaf.ia2.vospace.ui.service.NodesService; import it.inaf.ia2.vospace.ui.service.NodesService;
import java.util.Map; import java.util.Map;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
...@@ -35,20 +37,12 @@ public class NodesController extends BaseController { ...@@ -35,20 +37,12 @@ public class NodesController extends BaseController {
@Autowired @Autowired
private HttpServletRequest servletRequest; private HttpServletRequest servletRequest;
/** @GetMapping(value = {"/nodes", "/nodes/**"}, produces = MediaType.APPLICATION_JSON_VALUE)
* This is the only API endpoint that returns HTML code instead of JSON. The public ResponseEntity<ListNodeData> listNodes(User principal) throws Exception {
* 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 {
String path = getPath("/nodes/"); String path = getPath("/nodes/");
return nodesService.generateNodesHtml(path); return ResponseEntity.ok(nodesService.generateNodesHtml(path, principal));
} }
@DeleteMapping(value = {"/nodes", "/nodes/**"}) @DeleteMapping(value = {"/nodes", "/nodes/**"})
......
...@@ -29,6 +29,9 @@ public class Job { ...@@ -29,6 +29,9 @@ public class Job {
} }
private String formatCreationTime(XMLGregorianCalendar calendar) { private String formatCreationTime(XMLGregorianCalendar calendar) {
if (calendar == null) {
return null;
}
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return sdf.format(calendar.toGregorianCalendar().getTime()); return sdf.format(calendar.toGregorianCalendar().getTime());
} }
......
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;
}
}
package it.inaf.ia2.vospace.ui.service; package it.inaf.ia2.vospace.ui.service;
import it.inaf.ia2.vospace.ui.exception.VOSpaceException; import it.inaf.ia2.vospace.ui.exception.VOSpaceException;
import it.inaf.oats.vospace.datamodel.NodeProperties;
import java.util.List;
import java.util.Optional; import java.util.Optional;
import net.ivoa.xml.vospace.v2.DataNode;
import net.ivoa.xml.vospace.v2.Node; import net.ivoa.xml.vospace.v2.Node;
import net.ivoa.xml.vospace.v2.Property; import net.ivoa.xml.vospace.v2.Property;
import net.ivoa.xml.vospace.v2.View;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
...@@ -17,10 +21,13 @@ public class NodeInfo { ...@@ -17,10 +21,13 @@ public class NodeInfo {
private final String name; private final String name;
private final String size; private final String size;
private final String type; private final String type;
private final String creator;
private final String groupRead; private final String groupRead;
private final String groupWrite; private final String groupWrite;
private final boolean isPublic; private final boolean isPublic;
private final boolean asyncTrans; private final boolean asyncTrans;
private final boolean busy;
private final boolean listOfFiles;
public NodeInfo(Node node, String authority) { public NodeInfo(Node node, String authority) {
this.authority = authority; this.authority = authority;
...@@ -28,10 +35,13 @@ public class NodeInfo { ...@@ -28,10 +35,13 @@ public class NodeInfo {
this.name = path.substring(path.lastIndexOf("/") + 1); this.name = path.substring(path.lastIndexOf("/") + 1);
this.size = getSize(node); this.size = getSize(node);
this.type = node.getType(); this.type = node.getType();
this.creator = getCreator(node);
this.groupRead = getGroupRead(node); this.groupRead = getGroupRead(node);
this.groupWrite = getGroupWrite(node); this.groupWrite = getGroupWrite(node);
this.isPublic = isPublic(node); this.isPublic = isPublic(node);
this.asyncTrans = isAsyncTrans(node); this.asyncTrans = isAsyncTrans(node);
this.busy = isBusy(node);
this.listOfFiles = isListOfFiles(node);
} }
private String getPath(Node node) { private String getPath(Node node) {
...@@ -47,22 +57,45 @@ public class NodeInfo { ...@@ -47,22 +57,45 @@ public class NodeInfo {
return uri.substring(prefix.length()); return uri.substring(prefix.length());
} }
private String getCreator(Node node) {
return getProperty(node, NodeProperties.CREATOR_URI).orElse("");
}
private String getGroupRead(Node node) { 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) { 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) { 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) { private boolean isAsyncTrans(Node node) {
return getProperty(node, "urn:async_trans").map(value -> "t".equals(value)).orElse(false); 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) { private Optional<String> getProperty(Node node, String uri) {
if (node.getProperties() != null && node.getProperties() != null) { if (node.getProperties() != null && node.getProperties() != null) {
for (Property property : node.getProperties()) { for (Property property : node.getProperties()) {
...@@ -122,6 +155,10 @@ public class NodeInfo { ...@@ -122,6 +155,10 @@ public class NodeInfo {
return size; return size;
} }
public String getCreator() {
return creator;
}
public String getGroupRead() { public String getGroupRead() {
return groupRead; return groupRead;
} }
...@@ -137,4 +174,12 @@ public class NodeInfo { ...@@ -137,4 +174,12 @@ public class NodeInfo {
public boolean isAsyncTrans() { public boolean isAsyncTrans() {
return asyncTrans; return asyncTrans;
} }
public boolean isBusy() {
return busy;
}
public boolean isListOfFiles() {
return listOfFiles;
}
} }
package it.inaf.ia2.vospace.ui.service; 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.client.VOSpaceClient;
import it.inaf.ia2.vospace.ui.data.ListNodeData;
import it.inaf.oats.vospace.datamodel.NodeUtils;
import java.io.IOException; import java.io.IOException;
import java.io.StringWriter; import java.io.StringWriter;
import java.io.UncheckedIOException; 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.ContainerNode;
import net.ivoa.xml.vospace.v2.Node; import net.ivoa.xml.vospace.v2.Node;
import org.slf4j.Logger; import org.slf4j.Logger;
...@@ -23,32 +28,38 @@ public class NodesService { ...@@ -23,32 +28,38 @@ public class NodesService {
@Value("${vospace-authority}") @Value("${vospace-authority}")
private String 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); 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) { if (node instanceof ContainerNode) {
ContainerNode folder = (ContainerNode) node; ContainerNode folder = (ContainerNode) node;
sw.write("<tbody id=\"nodes\">"); sw.write("<tbody id=\"nodes\">");
for (Node child : folder.getNodes()) { for (Node child : folder.getNodes()) {
sw.write(getNodeHtml(child)); sw.write(getNodeHtml(child, user));
} }
sw.write("</tbody>"); sw.write("</tbody>");
} }
return sw.toString(); listNodeData.setHtmlTable(sw.toString());
return listNodeData;
} catch (IOException ex) { } catch (IOException ex) {
throw new UncheckedIOException(ex); throw new UncheckedIOException(ex);
} }
} }
private String getNodeHtml(Node node) { private String getNodeHtml(Node node, User user) {
NodeInfo nodeInfo = new NodeInfo(node, authority); NodeInfo nodeInfo = new NodeInfo(node, authority);
if (nodeInfo.getName().startsWith(".")) { if (nodeInfo.isListOfFiles()) {
// hidden file // hidden file
return ""; return "";
} }
...@@ -59,17 +70,24 @@ public class NodesService { ...@@ -59,17 +70,24 @@ public class NodesService {
html += "class=\"async\""; html += "class=\"async\"";
} }
html += "/></td>"; 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.getSize() + "</td>";
html += "<td>" + nodeInfo.getGroupRead() + "</td>"; html += "<td>" + nodeInfo.getGroupRead() + "</td>";
html += "<td>" + nodeInfo.getGroupWrite() + "</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>"; html += "</tr>";
return html; return html;
} }
private String getIcon(NodeInfo nodeInfo) { private String getIcon(NodeInfo nodeInfo) {
String html = "<span class=\"icon "; String html = "<span class=\"icon ";
if (nodeInfo.isFile() && nodeInfo.isBusy()) {
html += "gear";
} else {
if (nodeInfo.isFolder()) { if (nodeInfo.isFolder()) {
html += "folder"; html += "folder";
} else { } else {
...@@ -78,12 +96,13 @@ public class NodesService { ...@@ -78,12 +96,13 @@ public class NodesService {
if (nodeInfo.isAsyncTrans()) { if (nodeInfo.isAsyncTrans()) {
html += "-x"; html += "-x";
} }
}
html += "-icon\"></span>&nbsp;"; html += "-icon\"></span>&nbsp;";
return html; return html;
} }
private String getLink(NodeInfo nodeInfo) { private String getLink(NodeInfo nodeInfo, User user) {
if (isDownloadable(nodeInfo)) { if (isDownloadable(nodeInfo, user)) {
if (nodeInfo.isFolder()) { if (nodeInfo.isFolder()) {
return "<a href=\"#/nodes" + nodeInfo.getPath() + "\">" + nodeInfo.getName() + "</a>"; return "<a href=\"#/nodes" + nodeInfo.getPath() + "\">" + nodeInfo.getName() + "</a>";
} else { } else {
...@@ -93,14 +112,29 @@ public class NodesService { ...@@ -93,14 +112,29 @@ public class NodesService {
return nodeInfo.getName(); return nodeInfo.getName();
} }
private boolean isDownloadable(NodeInfo nodeInfo) { private boolean isDownloadable(NodeInfo nodeInfo, User user) {
if (nodeInfo.isFile() && nodeInfo.isAsyncTrans()) { if (nodeInfo.isFile()) {
if (nodeInfo.isAsyncTrans() || nodeInfo.isBusy()) {
return false; return false;
} }
}
if (nodeInfo.isPublic()) { if (nodeInfo.isPublic()) {
return true; 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;
} }
} }
...@@ -2,6 +2,7 @@ package it.inaf.ia2.vospace.ui.controller; ...@@ -2,6 +2,7 @@ package it.inaf.ia2.vospace.ui.controller;
import it.inaf.ia2.vospace.ui.service.NodesService; import it.inaf.ia2.vospace.ui.service.NodesService;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
...@@ -28,7 +29,7 @@ public class NodesControllerTest { ...@@ -28,7 +29,7 @@ public class NodesControllerTest {
mockMvc.perform(get("/nodes")) mockMvc.perform(get("/nodes"))
.andExpect(status().isOk()); .andExpect(status().isOk());
verify(nodesService).generateNodesHtml(eq("/")); verify(nodesService).generateNodesHtml(eq("/"), any());
} }
@Test @Test
...@@ -37,7 +38,7 @@ public class NodesControllerTest { ...@@ -37,7 +38,7 @@ public class NodesControllerTest {
mockMvc.perform(get("/nodes/")) mockMvc.perform(get("/nodes/"))
.andExpect(status().isOk()); .andExpect(status().isOk());
verify(nodesService).generateNodesHtml(eq("/")); verify(nodesService).generateNodesHtml(eq("/"), any());
} }
@Test @Test
...@@ -46,6 +47,6 @@ public class NodesControllerTest { ...@@ -46,6 +47,6 @@ public class NodesControllerTest {
mockMvc.perform(get("/nodes/a/b/c")) mockMvc.perform(get("/nodes/a/b/c"))
.andExpect(status().isOk()); .andExpect(status().isOk());
verify(nodesService).generateNodesHtml(eq("/a/b/c")); verify(nodesService).generateNodesHtml(eq("/a/b/c"), any());
} }
} }
<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>
{
"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>"
}
<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>
{
"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>"
}
<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>
{
"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>"
}
import root from 'raw-loader!./data/nodes/root.html'; import root from './data/nodes/root';
import folder1 from 'raw-loader!./data/nodes/folder1.html'; import folder1 from './data/nodes/folder1';
import folder2 from 'raw-loader!./data/nodes/folder2.html'; import folder2 from './data/nodes/folder2';
import job from './data/job'; import job from './data/job';
import jobs from './data/jobs'; import jobs from './data/jobs';
import user from './data/user'; import user from './data/user';
......
...@@ -2,8 +2,8 @@ ...@@ -2,8 +2,8 @@
<div class="container"> <div class="container">
<b-breadcrumb :items="breadcrumbs"></b-breadcrumb> <b-breadcrumb :items="breadcrumbs"></b-breadcrumb>
<div class="mb-3"> <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="!writable" 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.upload-files-modal>Upload files</b-button>
<b-button variant="primary" class="mr-2" v-if="asyncButtonEnabled" @click="startAsyncRecallJob">Async recall</b-button> <b-button variant="primary" class="mr-2" v-if="asyncButtonEnabled" @click="startAsyncRecallJob">Async recall</b-button>
</div> </div>
<b-card> <b-card>
...@@ -68,6 +68,9 @@ export default { ...@@ -68,6 +68,9 @@ export default {
}, },
asyncButtonEnabled() { asyncButtonEnabled() {
return this.$store.state.asyncButtonEnabled; return this.$store.state.asyncButtonEnabled;
},
writable() {
return this.$store.state.writable;
} }
}, },
created() { created() {
......
...@@ -14,7 +14,8 @@ export default new Vuex.Store({ ...@@ -14,7 +14,8 @@ export default new Vuex.Store({
asyncButtonEnabled: false, asyncButtonEnabled: false,
jobs: [], jobs: [],
user: 'anonymous', user: 'anonymous',
nodeToDelete: null nodeToDelete: null,
writable: false
}, },
mutations: { mutations: {
setLoading(state, loading) { setLoading(state, loading) {
...@@ -45,6 +46,9 @@ export default new Vuex.Store({ ...@@ -45,6 +46,9 @@ export default new Vuex.Store({
}, },
setNodeToDelete(state, path) { setNodeToDelete(state, path) {
state.nodeToDelete = path; state.nodeToDelete = path;
},
setWritable(state, value) {
state.writable = value;
} }
}, },
actions: { actions: {
...@@ -52,7 +56,8 @@ export default new Vuex.Store({ ...@@ -52,7 +56,8 @@ export default new Vuex.Store({
commit('setPath', path); commit('setPath', path);
client.getNode(state.path) client.getNode(state.path)
.then(res => { .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"]'); let checkboxes = document.querySelectorAll('#nodes input[type="checkbox"]');
for (let i = 0; i < checkboxes.length; i++) { for (let i = 0; i < checkboxes.length; i++) {
checkboxes[i].addEventListener('change', function() { checkboxes[i].addEventListener('change', function() {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment