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

Node listing implementation

parent 58f6570b
Branches
No related tags found
No related merge requests found
...@@ -29,4 +29,9 @@ public class NodesController { ...@@ -29,4 +29,9 @@ public class NodesController {
nodesService.generateNodesHtml(path, response.getOutputStream()); nodesService.generateNodesHtml(path, response.getOutputStream());
} }
@GetMapping(value = "/download/{path}")
public void directDownload(@PathVariable("path") String path) {
// TODO: call pullFromVoSpace sync transfer
}
} }
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;
}
}
package it.inaf.ia2.vospace.ui.service; package it.inaf.ia2.vospace.ui.service;
import it.inaf.ia2.vospace.ui.VOSpaceException;
import it.inaf.ia2.vospace.ui.client.VOSpaceClient; import it.inaf.ia2.vospace.ui.client.VOSpaceClient;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.PrintWriter; import java.io.PrintWriter;
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.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
...@@ -13,6 +14,8 @@ import org.springframework.stereotype.Service; ...@@ -13,6 +14,8 @@ import org.springframework.stereotype.Service;
@Service @Service
public class NodesService { public class NodesService {
private static final Logger LOG = LoggerFactory.getLogger(NodesService.class);
@Autowired @Autowired
private VOSpaceClient client; private VOSpaceClient client;
...@@ -37,24 +40,46 @@ public class NodesService { ...@@ -37,24 +40,46 @@ public class NodesService {
} }
private String getNodeHtml(Node node) { private String getNodeHtml(Node node) {
NodeInfo nodeInfo = new NodeInfo(node, authority);
String html = "<tr>"; String html = "<tr>";
html += "<td><a href=\"#\">"; html += "<td><input type=\"checkbox\" data-node=\"" + nodeInfo.getPath() + "\" /></td>";
html += getName(node); html += "<td>" + getIcon(nodeInfo) + getLink(nodeInfo) + "</td>";
html += "</a></td>"; html += "<td>" + nodeInfo.getSize() + "</td>";
html += "<td>" + nodeInfo.getGroupRead() + "</td>";
html += "<td>" + nodeInfo.getGroupWrite() + "</td>";
html += "</tr>"; html += "</tr>";
return html; return html;
} }
private String getName(Node node) { private String getIcon(NodeInfo nodeInfo) {
String html = "<span class=\"icon ";
String uri = node.getUri(); if ("vos:ContainerNode".equals(nodeInfo.getType())) {
html += "folder";
String prefix = "vos://" + authority; } else {
html += "file";
}
html += "-icon\"></span>&nbsp;";
return html;
}
if (!uri.startsWith(prefix)) { private String getLink(NodeInfo nodeInfo) {
throw new VOSpaceException("Node authority is different from configured one! Configured is " + authority + ", but node URI is " + uri); 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;
} }
} }
.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");
}
...@@ -4,7 +4,15 @@ ...@@ -4,7 +4,15 @@
<table class="table b-table table-striped table-hover"> <table class="table b-table table-striped table-hover">
<thead> <thead>
<tr> <tr>
<th>File</th> <th id="checkboxes">
<a href="#" @click.stop.prevent="selectAll"><BIconCheckSquare /></a>
&nbsp;
<a href="#" @click.stop.prevent="deSelectAll"><BIconSquare /></a>
</th>
<th>Name</th>
<th>Size</th>
<th>Group read</th>
<th>Group write</th>
</tr> </tr>
</thead> </thead>
<tbody id="nodes"></tbody> <tbody id="nodes"></tbody>
...@@ -15,13 +23,42 @@ ...@@ -15,13 +23,42 @@
<script> <script>
import client from 'api-client'; import client from 'api-client';
import { BIconCheckSquare, BIconSquare } from 'bootstrap-vue'
export default { export default {
components: {
BIconCheckSquare, BIconSquare
},
mounted() { mounted() {
client.getNode(this.$store.state.path) client.getNode(this.$store.state.path)
.then(res => { .then(res => {
document.getElementById('nodes').outerHTML = 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> </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>
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment