Loading vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/controller/NodesController.java +5 −0 Original line number Diff line number Diff line Loading @@ -29,4 +29,9 @@ public class NodesController { nodesService.generateNodesHtml(path, response.getOutputStream()); } @GetMapping(value = "/download/{path}") public void directDownload(@PathVariable("path") String path) { // TODO: call pullFromVoSpace sync transfer } } vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/service/NodeInfo.java 0 → 100644 +127 −0 Original line number Diff line number Diff line package it.inaf.ia2.vospace.ui.service; import it.inaf.ia2.vospace.ui.VOSpaceException; import java.util.Optional; import net.ivoa.xml.vospace.v2.Node; import net.ivoa.xml.vospace.v2.Property; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class NodeInfo { private static final Logger LOG = LoggerFactory.getLogger(NodeInfo.class); private final String authority; private final Node node; private final String path; private final String name; private final String size; private final String groupRead; private final String groupWrite; private final boolean isPublic; public NodeInfo(Node node, String authority) { this.authority = authority; this.node = node; this.path = getPath(node); this.name = path.substring(path.lastIndexOf("/") + 1); this.size = getSize(node); this.groupRead = getGroupRead(node); this.groupWrite = getGroupWrite(node); this.isPublic = isPublic(node); } private String getPath(Node node) { String uri = node.getUri(); String prefix = "vos://" + authority; if (!uri.startsWith(prefix)) { throw new VOSpaceException("Node authority is different from configured one! Configured is " + authority + ", but node URI is " + uri); } return uri.substring(prefix.length()); } private String getGroupRead(Node node) { return getProperty(node, "ivo://ivoa.net/vospace/core#groupread").orElse(""); } private String getGroupWrite(Node node) { return getProperty(node, "ivo://ivoa.net/vospace/core#groupwrite").orElse(""); } private boolean isPublic(Node node) { return Boolean.parseBoolean(getProperty(node, "ivo://ivoa.net/vospace/core#ispublic").orElse("false")); } private Optional<String> getProperty(Node node, String uri) { if (node.getProperties() != null && node.getProperties().getProperty() != null) { for (Property property : node.getProperties().getProperty()) { if (uri.equals(property.getUri())) { return Optional.of(property.getValue()); } } } return Optional.empty(); } private String getSize(Node node) { return getProperty(node, "ivo://ivoa.net/vospace/core#length") .map(value -> { try { long bytes = Long.parseLong(value); return getHumanReadableSize(bytes); } catch (NumberFormatException ex) { LOG.warn("Invalid length for node " + node.getUri() + ". Length is " + value); return ""; } }) .orElse(""); } /** * Credits: https://stackoverflow.com/a/16576773/771431 */ private String getHumanReadableSize(long bytes) { int u = 0; for (; bytes > 1024 * 1024; bytes >>= 10) { u++; } if (bytes > 1024) { u++; } return String.format("%.1f %cB", bytes / 1024f, " kMGTPE".charAt(u)); } public String getType() { return node.getType(); } public String getPath() { return path; } public String getName() { return name; } public String getSize() { return size; } public String getGroupRead() { return groupRead; } public String getGroupWrite() { return groupWrite; } public boolean isPublic() { return isPublic; } } vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/service/NodesService.java +37 −12 Original line number Diff line number Diff line package it.inaf.ia2.vospace.ui.service; import it.inaf.ia2.vospace.ui.VOSpaceException; import it.inaf.ia2.vospace.ui.client.VOSpaceClient; import java.io.OutputStream; import java.io.PrintWriter; import net.ivoa.xml.vospace.v2.ContainerNode; import net.ivoa.xml.vospace.v2.Node; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; Loading @@ -13,6 +14,8 @@ import org.springframework.stereotype.Service; @Service public class NodesService { private static final Logger LOG = LoggerFactory.getLogger(NodesService.class); @Autowired private VOSpaceClient client; Loading @@ -37,24 +40,46 @@ public class NodesService { } private String getNodeHtml(Node node) { NodeInfo nodeInfo = new NodeInfo(node, authority); String html = "<tr>"; html += "<td><a href=\"#\">"; html += getName(node); html += "</a></td>"; html += "<td><input type=\"checkbox\" data-node=\"" + nodeInfo.getPath() + "\" /></td>"; html += "<td>" + getIcon(nodeInfo) + getLink(nodeInfo) + "</td>"; html += "<td>" + nodeInfo.getSize() + "</td>"; html += "<td>" + nodeInfo.getGroupRead() + "</td>"; html += "<td>" + nodeInfo.getGroupWrite() + "</td>"; html += "</tr>"; return html; } private String getName(Node node) { String uri = node.getUri(); String prefix = "vos://" + authority; private String getIcon(NodeInfo nodeInfo) { String html = "<span class=\"icon "; if ("vos:ContainerNode".equals(nodeInfo.getType())) { html += "folder"; } else { html += "file"; } html += "-icon\"></span> "; return html; } if (!uri.startsWith(prefix)) { throw new VOSpaceException("Node authority is different from configured one! Configured is " + authority + ", but node URI is " + uri); private String getLink(NodeInfo nodeInfo) { if (isDownloadable(nodeInfo)) { if ("vos:ContainerNode".equals(nodeInfo.getType())) { return "<a href=\"#nodes" + nodeInfo.getPath() + "\">" + nodeInfo.getName() + "</a>"; } else { return "<a href=\"download" + nodeInfo.getPath() + "\" target=\"blank_\">" + nodeInfo.getName() + "</a>"; } } return nodeInfo.getName(); } return uri.substring(prefix.length() + 1); private boolean isDownloadable(NodeInfo nodeInfo) { if (nodeInfo.isPublic()) { return true; } // TODO: check user group return false; } } vospace-ui-frontend/src/assets/css/fonts.css 0 → 100644 +14 −0 Original line number Diff line number Diff line .icon { display: inline-block; width: 1em; height: 1em; vertical-align: -0.15em; } .folder-icon { 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"); } .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"); } vospace-ui-frontend/src/components/Main.vue +38 −1 Original line number Diff line number Diff line Loading @@ -4,7 +4,15 @@ <table class="table b-table table-striped table-hover"> <thead> <tr> <th>File</th> <th id="checkboxes"> <a href="#" @click.stop.prevent="selectAll"><BIconCheckSquare /></a> <a href="#" @click.stop.prevent="deSelectAll"><BIconSquare /></a> </th> <th>Name</th> <th>Size</th> <th>Group read</th> <th>Group write</th> </tr> </thead> <tbody id="nodes"></tbody> Loading @@ -15,13 +23,42 @@ <script> import client from 'api-client'; import { BIconCheckSquare, BIconSquare } from 'bootstrap-vue' export default { components: { BIconCheckSquare, BIconSquare }, mounted() { client.getNode(this.$store.state.path) .then(res => { document.getElementById('nodes').outerHTML = res; }); }, methods: { selectAll() { this.selectInputs(true); }, deSelectAll() { this.selectInputs(false); }, selectInputs(value) { document.querySelectorAll('#nodes input').forEach(input => input.checked = value); } } } </script> <style> @import '../assets/css/fonts.css'; .table { text-align: left; } th#checkboxes { /* Credits: https://stackoverflow.com/a/43615091/771431 */ width:0.1%; white-space: nowrap; } </style> Loading
vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/controller/NodesController.java +5 −0 Original line number Diff line number Diff line Loading @@ -29,4 +29,9 @@ public class NodesController { nodesService.generateNodesHtml(path, response.getOutputStream()); } @GetMapping(value = "/download/{path}") public void directDownload(@PathVariable("path") String path) { // TODO: call pullFromVoSpace sync transfer } }
vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/service/NodeInfo.java 0 → 100644 +127 −0 Original line number Diff line number Diff line package it.inaf.ia2.vospace.ui.service; import it.inaf.ia2.vospace.ui.VOSpaceException; import java.util.Optional; import net.ivoa.xml.vospace.v2.Node; import net.ivoa.xml.vospace.v2.Property; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class NodeInfo { private static final Logger LOG = LoggerFactory.getLogger(NodeInfo.class); private final String authority; private final Node node; private final String path; private final String name; private final String size; private final String groupRead; private final String groupWrite; private final boolean isPublic; public NodeInfo(Node node, String authority) { this.authority = authority; this.node = node; this.path = getPath(node); this.name = path.substring(path.lastIndexOf("/") + 1); this.size = getSize(node); this.groupRead = getGroupRead(node); this.groupWrite = getGroupWrite(node); this.isPublic = isPublic(node); } private String getPath(Node node) { String uri = node.getUri(); String prefix = "vos://" + authority; if (!uri.startsWith(prefix)) { throw new VOSpaceException("Node authority is different from configured one! Configured is " + authority + ", but node URI is " + uri); } return uri.substring(prefix.length()); } private String getGroupRead(Node node) { return getProperty(node, "ivo://ivoa.net/vospace/core#groupread").orElse(""); } private String getGroupWrite(Node node) { return getProperty(node, "ivo://ivoa.net/vospace/core#groupwrite").orElse(""); } private boolean isPublic(Node node) { return Boolean.parseBoolean(getProperty(node, "ivo://ivoa.net/vospace/core#ispublic").orElse("false")); } private Optional<String> getProperty(Node node, String uri) { if (node.getProperties() != null && node.getProperties().getProperty() != null) { for (Property property : node.getProperties().getProperty()) { if (uri.equals(property.getUri())) { return Optional.of(property.getValue()); } } } return Optional.empty(); } private String getSize(Node node) { return getProperty(node, "ivo://ivoa.net/vospace/core#length") .map(value -> { try { long bytes = Long.parseLong(value); return getHumanReadableSize(bytes); } catch (NumberFormatException ex) { LOG.warn("Invalid length for node " + node.getUri() + ". Length is " + value); return ""; } }) .orElse(""); } /** * Credits: https://stackoverflow.com/a/16576773/771431 */ private String getHumanReadableSize(long bytes) { int u = 0; for (; bytes > 1024 * 1024; bytes >>= 10) { u++; } if (bytes > 1024) { u++; } return String.format("%.1f %cB", bytes / 1024f, " kMGTPE".charAt(u)); } public String getType() { return node.getType(); } public String getPath() { return path; } public String getName() { return name; } public String getSize() { return size; } public String getGroupRead() { return groupRead; } public String getGroupWrite() { return groupWrite; } public boolean isPublic() { return isPublic; } }
vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/service/NodesService.java +37 −12 Original line number Diff line number Diff line package it.inaf.ia2.vospace.ui.service; import it.inaf.ia2.vospace.ui.VOSpaceException; import it.inaf.ia2.vospace.ui.client.VOSpaceClient; import java.io.OutputStream; import java.io.PrintWriter; import net.ivoa.xml.vospace.v2.ContainerNode; import net.ivoa.xml.vospace.v2.Node; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; Loading @@ -13,6 +14,8 @@ import org.springframework.stereotype.Service; @Service public class NodesService { private static final Logger LOG = LoggerFactory.getLogger(NodesService.class); @Autowired private VOSpaceClient client; Loading @@ -37,24 +40,46 @@ public class NodesService { } private String getNodeHtml(Node node) { NodeInfo nodeInfo = new NodeInfo(node, authority); String html = "<tr>"; html += "<td><a href=\"#\">"; html += getName(node); html += "</a></td>"; html += "<td><input type=\"checkbox\" data-node=\"" + nodeInfo.getPath() + "\" /></td>"; html += "<td>" + getIcon(nodeInfo) + getLink(nodeInfo) + "</td>"; html += "<td>" + nodeInfo.getSize() + "</td>"; html += "<td>" + nodeInfo.getGroupRead() + "</td>"; html += "<td>" + nodeInfo.getGroupWrite() + "</td>"; html += "</tr>"; return html; } private String getName(Node node) { String uri = node.getUri(); String prefix = "vos://" + authority; private String getIcon(NodeInfo nodeInfo) { String html = "<span class=\"icon "; if ("vos:ContainerNode".equals(nodeInfo.getType())) { html += "folder"; } else { html += "file"; } html += "-icon\"></span> "; return html; } if (!uri.startsWith(prefix)) { throw new VOSpaceException("Node authority is different from configured one! Configured is " + authority + ", but node URI is " + uri); private String getLink(NodeInfo nodeInfo) { if (isDownloadable(nodeInfo)) { if ("vos:ContainerNode".equals(nodeInfo.getType())) { return "<a href=\"#nodes" + nodeInfo.getPath() + "\">" + nodeInfo.getName() + "</a>"; } else { return "<a href=\"download" + nodeInfo.getPath() + "\" target=\"blank_\">" + nodeInfo.getName() + "</a>"; } } return nodeInfo.getName(); } return uri.substring(prefix.length() + 1); private boolean isDownloadable(NodeInfo nodeInfo) { if (nodeInfo.isPublic()) { return true; } // TODO: check user group return false; } }
vospace-ui-frontend/src/assets/css/fonts.css 0 → 100644 +14 −0 Original line number Diff line number Diff line .icon { display: inline-block; width: 1em; height: 1em; vertical-align: -0.15em; } .folder-icon { 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"); } .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"); }
vospace-ui-frontend/src/components/Main.vue +38 −1 Original line number Diff line number Diff line Loading @@ -4,7 +4,15 @@ <table class="table b-table table-striped table-hover"> <thead> <tr> <th>File</th> <th id="checkboxes"> <a href="#" @click.stop.prevent="selectAll"><BIconCheckSquare /></a> <a href="#" @click.stop.prevent="deSelectAll"><BIconSquare /></a> </th> <th>Name</th> <th>Size</th> <th>Group read</th> <th>Group write</th> </tr> </thead> <tbody id="nodes"></tbody> Loading @@ -15,13 +23,42 @@ <script> import client from 'api-client'; import { BIconCheckSquare, BIconSquare } from 'bootstrap-vue' export default { components: { BIconCheckSquare, BIconSquare }, mounted() { client.getNode(this.$store.state.path) .then(res => { document.getElementById('nodes').outerHTML = res; }); }, methods: { selectAll() { this.selectInputs(true); }, deSelectAll() { this.selectInputs(false); }, selectInputs(value) { document.querySelectorAll('#nodes input').forEach(input => input.checked = value); } } } </script> <style> @import '../assets/css/fonts.css'; .table { text-align: left; } th#checkboxes { /* Credits: https://stackoverflow.com/a/43615091/771431 */ width:0.1%; white-space: nowrap; } </style>