Created
October 7, 2025 20:56
-
-
Save zacharygraber/887f76f9c44e2de40da70ebd4a579e84 to your computer and use it in GitHub Desktop.
Basic Jetstream2 object storage browse page
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!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