mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-07-15 15:05:48 +00:00
246 lines
10 KiB
Django/Jinja
246 lines
10 KiB
Django/Jinja
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js" crossorigin="anonymous" type="application/javascript"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/js-md5@0.8.3/src/md5.min.js" crossorigin="anonymous" type="application/javascript"></script>
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/tableexport.jquery.plugin@1.33.0/tableExport.min.js" crossorigin="anonymous" type="application/javascript"></script>
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/jquery-resizable-columns@0.2.3/dist/jquery.resizableColumns.min.js" crossorigin="anonymous" type="application/javascript"></script>
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js" crossorigin="anonymous" type="application/javascript"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/js/bootstrap.min.js" crossorigin="anonymous" type="application/javascript"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.24.1/dist/bootstrap-table.min.js" crossorigin="anonymous" type="application/javascript"></script>
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.24.1/dist/extensions/export/bootstrap-table-export.min.js" crossorigin="anonymous" type="application/javascript"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.24.1/dist/extensions/resizable/bootstrap-table-resizable.js" crossorigin="anonymous" type="application/javascript"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.24.1/dist/extensions/filter-control/bootstrap-table-filter-control.js" crossorigin="anonymous" type="application/javascript"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.24.1/dist/extensions/cookie/bootstrap-table-cookie.min.js" crossorigin="anonymous" type="application/javascript"></script>
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/@easepick/bundle@1.2.1/dist/index.umd.min.js" crossorigin="anonymous" type="application/javascript"></script>
|
|
|
|
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.11.1/build/highlight.min.js" crossorigin="anonymous" type="application/javascript"></script>
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.5.0/dist/chart.umd.min.js" crossorigin="anonymous" type="application/javascript"></script>
|
|
|
|
<script>
|
|
async function copyToClipboard(text, button) {
|
|
await navigator.clipboard.writeText(text);
|
|
button.innerHTML = "<i class=\"bi bi-clipboard-check\"></i> copied";
|
|
setTimeout(_ => {
|
|
button.innerHTML = "<i class=\"bi bi-clipboard\"></i> copy";
|
|
}, 2000);
|
|
}
|
|
|
|
function extractDataList(data, column) {
|
|
const elements = data.flatMap(row => row[column].split("<br>")).filter(v => v); // remove empty elements from array
|
|
return Array.from(new Set(elements)).sort();
|
|
}
|
|
|
|
function extractListProperties(description, property) {
|
|
return Object.values(description.packages)
|
|
.map(pkg => pkg[property])
|
|
.reduce((left, right) => left.concat(right), []);
|
|
}
|
|
|
|
function filterContains(text, value) {
|
|
return value.includes(text.toLowerCase().trim());
|
|
}
|
|
|
|
function filterDateRange(text, value) {
|
|
const asOfStartOfDay = date => date.setUTCHours(0, 0, 0, 0);
|
|
|
|
const [minDate, maxDate] = text.split(" - ");
|
|
const buildDate = asOfStartOfDay(new Date(value));
|
|
|
|
return (buildDate >= new Date(minDate)) && (buildDate <= new Date(maxDate));
|
|
}
|
|
|
|
function filterList(index, value, field, data) {
|
|
const dataList = extractDataList(data, field);
|
|
// the library removes all symbols from string, so it is just string
|
|
return value.includes(dataList[index].toLowerCase());
|
|
}
|
|
|
|
function hasActiveSelection() {
|
|
return !document.getSelection().isCollapsed; // not sure if it is a valid way, but I guess so
|
|
}
|
|
|
|
function hasActiveDropdown() {
|
|
return Array.from(document.querySelectorAll(".dropdown-menu"))
|
|
.some(el => el.classList.contains("show"));
|
|
}
|
|
|
|
function hasActiveModal() {
|
|
return Array.from(document.querySelectorAll(".modal"))
|
|
.some(el => el.classList.contains("show"));
|
|
}
|
|
|
|
function headerClass(status) {
|
|
if (status === "pending") return ["bg-warning"];
|
|
if (status === "building") return ["bg-warning"];
|
|
if (status === "failed") return ["bg-danger", "text-white"];
|
|
if (status === "success") return ["bg-success", "text-white"];
|
|
return ["bg-secondary", "text-white"];
|
|
}
|
|
|
|
function listToTable(data) {
|
|
return Array.from(new Set(data))
|
|
.sort()
|
|
.map(entry => safe(entry))
|
|
.join("<br>");
|
|
}
|
|
|
|
function makeRequest(url, params, onSuccess, onFailure) {
|
|
const requestParams = {
|
|
method: params.method,
|
|
body: params.json ? JSON.stringify(params.json) : params.json,
|
|
headers: {
|
|
"Accept": "application/json",
|
|
"Content-Type": "application/json",
|
|
},
|
|
};
|
|
if (params.query) {
|
|
const query = new URLSearchParams(params.query);
|
|
url += `?${query.toString()}`;
|
|
}
|
|
const convert = params.convert ?? (response => response.text());
|
|
|
|
return fetch(url, requestParams)
|
|
.then(response => {
|
|
if (response.ok) {
|
|
return convert(response);
|
|
} else {
|
|
const error = new Error("Network request error");
|
|
error.status = response.status;
|
|
error.statusText = response.statusText;
|
|
return response.text().then(text => {
|
|
error.text = text;
|
|
throw error;
|
|
});
|
|
}
|
|
})
|
|
.then(data => onSuccess && onSuccess(data))
|
|
.catch(error => onFailure && onFailure(error));
|
|
}
|
|
|
|
function readOptional(extractor, callback) {
|
|
for (let value = extractor(); !!value; value = null) {
|
|
callback(value);
|
|
}
|
|
}
|
|
|
|
function ready(fn) {
|
|
if (document.readyState === "complete" || document.readyState === "interactive") {
|
|
setTimeout(fn, 1);
|
|
} else {
|
|
document.addEventListener("DOMContentLoaded", fn);
|
|
}
|
|
}
|
|
|
|
function restoreAutoReloadSettings(toggle, intervalSelector) {
|
|
readOptional(() => localStorage.getItem(`ahriman-${toggle.id}-refresh-enabled`), value => toggle.checked = value === "true");
|
|
readOptional(() => localStorage.getItem(`ahriman-${toggle.id}-refresh-interval`), value => toggleActiveElement(intervalSelector, "interval", value));
|
|
}
|
|
|
|
function safe(string) {
|
|
return String(string)
|
|
.replace(/&/g, "&")
|
|
.replace(/</g, "<")
|
|
.replace(/>/g, ">")
|
|
.replace(/"/g, """);
|
|
}
|
|
|
|
// because I'm tired of safe element generation
|
|
function safeLink(url, text, title) {
|
|
const element = document.createElement("a");
|
|
element.href = url;
|
|
element.innerText = text;
|
|
if (title) {
|
|
element.title = title;
|
|
}
|
|
return element;
|
|
}
|
|
|
|
function toggleActiveElement(selector, dataType, value) {
|
|
const targetElement = selector.querySelector(`a[data-${dataType}="${value}"]`);
|
|
if (targetElement?.classList?.contains("active")) {
|
|
return; // element is already active, skip processing
|
|
}
|
|
|
|
Array.from(selector.children).forEach(il => {
|
|
Array.from(il.children).forEach(el => el.classList.remove("active"));
|
|
});
|
|
targetElement?.classList?.add("active");
|
|
}
|
|
|
|
function toggleAutoReload(toggle, interval, intervalSelector, callback) {
|
|
if (interval) {
|
|
toggle.checked = true; // toggle reload
|
|
} else {
|
|
interval = intervalSelector.querySelector(".active")?.dataset?.interval; // find active element
|
|
}
|
|
|
|
let intervalId = null;
|
|
if (interval) {
|
|
if (toggle.checked) {
|
|
// refresh UI
|
|
toggleActiveElement(intervalSelector, "interval", interval);
|
|
// finally create timer task
|
|
intervalId = setInterval(callback, interval);
|
|
}
|
|
} else {
|
|
toggle.checked = false; // no active interval found, disable toggle
|
|
}
|
|
|
|
localStorage.setItem(`ahriman-${toggle.id}-refresh-enabled`, toggle.checked);
|
|
localStorage.setItem(`ahriman-${toggle.id}-refresh-interval`, interval);
|
|
return intervalId;
|
|
}
|
|
|
|
function updateTable(table, rows) {
|
|
// instead of using load method here, we just update rows manually to avoid table reinitialization
|
|
const currentData = table.bootstrapTable("getData").reduce((accumulator, row) => {
|
|
accumulator[row.id] = row["0"];
|
|
return accumulator;
|
|
}, {});
|
|
// insert or update rows
|
|
rows.forEach(row => {
|
|
if (Object.hasOwn(currentData, row.id)) {
|
|
row["0"] = currentData[row.id]; // copy checkbox state
|
|
table.bootstrapTable("updateByUniqueId", {
|
|
id: row.id,
|
|
row: row,
|
|
replace: true,
|
|
});
|
|
} else {
|
|
table.bootstrapTable("insertRow", {index: 0, row: row});
|
|
}
|
|
});
|
|
// remove old rows
|
|
const newData = rows.map(value => value.id);
|
|
Object.keys(currentData).forEach(id => {
|
|
if (!newData.includes(id)) {
|
|
table.bootstrapTable("removeByUniqueId", id);
|
|
}
|
|
});
|
|
}
|
|
|
|
Array.prototype.equals = function (right, comparator) {
|
|
let index = this.length;
|
|
if (index !== right.length) {
|
|
return false;
|
|
}
|
|
|
|
while (index--) {
|
|
if (!comparator(this[index], right[index])) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
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())}`;
|
|
}
|
|
</script>
|