Skip to content

Instantly share code, notes, and snippets.

@zacharygraber
Created October 7, 2025 20:56
Show Gist options
  • Select an option

  • Save zacharygraber/887f76f9c44e2de40da70ebd4a579e84 to your computer and use it in GitHub Desktop.

Select an option

Save zacharygraber/887f76f9c44e2de40da70ebd4a579e84 to your computer and use it in GitHub Desktop.
Basic Jetstream2 object storage browse page
<!DOCTYPE html>
<html>
<head>
<style>
th,
td {
padding-left: 10px;
padding-right: 10px;
font-family: monospace;
}
.loader {
border: 5px solid #edebeb;
border-top: 5px solid #252525;
border-radius: 50%;
width: 50px;
height: 50px;
margin: 50px 200px;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>
</head>
<body>
<table>
<thead>
<tr>
<th>Last Modified</th>
<th>Size</th>
<th>Key</th>
</tr>
</thead>
<tbody id="tableBody">
</tbody>
</table>
<div class="loader" id="loader"></div>
<script>
const S3_ENDPOINT = "https://js2.jetstream-cloud.org:8001/zegraber-sample-public-bucket";
// Shamelessly stolen from https://stackoverflow.com/questions/15900485/correct-way-to-convert-size-in-bytes-to-kb-mb-gb-in-javascript
function formatBytes(a, b = 2) { if (!+a) return ""; const c = 0 > b ? 0 : b, d = Math.floor(Math.log(a) / Math.log(1024)); return `${parseFloat((a / Math.pow(1024, d)).toFixed(c))} ${["Bytes", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"][d]}` }
fetch(S3_ENDPOINT)
.then(res => res.text())
.then(str => new window.DOMParser().parseFromString(str, "application/xml"))
.then(xml => {
document.title = xml.querySelector("Name").textContent;
let rows = xml.querySelectorAll("Contents");
const tbody = document.getElementById("tableBody");
let prefix = (new URLSearchParams(window.location.search)).get("prefix")
prefix = (prefix === null || prefix == "/") ? "" : prefix;
let items = new Map();
rows.forEach(item => {
let key = item.querySelector("Key").textContent;
if (!key.startsWith(prefix) || key == prefix) return;
key = key.substring(prefix.length)
let size = parseInt(item.querySelector("Size").textContent, 10);
let lastModified = new Date(item.querySelector("LastModified").textContent);
// Collapse (hide) items in folders
if (key.includes("/")) {
let path = key.substring(0, key.indexOf("/") + 1);
items.set(path, {
type: "folder",
size: items.has(path) ? items.get(path).size + size : parseInt(item.querySelector("Size").textContent, 10),
lastModified: items.has(path) ? (lastModified > items.get(path).lastModified ? lastModified : items.get(path).lastModified) : lastModified
});
}
else {
let path = key;
items.set(path, {
type: "object",
size: size,
lastModified: lastModified
});
}
});
// Create '../' back link
if (prefix.includes("/")) {
let newPrefix = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
newPrefix = newPrefix.substring(0, newPrefix.lastIndexOf("/") + 1);
let tr = document.createElement("tr");
tr.innerHTML = `
<td> </td>
<td> </td>
<td><a href="${window.location.origin + window.location.pathname + "?prefix=" + encodeURIComponent(newPrefix)}">../</a></td>
`;
tbody.appendChild(tr);
}
Array.from(items.keys()).sort().forEach(path => {
let tr = document.createElement("tr");
tr.innerHTML = `
<td>${items.get(path).lastModified.toLocaleString()}</td>
<td>${formatBytes(items.get(path).size)}</td>
<td><a href="${items.get(path).type == "object" ? (S3_ENDPOINT + "/" + prefix + path) : (window.location.origin + window.location.pathname + "?prefix=" + encodeURIComponent(prefix + path))}">${path}</a></td>
`;
tbody.appendChild(tr);
});
document.getElementById("loader").style.display = 'none';
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment