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.

Revisions

  1. zacharygraber created this gist Oct 7, 2025.
    126 changes: 126 additions & 0 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,126 @@
    <!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>