diff options
Diffstat (limited to 'src/zenserver/frontend/html')
| -rw-r--r-- | src/zenserver/frontend/html/pages/cache.js | 4 | ||||
| -rw-r--r-- | src/zenserver/frontend/html/pages/compute.js | 13 | ||||
| -rw-r--r-- | src/zenserver/frontend/html/pages/hub.js | 10 | ||||
| -rw-r--r-- | src/zenserver/frontend/html/pages/orchestrator.js | 5 | ||||
| -rw-r--r-- | src/zenserver/frontend/html/pages/projects.js | 5 | ||||
| -rw-r--r-- | src/zenserver/frontend/html/pages/workspaces.js | 2 | ||||
| -rw-r--r-- | src/zenserver/frontend/html/util/widgets.js | 44 | ||||
| -rw-r--r-- | src/zenserver/frontend/html/zen.css | 29 |
8 files changed, 103 insertions, 9 deletions
diff --git a/src/zenserver/frontend/html/pages/cache.js b/src/zenserver/frontend/html/pages/cache.js index 58e2023f9..683f7df4f 100644 --- a/src/zenserver/frontend/html/pages/cache.js +++ b/src/zenserver/frontend/html/pages/cache.js @@ -6,7 +6,7 @@ import { ZenPage } from "./page.js" import { Fetcher } from "../util/fetcher.js" import { Friendly } from "../util/friendly.js" import { Modal } from "../util/modal.js" -import { Table, Toolbar, Pager } from "../util/widgets.js" +import { Table, Toolbar, Pager, add_copy_button } from "../util/widgets.js" //////////////////////////////////////////////////////////////////////////////// export class Page extends ZenPage @@ -111,6 +111,8 @@ export class Page extends ZenPage const cell = row.get_cell(0); cell.tag().text(item.namespace).on_click(() => this.view_namespace(item.namespace)); + add_copy_button(cell.inner(), item.namespace); + add_copy_button(row.get_cell(1).inner(), data["Configuration"]["RootDir"]); const action_cell = row.get_cell(-1); const action_tb = new Toolbar(action_cell, true); diff --git a/src/zenserver/frontend/html/pages/compute.js b/src/zenserver/frontend/html/pages/compute.js index 2eb4d4e9b..c2257029e 100644 --- a/src/zenserver/frontend/html/pages/compute.js +++ b/src/zenserver/frontend/html/pages/compute.js @@ -5,7 +5,7 @@ import { ZenPage } from "./page.js" import { Fetcher } from "../util/fetcher.js" import { Friendly } from "../util/friendly.js" -import { Table } from "../util/widgets.js" +import { Table, add_copy_button } from "../util/widgets.js" const MAX_HISTORY_POINTS = 60; @@ -352,8 +352,9 @@ export class Page extends ZenPage id, ); - // Worker ID column: monospace for hex readability + // Worker ID column: monospace for hex readability, copy button row.get_cell(5).style("fontFamily", "'SF Mono', 'Cascadia Mono', Consolas, 'DejaVu Sans Mono', monospace"); + add_copy_button(row.get_cell(5).inner(), id); // Make name clickable to expand detail const cell = row.get_cell(0); @@ -524,7 +525,7 @@ export class Page extends ZenPage : q.state === "draining" ? "draining" : q.is_complete ? "complete" : "active"; - this._queues_table.add_row( + const qrow = this._queues_table.add_row( id, status, String(q.active_count ?? 0), @@ -534,6 +535,10 @@ export class Page extends ZenPage String(q.cancelled_count ?? 0), q.queue_token || "-", ); + if (q.queue_token) + { + add_copy_button(qrow.get_cell(7).inner(), q.queue_token); + } } } @@ -590,7 +595,9 @@ export class Page extends ZenPage // use monospace for readability, and show full value on hover const mono = "'SF Mono', 'Cascadia Mono', Consolas, 'DejaVu Sans Mono', monospace"; row.get_cell(7).style("textAlign", "right").style("fontFamily", mono).attr("title", workerId); + if (workerId !== "-") { add_copy_button(row.get_cell(7).inner(), workerId); } row.get_cell(8).style("textAlign", "right").style("fontFamily", mono).attr("title", actionId); + if (actionId !== "-") { add_copy_button(row.get_cell(8).inner(), actionId); } } } diff --git a/src/zenserver/frontend/html/pages/hub.js b/src/zenserver/frontend/html/pages/hub.js index 3cbfe6092..b2bca9324 100644 --- a/src/zenserver/frontend/html/pages/hub.js +++ b/src/zenserver/frontend/html/pages/hub.js @@ -6,7 +6,7 @@ import { ZenPage } from "./page.js" import { Fetcher } from "../util/fetcher.js" import { Friendly } from "../util/friendly.js" import { Modal } from "../util/modal.js" -import { flash_highlight } from "../util/widgets.js" +import { flash_highlight, copy_button } from "../util/widgets.js" //////////////////////////////////////////////////////////////////////////////// const STABLE_STATES = new Set(["provisioned", "hibernated", "crashed"]); @@ -355,6 +355,7 @@ export class Page extends ZenPage } row.state_text.nodeValue = state; row.port_text.nodeValue = m.port ? String(m.port) : ""; + row.copy_port_btn.style.display = m.port ? "" : "none"; if (m.state_change_time) { const state_label = state.charAt(0).toUpperCase() + state.slice(1); @@ -427,6 +428,8 @@ export class Page extends ZenPage id_wrap.style.cssText = "display:inline-flex;align-items:center;font-family:monospace;font-size:14px;"; id_wrap.appendChild(btn_expand); id_wrap.appendChild(document.createTextNode("\u00A0" + id)); + const copy_id_btn = copy_button(id); + id_wrap.appendChild(copy_id_btn); td_id.appendChild(id_wrap); tr.appendChild(td_id); @@ -448,6 +451,9 @@ export class Page extends ZenPage td_port.style.cssText = "font-variant-numeric:tabular-nums;"; const port_node = document.createTextNode(port ? String(port) : ""); td_port.appendChild(port_node); + const copy_port_btn = copy_button(() => port_node.nodeValue); + copy_port_btn.style.display = port ? "" : "none"; + td_port.appendChild(copy_port_btn); tr.appendChild(td_port); const td_action = document.createElement("td"); @@ -528,7 +534,7 @@ export class Page extends ZenPage metrics_td.appendChild(metrics_grid); metrics_tr.appendChild(metrics_td); - row = { tr, metrics_tr, idx: td_idx, cb, dot, state_text: state_node, port_text: port_node, btn_expand, btn_open: btn_o, btn_hibernate: btn_h, btn_wake: btn_w, btn_deprov: btn_d, btn_oblit: btn_x, metric_nodes, state_since_node, state_age_node, state_since_label, state_age_label }; + row = { tr, metrics_tr, idx: td_idx, cb, dot, state_text: state_node, port_text: port_node, copy_port_btn, btn_expand, btn_open: btn_o, btn_hibernate: btn_h, btn_wake: btn_w, btn_deprov: btn_d, btn_oblit: btn_x, metric_nodes, state_since_node, state_age_node, state_since_label, state_age_label }; this._row_cache.set(id, row); } diff --git a/src/zenserver/frontend/html/pages/orchestrator.js b/src/zenserver/frontend/html/pages/orchestrator.js index 30f6a8122..d11306998 100644 --- a/src/zenserver/frontend/html/pages/orchestrator.js +++ b/src/zenserver/frontend/html/pages/orchestrator.js @@ -5,7 +5,7 @@ import { ZenPage } from "./page.js" import { Fetcher } from "../util/fetcher.js" import { Friendly } from "../util/friendly.js" -import { Table } from "../util/widgets.js" +import { Table, add_copy_button } from "../util/widgets.js" //////////////////////////////////////////////////////////////////////////////// export class Page extends ZenPage @@ -298,12 +298,13 @@ export class Page extends ZenPage for (const c of clients) { - this._clients_table.add_row( + const crow = this._clients_table.add_row( c.id || "", c.hostname || "", c.address || "", this._format_last_seen(c.dt), ); + if (c.id) { add_copy_button(crow.get_cell(0).inner(), c.id); } } } diff --git a/src/zenserver/frontend/html/pages/projects.js b/src/zenserver/frontend/html/pages/projects.js index af7c5396a..2e76a80f1 100644 --- a/src/zenserver/frontend/html/pages/projects.js +++ b/src/zenserver/frontend/html/pages/projects.js @@ -6,7 +6,7 @@ import { ZenPage } from "./page.js" import { Fetcher } from "../util/fetcher.js" import { Friendly } from "../util/friendly.js" import { Modal } from "../util/modal.js" -import { Table, Toolbar, Pager } from "../util/widgets.js" +import { Table, Toolbar, Pager, add_copy_button } from "../util/widgets.js" //////////////////////////////////////////////////////////////////////////////// export class Page extends ZenPage @@ -177,16 +177,19 @@ export class Page extends ZenPage const cell = row.get_cell(0); cell.tag().text(project.Id).on_click(() => this.view_project(project.Id)); + add_copy_button(cell.inner(), project.Id); if (project.ProjectRootDir) { row.get_cell(1).tag("a").text(project.ProjectRootDir) .attr("href", "vscode://" + project.ProjectRootDir.replace(/\\/g, "/")); + add_copy_button(row.get_cell(1).inner(), project.ProjectRootDir); } if (project.EngineRootDir) { row.get_cell(2).tag("a").text(project.EngineRootDir) .attr("href", "vscode://" + project.EngineRootDir.replace(/\\/g, "/")); + add_copy_button(row.get_cell(2).inner(), project.EngineRootDir); } const action_cell = row.get_cell(-1); diff --git a/src/zenserver/frontend/html/pages/workspaces.js b/src/zenserver/frontend/html/pages/workspaces.js index 1668e096f..db02e8be1 100644 --- a/src/zenserver/frontend/html/pages/workspaces.js +++ b/src/zenserver/frontend/html/pages/workspaces.js @@ -4,6 +4,7 @@ import { ZenPage } from "./page.js" import { Fetcher } from "../util/fetcher.js" +import { copy_button } from "../util/widgets.js" //////////////////////////////////////////////////////////////////////////////// export class Page extends ZenPage @@ -157,6 +158,7 @@ export class Page extends ZenPage id_wrap.className = "ws-id-wrap"; id_wrap.appendChild(btn_expand); id_wrap.appendChild(document.createTextNode("\u00A0" + id)); + id_wrap.appendChild(copy_button(id)); const td_id = document.createElement("td"); td_id.appendChild(id_wrap); tr.appendChild(td_id); diff --git a/src/zenserver/frontend/html/util/widgets.js b/src/zenserver/frontend/html/util/widgets.js index b8fc720c1..651686a11 100644 --- a/src/zenserver/frontend/html/util/widgets.js +++ b/src/zenserver/frontend/html/util/widgets.js @@ -14,6 +14,50 @@ export function flash_highlight(element) } //////////////////////////////////////////////////////////////////////////////// +export function copy_button(value_or_fn) +{ + if (!navigator.clipboard) + { + const stub = document.createElement("span"); + stub.style.display = "none"; + return stub; + } + + let reset_timer = 0; + const btn = document.createElement("button"); + btn.className = "zen-copy-btn"; + btn.title = "Copy to clipboard"; + btn.textContent = "\u29C9"; + btn.addEventListener("click", async (e) => { + e.stopPropagation(); + const v = typeof value_or_fn === "function" ? value_or_fn() : value_or_fn; + if (!v) { return; } + try + { + await navigator.clipboard.writeText(v); + clearTimeout(reset_timer); + btn.classList.add("zen-copy-ok"); + btn.textContent = "\u2713"; + reset_timer = setTimeout(() => { btn.classList.remove("zen-copy-ok"); btn.textContent = "\u29C9"; }, 800); + } + catch (_e) { /* clipboard not available */ } + }); + return btn; +} + +// Wraps the existing children of `element` plus a copy button into an +// inline-flex nowrap container so the button never wraps to a new line. +export function add_copy_button(element, value_or_fn) +{ + if (!navigator.clipboard) { return; } + const wrap = document.createElement("span"); + wrap.className = "zen-copy-wrap"; + while (element.firstChild) { wrap.appendChild(element.firstChild); } + wrap.appendChild(copy_button(value_or_fn)); + element.appendChild(wrap); +} + +//////////////////////////////////////////////////////////////////////////////// class Widget extends Component { } diff --git a/src/zenserver/frontend/html/zen.css b/src/zenserver/frontend/html/zen.css index 8d4e60472..d3c6c9036 100644 --- a/src/zenserver/frontend/html/zen.css +++ b/src/zenserver/frontend/html/zen.css @@ -1816,6 +1816,35 @@ tr:last-child td { color: var(--theme_bright); } +.zen-copy-btn { + background: transparent; + border: 1px solid var(--theme_g2); + border-radius: 4px; + color: var(--theme_g1); + cursor: pointer; + font-size: 12px; + line-height: 1; + padding: 2px 5px; + margin-left: 6px; + vertical-align: middle; + flex-shrink: 0; + transition: background 0.1s, color 0.1s; +} +.zen-copy-btn:hover { + background: var(--theme_g2); + color: var(--theme_bright); +} +.zen-copy-btn.zen-copy-ok { + color: var(--theme_ok); + border-color: var(--theme_ok); +} + +.zen-copy-wrap { + display: inline-flex; + align-items: center; + white-space: nowrap; +} + .module-metrics-row td { padding: 6px 10px 10px 42px; background: var(--theme_g3); |