// Copyright Epic Games, Inc. All Rights Reserved. "use strict"; import { ZenPage } from "./page.js" import { Fetcher } from "../util/fetcher.js" import { Friendly } from "../util/friendly.js" import { Table } from "../util/widgets.js" //////////////////////////////////////////////////////////////////////////////// export class Page extends ZenPage { generate_crumbs() {} async main() { this.set_title("object store"); const section = this.add_section("Object Store"); this._stats_host = section; const buckets_section = this.add_section("Buckets"); this._buckets_host = buckets_section; await this._update(); this._poll_timer = setInterval(() => this._update(), 5000); } async _update() { try { const [data, stats] = await Promise.all([ new Fetcher().resource("/obj/").json(), new Fetcher().resource("stats", "obj").json().catch(() => null), ]); this._render(data, stats); } catch (e) { /* service unavailable */ } } _render(data, stats) { const buckets = data.buckets || []; // Stats summary { const host = this._stats_host; if (!this._stats_grid) { this._stats_grid = host.tag().classify("grid").classify("stats-tiles"); } const grid = this._stats_grid; grid.inner().innerHTML = ""; const total_objects = buckets.reduce((sum, b) => sum + (b.object_count || 0), 0); const total_size = buckets.reduce((sum, b) => sum + (b.size || 0), 0); // HTTP Requests tile this._render_http_requests_tile(grid, stats && stats.requests); { const tile = grid.tag().classify("card").classify("stats-tile"); tile.tag().classify("card-title").text("Object Store"); const body = tile.tag().classify("tile-metrics"); this._metric(body, Friendly.sep(buckets.length), "buckets", true); this._metric(body, Friendly.sep(total_objects), "objects"); this._metric(body, Friendly.bytes(total_size), "storage"); this._metric(body, Friendly.bytes(data.total_bytes_served || 0), "bytes served"); } } // Buckets table { const host = this._buckets_host; if (this._buckets_table) { this._buckets_table.clear(); } else { this._buckets_table = host.add_widget( Table, ["name", "objects", "size"], Table.Flag_FitLeft ); } if (buckets.length === 0) { return; } for (const bucket of buckets) { const row = this._buckets_table.add_row( bucket.name || "-", Friendly.sep(bucket.object_count || 0), Friendly.bytes(bucket.size || 0), ); const row_elem = row.inner(); for (const cell of row_elem.children) { cell.style.cursor = "pointer"; cell.addEventListener("click", () => this._toggle_bucket(bucket.name, row)); } } } } async _toggle_bucket(name, row) { // If already expanded, collapse if (this._expanded_bucket === name) { if (this._expanded_el) { this._expanded_el.remove(); this._expanded_el = null; } this._expanded_bucket = null; return; } // Collapse any previous if (this._expanded_el) { this._expanded_el.remove(); this._expanded_el = null; } this._expanded_bucket = name; try { const data = await new Fetcher().resource("/obj/bucket/" + encodeURIComponent(name) + "/").json(); const result = data.ListBucketResult || {}; const contents = result.Contents || []; const detail = document.createElement("div"); detail.className = "objectstore-bucket-detail"; if (contents.length === 0) { detail.textContent = "Bucket is empty."; } else { const tbl = document.createElement("table"); tbl.className = "objectstore-objects-table"; const thead = document.createElement("thead"); const hdr = document.createElement("tr"); for (const col of ["key", "size"]) { const th = document.createElement("th"); th.textContent = col; hdr.appendChild(th); } thead.appendChild(hdr); tbl.appendChild(thead); const tbody = document.createElement("tbody"); const limit = Math.min(contents.length, 100); for (let i = 0; i < limit; i++) { const obj = contents[i]; const tr = document.createElement("tr"); const td_key = document.createElement("td"); td_key.textContent = obj.Key || "-"; tr.appendChild(td_key); const td_size = document.createElement("td"); td_size.textContent = Friendly.bytes(obj.Size || 0); td_size.style.textAlign = "right"; tr.appendChild(td_size); tbody.appendChild(tr); } tbl.appendChild(tbody); detail.appendChild(tbl); if (contents.length > limit) { const more = document.createElement("div"); more.style.opacity = "0.7"; more.style.fontSize = "0.85em"; more.style.marginTop = "4px"; more.textContent = `Showing ${limit} of ${contents.length} objects.`; detail.appendChild(more); } } // Insert after the row's last cell const last_cell = row.inner().lastElementChild; if (last_cell) { last_cell.after(detail); } this._expanded_el = detail; } catch (e) { this._expanded_bucket = null; } } }