feat: add autorefresh button to the main page (#149)

* also add configuration options and change behaviour accordingly
This commit is contained in:
2025-07-01 03:22:01 +03:00
committed by GitHub
parent 939a94d889
commit 256376df85
12 changed files with 175 additions and 30 deletions

View File

@ -28,6 +28,9 @@ allow_read_only = yes
; External address of the web service. Will be used for some features like OAuth. If none set will be generated as
; address = http://${web:host}:${web:port}
;address = http://${web:host}:${web:port}
; Enable page auto refresh. Intervals are given in seconds. Default interval is always the first element of the list.
; If no intervals set, auto refresh will be disabled.
autorefresh_intervals = 5 1 10 30 60
; Enable file upload endpoint used by some triggers.
;enable_archive_upload = no
; Address to bind the server.

View File

@ -80,6 +80,21 @@
<button type="button" class="btn btn-secondary" onclick="reload()">
<i class="bi bi-arrow-clockwise"></i><span class="d-none d-sm-inline"> reload</span>
</button>
{% if autorefresh_intervals %}
<div class="btn-group">
<input id="table-autoreload-button" type="checkbox" class="btn-check" autocomplete="off" onclick="toggleTableAutoReload()" checked>
<label for="table-autoreload-button" class="btn btn-outline-secondary" title="toggle auto reload"><i class="bi bi-clock"></i></label>
<button type="button" class="btn btn-outline-secondary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
<span class="visually-hidden">select interval</span>
</button>
<ul id="table-autoreload-input" class="dropdown-menu">
{% for interval in autorefresh_intervals %}
<li><a class="dropdown-item {{ "active" if interval.is_active }}" onclick="toggleTableAutoReload({{ interval.interval }})" data-interval="{{ interval.interval }}">{{ interval.text }}</a></li>
{% endfor %}
</ul>
</div>
{% endif %}
</div>
<table id="packages"

View File

@ -101,9 +101,21 @@
<button id="package-info-update-button" type="submit" class="btn btn-success" onclick="packageInfoUpdate()" data-bs-dismiss="modal"><i class="bi bi-play"></i><span class="d-none d-sm-inline"> update</span></button>
<button id="package-info-remove-button" type="submit" class="btn btn-danger" onclick="packageInfoRemove()" data-bs-dismiss="modal"><i class="bi bi-trash"></i><span class="d-none d-sm-inline"> remove</span></button>
{% endif %}
<input id="package-info-autoreload-button" type="checkbox" class="btn-check" autocomplete="off" onclick="togglePackageInfoAutoReload()" checked>
<label for="package-info-autoreload-button" class="btn btn-outline-secondary" title="toggle auto reload"><i class="bi bi-clock"></i></label>
<button type="button" class="btn btn-secondary" onclick="showPackageInfo()"><i class="bi bi-arrow-clockwise"></i><span class="d-none d-sm-inline"> reload</span></button>
{% if autorefresh_intervals %}
<button type="button" class="btn btn-secondary" onclick="showPackageInfo()"><i class="bi bi-arrow-clockwise"></i><span class="d-none d-sm-inline"> reload</span></button>
<div class="btn-group dropup">
<input id="package-info-autoreload-button" type="checkbox" class="btn-check" autocomplete="off" onclick="togglePackageInfoAutoReload()" checked>
<label for="package-info-autoreload-button" class="btn btn-outline-secondary" title="toggle auto reload"><i class="bi bi-clock"></i></label>
<button type="button" class="btn btn-outline-secondary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
<span class="visually-hidden">select interval</span>
</button>
<ul id="package-info-autoreload-input" class="dropdown-menu">
{% for interval in autorefresh_intervals %}
<li><a class="dropdown-item {{ "active" if interval.is_active }}" onclick="togglePackageInfoAutoReload({{ interval.interval }})" data-interval="{{ interval.interval }}">{{ interval.text }}</a></li>
{% endfor %}
</ul>
</div>
{% endif %}
<button type="button" class="btn btn-primary" data-bs-dismiss="modal"><i class="bi bi-x"></i><span class="d-none d-sm-inline"> close</span></button>
</div>
</div>
@ -143,6 +155,7 @@
const packageInfoRefreshInput = document.getElementById("package-info-refresh-input");
const packageInfoAutoReloadButton = document.getElementById("package-info-autoreload-button");
const packageInfoAutoReloadInput = document.getElementById("package-info-autoreload-input");
let packageInfoAutoReloadTask = null;
function clearChart() {
@ -477,22 +490,22 @@
if (isPackageBaseSet) {
bootstrap.Modal.getOrCreateInstance(packageInfoModal).show();
togglePackageInfoAutoReload();
{% if autorefresh_intervals %}
togglePackageInfoAutoReload();
{% endif %}
}
}
function togglePackageInfoAutoReload() {
function togglePackageInfoAutoReload(interval) {
clearInterval(packageInfoAutoReloadTask);
if (packageInfoAutoReloadButton.checked) {
packageInfoAutoReloadTask = setInterval(_ => {
if (!hasActiveSelection()) {
const packageBase = packageInfoModal.dataset.package;
// we only poll status and logs here
loadPackage(packageBase);
reloadActiveLogs(packageBase);
}
}, 5000);
}
packageInfoAutoReloadTask = toggleAutoReload(packageInfoAutoReloadButton, interval, packageInfoAutoReloadInput, _ => {
if (!hasActiveSelection()) {
const packageBase = packageInfoModal.dataset.package;
// we only poll status and logs here
loadPackage(packageBase);
reloadActiveLogs(packageBase);
}
});
}
ready(_ => {

View File

@ -10,6 +10,10 @@
const dashboardButton = document.getElementById("dashboard-button");
const versionBadge = document.getElementById("badge-version");
const tableAutoReloadButton = document.getElementById("table-autoreload-button");
const tableAutoReloadInput = document.getElementById("table-autoreload-input");
let tableAutoReloadTask = null;
function doPackageAction(uri, packages, repository, successText, failureText, data) {
makeRequest(
uri,
@ -86,8 +90,10 @@
doPackageAction("/api/v1/service/update", [], repository, onSuccess, onFailure, parameters);
}
function reload() {
table.bootstrapTable("showLoading");
function reload(silent) {
if (!silent) {
table.bootstrapTable("showLoading");
}
const badgeClass = status => {
if (status === "pending") return "btn-outline-warning";
@ -128,16 +134,18 @@
table.bootstrapTable("hideLoading");
},
error => {
if ((error.status === 401) || (error.status === 403)) {
// authorization error
const text = "In order to see statuses you must login first.";
table.find("tr.unauthorized").remove();
table.find("tbody").append(`<tr class="unauthorized"><td colspan="100%">${safe(text)}</td></tr>`);
table.bootstrapTable("hideLoading");
} else {
// other errors
const message = details => `Could not load list of packages: ${details}`;
showFailure("Load failure", message, error);
if (!silent) {
if ((error.status === 401) || (error.status === 403)) {
// authorization error
const text = "In order to see statuses you must login first.";
table.find("tr.unauthorized").remove();
table.find("tbody").append(`<tr class="unauthorized"><td colspan="100%">${safe(text)}</td></tr>`);
table.bootstrapTable("hideLoading");
} else {
// other errors
const message = details => `Could not load list of packages: ${details}`;
showFailure("Load failure", message, error);
}
}
},
);
@ -230,6 +238,15 @@
return {classes: cellClass(value)};
}
function toggleTableAutoReload(interval) {
clearInterval(tableAutoReloadTask);
tableAutoReloadTask = toggleAutoReload(tableAutoReloadButton, interval, tableAutoReloadInput, _ => {
if (getSelection().length === 0) {
reload(true);
}
});
}
ready(_ => {
document.querySelectorAll("#repositories a").forEach(element => {
element.onclick = _ => {
@ -289,5 +306,8 @@
});
selectRepository();
{% if autorefresh_intervals %}
toggleTableAutoReload();
{% endif %}
});
</script>

View File

@ -137,6 +137,28 @@
return element;
}
function toggleAutoReload(toggle, interval, intervalSelector, callback) {
if (interval) {
toggle.checked = true; // toggle reload
} else {
interval = intervalSelector.querySelector(".active")?.dataset?.interval; // find active element
}
if (interval) {
if (toggle.checked) {
// refresh UI
Array.from(intervalSelector.children).forEach(il => {
Array.from(il.children).forEach(el => el.classList.remove("active"));
});
intervalSelector.querySelector(`a[data-interval="${interval}"]`)?.classList?.add("active");
// finally create timer task
return setInterval(callback, interval);
}
}
return null; // return null to assign to keep method sane
}
Date.prototype.toISOStringShort = function() {
const pad = number => String(number).padStart(2, "0");
return `${this.getFullYear()}-${pad(this.getMonth() + 1)}-${pad(this.getDate())} ${pad(this.getHours())}:${pad(this.getMinutes())}:${pad(this.getSeconds())}`;