Files
broker/coreci_broker_ui/app_registrations.html
coreci 7f7868f7cf docs(milestone): complete coreci-broker
---ci---
phase: 5
milestone: v0.1
status: complete
requirements:
  covered: [REQ-01, REQ-02, REQ-03, REQ-04, REQ-05, REQ-06, REQ-07, REQ-08, REQ-09, REQ-10, REQ-11, REQ-12, REQ-13, REQ-14, REQ-15, REQ-16, REQ-17, REQ-18, REQ-19, REQ-20]
  partial: []
---/ci---
2026-10-10 15:30:14 +00:00

271 lines
7.7 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>App Registrations — CoreCI Broker</title>
<link rel="stylesheet" href="/ui/shared.css">
<style>
.page-head {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 16px;
flex-wrap: wrap;
margin-bottom: 8px;
}
.page-sub {
color: var(--muted);
font-size: 14px;
margin: 0;
}
.table-empty {
color: var(--muted);
}
.cell-actions {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.btn-danger {
color: var(--danger);
background: transparent;
border: 1px solid rgba(240, 122, 114, 0.45);
}
.badge-revoked {
color: var(--danger);
border-color: rgba(240, 122, 114, 0.45);
background: rgba(240, 122, 114, 0.08);
}
</style>
</head>
<body>
<div id="nav"></div>
<main class="wrap">
<section class="card">
<div class="page-head">
<div>
<h1>App Registrations</h1>
<p class="page-sub">Tool app UUIDs allowed to register against the broker.</p>
</div>
<button type="button" class="btn btn-primary" id="add-open">Add registration</button>
</div>
<p class="form-error" id="page-error" role="alert" hidden></p>
<table>
<thead>
<tr>
<th>UUID</th>
<th>Name</th>
<th>Status</th>
<th>Created</th>
<th>Revoked</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="rows"></tbody>
</table>
</section>
</main>
<div class="modal-backdrop" id="modal" hidden role="dialog" aria-modal="true" aria-labelledby="modal-title">
<div class="modal">
<h2 id="modal-title">Add registration</h2>
<form id="add-form">
<div class="field">
<label for="uuid">App UUID</label>
<input id="uuid" name="uuid" type="text" class="mono" placeholder="00000000-0000-0000-0000-000000000000" required>
</div>
<div class="field">
<label for="name">Name (optional)</label>
<input id="name" name="name" type="text" placeholder="proxmox tool">
</div>
<p class="form-error" id="add-error" role="alert" hidden></p>
<div class="modal-actions">
<button type="button" class="btn btn-subtle" id="add-cancel">Cancel</button>
<button type="submit" class="btn btn-primary" id="add-submit">Add</button>
</div>
</form>
</div>
</div>
<script src="/ui/nav.js"></script>
<script>
(function () {
var API = "/api/broker/app_registrations";
var rows = document.getElementById("rows");
var pageError = document.getElementById("page-error");
var modal = document.getElementById("modal");
var form = document.getElementById("add-form");
var uuidInput = document.getElementById("uuid");
var nameInput = document.getElementById("name");
var addError = document.getElementById("add-error");
var submitButton = document.getElementById("add-submit");
function showPageError(message) {
pageError.textContent = message;
pageError.hidden = false;
}
function clearPageError() {
pageError.hidden = true;
pageError.textContent = "";
}
function messageRow(text) {
rows.replaceChildren();
var tr = document.createElement("tr");
var td = document.createElement("td");
td.colSpan = 6;
td.className = "table-empty";
td.textContent = text;
tr.appendChild(td);
rows.appendChild(tr);
}
function textCell(value, mono) {
var td = document.createElement("td");
if (mono) {
td.className = "mono";
}
td.textContent = value === null || value === undefined || value === "" ? "—" : value;
return td;
}
function statusBadge(status) {
var span = document.createElement("span");
span.className = "badge " + (status === "active" ? "badge-active" : "badge-revoked");
span.textContent = status;
return span;
}
function actionButton(label, className, disabled, handler) {
var button = document.createElement("button");
button.type = "button";
button.className = "btn " + className;
button.textContent = label;
button.disabled = disabled;
button.addEventListener("click", handler);
return button;
}
function renderRow(row) {
var tr = document.createElement("tr");
tr.appendChild(textCell(row.uuid, true));
tr.appendChild(textCell(row.name));
var statusCell = document.createElement("td");
statusCell.appendChild(statusBadge(row.status));
tr.appendChild(statusCell);
tr.appendChild(textCell(row.created_at));
tr.appendChild(textCell(row.revoked_at));
var actionsCell = document.createElement("td");
var actions = document.createElement("div");
actions.className = "cell-actions";
var key = encodeURIComponent(row.uuid);
actions.appendChild(actionButton("Revoke", "btn-danger", row.status !== "active", function () {
rowAction("DELETE", API + "/" + key, "Revoke app registration " + row.uuid + "?", "Revoke failed", row.uuid);
}));
actions.appendChild(actionButton("Reinstate", "btn-subtle", row.status === "active", function () {
rowAction("POST", API + "/" + key + "/reinstate", "Reinstate app registration " + row.uuid + "?", "Reinstate failed", row.uuid);
}));
actionsCell.appendChild(actions);
tr.appendChild(actionsCell);
return tr;
}
async function refresh() {
messageRow("Loading registrations…");
var result = await api(API);
if (!result.ok) {
messageRow(result.status === 0 ? "Broker unreachable." : "Could not load registrations.");
return;
}
var list = (result.data && result.data.registrations) || [];
if (!list.length) {
messageRow("No app registrations yet.");
return;
}
rows.replaceChildren();
list.forEach(function (row) {
rows.appendChild(renderRow(row));
});
}
async function rowAction(method, path, question, label, uuid) {
if (!confirm(question)) {
return;
}
clearPageError();
var result = await api(path, {method: method});
if (!result.ok) {
if (result.status === 0) {
showPageError(label + ": broker unreachable.");
} else {
var detail = result.data && result.data.message ? ": " + result.data.message : ".";
showPageError(label + " " + uuid + detail);
}
}
await refresh();
}
function openModal() {
form.reset();
addError.hidden = true;
addError.textContent = "";
submitButton.disabled = false;
modal.hidden = false;
uuidInput.focus();
}
function closeModal() {
modal.hidden = true;
}
form.addEventListener("submit", async function (event) {
event.preventDefault();
addError.hidden = true;
addError.textContent = "";
submitButton.disabled = true;
var payload = {uuid: uuidInput.value.trim()};
var name = nameInput.value.trim();
if (name) {
payload.name = name;
}
var result = await api(API, {method: "POST", body: payload});
submitButton.disabled = false;
if (result.ok) {
closeModal();
clearPageError();
await refresh();
return;
}
if (result.status === 0) {
addError.textContent = "Broker unreachable.";
} else if (result.data && result.data.message) {
addError.textContent = result.data.message;
} else {
addError.textContent = "Could not add registration.";
}
addError.hidden = false;
await refresh();
});
document.getElementById("add-open").addEventListener("click", openModal);
document.getElementById("add-cancel").addEventListener("click", closeModal);
modal.addEventListener("click", function (event) {
if (event.target === modal) {
closeModal();
}
});
document.addEventListener("keydown", function (event) {
if (event.key === "Escape" && !modal.hidden) {
closeModal();
}
});
refresh();
})();
</script>
</body>
</html>