mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-04-24 15:27:17 +00:00
466 lines
22 KiB
Django/Jinja
466 lines
22 KiB
Django/Jinja
<div id="package-info-modal" tabindex="-1" role="dialog" class="modal fade">
|
|
<div class="modal-dialog modal-xl" role="document">
|
|
<div class="modal-content">
|
|
<div id="package-info-modal-header" class="modal-header">
|
|
<h4 id="package-info" class="modal-title"></h4>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="close"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="form-group row mt-2">
|
|
<div class="col-4 col-lg-1" style="text-align: right">packages</div>
|
|
<div id="package-info-packages" class="col-8 col-lg-5"></div>
|
|
<div class="col-4 col-lg-1" style="text-align: right">version</div>
|
|
<div id="package-info-version" class="col-8 col-lg-5"></div>
|
|
</div>
|
|
|
|
<div class="form-group row mt-2">
|
|
<div class="col-4 col-lg-1" style="text-align: right">packager</div>
|
|
<div id="package-info-packager" class="col-8 col-lg-5"></div>
|
|
<div class="col-4 col-lg-1" style="text-align: right"></div>
|
|
<div id="package-info---placeholder" class="col-8 col-lg-5"></div>
|
|
</div>
|
|
|
|
<div class="form-group row mt-2">
|
|
<div class="col-4 col-lg-1" style="text-align: right">groups</div>
|
|
<div id="package-info-groups" class="col-8 col-lg-5"></div>
|
|
<div class="col-4 col-lg-1" style="text-align: right">licenses</div>
|
|
<div id="package-info-licenses" class="col-8 col-lg-5"></div>
|
|
</div>
|
|
|
|
<div class="form-group row mt-2">
|
|
<div class="col-4 col-lg-1" style="text-align: right">upstream</div>
|
|
<div id="package-info-upstream-url" class="col-8 col-lg-5"></div>
|
|
<div class="col-4 col-lg-1" style="text-align: right">AUR</div>
|
|
<div id="package-info-aur-url" class="col-8 col-lg-5"></div>
|
|
</div>
|
|
|
|
<div class="form-group row mt-2">
|
|
<div class="col-4 col-lg-1" style="text-align: right">depends</div>
|
|
<div id="package-info-depends" class="col-8 col-lg-5"></div>
|
|
<div class="col-4 col-lg-1" style="text-align: right">implicitly depends</div>
|
|
<div id="package-info-implicitly-depends" class="col-8 col-lg-5"></div>
|
|
</div>
|
|
|
|
<hr class="col-12">
|
|
|
|
<div id="package-info-variables-block" hidden>
|
|
<h3>Environment variables</h3>
|
|
<div id="package-info-variables-div" class="form-group row"></div>
|
|
|
|
<hr class="col-12">
|
|
</div>
|
|
|
|
<nav>
|
|
<div class="nav nav-tabs" role="tablist">
|
|
<button id="package-info-logs-button" class="nav-link active" data-bs-toggle="tab" data-bs-target="#package-info-logs" type="button" role="tab" aria-controls="package-info-logs" aria-selected="true">Build logs</button>
|
|
<button id="package-info-changes-button" class="nav-link" data-bs-toggle="tab" data-bs-target="#package-info-changes" type="button" role="tab" aria-controls="package-info-changes" aria-selected="false">Changes</button>
|
|
<button id="package-info-events-button" class="nav-link" data-bs-toggle="tab" data-bs-target="#package-info-events" type="button" role="tab" aria-controls="package-info-events" aria-selected="false">Events</button>
|
|
</div>
|
|
</nav>
|
|
<div class="tab-content" id="nav-tabContent">
|
|
<div id="package-info-logs" class="tab-pane fade show active" role="tabpanel" aria-labelledby="package-info-logs-button" tabindex="0">
|
|
<div class="row">
|
|
<div class="col-2">
|
|
<nav id="package-info-logs-versions" class="nav flex-column"></nav>
|
|
</div>
|
|
<div class="col-10">
|
|
<pre class="language-console"><code id="package-info-logs-input" class="pre-scrollable language-console"></code><button id="package-info-logs-copy-button" type="button" class="btn language-console" onclick="copyLogs()"><i class="bi bi-clipboard"></i> copy</button></pre>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div id="package-info-changes" class="tab-pane fade" role="tabpanel" aria-labelledby="package-info-changes-button" tabindex="0">
|
|
<pre class="language-diff"><code id="package-info-changes-input" class="pre-scrollable language-diff"></code><button id="package-info-changes-copy-button" type="button" class="btn language-diff" onclick="copyChanges()"><i class="bi bi-clipboard"></i> copy</button></pre>
|
|
</div>
|
|
<div id="package-info-events" class="tab-pane fade" role="tabpanel" aria-labelledby="package-info-events-button" tabindex="0">
|
|
<canvas id="package-info-events-update-chart" hidden></canvas>
|
|
<table id="package-info-events-table"
|
|
data-classes="table table-hover"
|
|
data-sortable="true"
|
|
data-sort-name="timestamp"
|
|
data-sort-order="desc"
|
|
data-toggle="table">
|
|
<thead class="table-primary">
|
|
<tr>
|
|
<th data-align="right" data-field="timestamp">date</th>
|
|
<th data-field="event">event</th>
|
|
<th data-field="message">description</th>
|
|
</tr>
|
|
</thead>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
{% if not auth.enabled or auth.username is not none %}
|
|
<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 %}
|
|
<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>
|
|
<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>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
const packageInfoModal = document.getElementById("package-info-modal");
|
|
const packageInfoModalHeader = document.getElementById("package-info-modal-header");
|
|
const packageInfo = document.getElementById("package-info");
|
|
|
|
const packageInfoLogsVersions = document.getElementById("package-info-logs-versions");
|
|
const packageInfoLogsInput = document.getElementById("package-info-logs-input");
|
|
const packageInfoLogsCopyButton = document.getElementById("package-info-logs-copy-button");
|
|
|
|
const packageInfoChangesInput = document.getElementById("package-info-changes-input");
|
|
const packageInfoChangesCopyButton = document.getElementById("package-info-changes-copy-button");
|
|
|
|
// so far bootstrap-table only operates with jquery elements
|
|
const packageInfoEventsTable = $(document.getElementById("package-info-events-table"));
|
|
const packageInfoEventsUpdateChartCanvas = document.getElementById("package-info-events-update-chart");
|
|
let packageInfoEventsUpdateChart = null;
|
|
|
|
const packageInfoAurUrl = document.getElementById("package-info-aur-url");
|
|
const packageInfoDepends = document.getElementById("package-info-depends");
|
|
const packageInfoGroups = document.getElementById("package-info-groups");
|
|
const packageInfoImplicitlyDepends = document.getElementById("package-info-implicitly-depends");
|
|
const packageInfoLicenses = document.getElementById("package-info-licenses");
|
|
const packageInfoPackager = document.getElementById("package-info-packager");
|
|
const packageInfoPackages = document.getElementById("package-info-packages");
|
|
const packageInfoUpstreamUrl = document.getElementById("package-info-upstream-url");
|
|
const packageInfoVersion = document.getElementById("package-info-version");
|
|
|
|
const packageInfoVariablesBlock = document.getElementById("package-info-variables-block");
|
|
const packageInfoVariablesDiv = document.getElementById("package-info-variables-div");
|
|
|
|
function clearChart() {
|
|
packageInfoEventsUpdateChartCanvas.hidden = true;
|
|
if (packageInfoEventsUpdateChart) {
|
|
packageInfoEventsUpdateChart.data = {};
|
|
packageInfoEventsUpdateChart.update();
|
|
}
|
|
}
|
|
|
|
async function copyChanges() {
|
|
const changes = packageInfoChangesInput.textContent;
|
|
await copyToClipboard(changes, packageInfoChangesCopyButton);
|
|
}
|
|
|
|
async function copyLogs() {
|
|
const logs = packageInfoLogsInput.textContent;
|
|
await copyToClipboard(logs, packageInfoLogsCopyButton);
|
|
}
|
|
|
|
function highlight(element) {
|
|
delete element.dataset.highlighted;
|
|
hljs.highlightElement(element);
|
|
}
|
|
|
|
function insertVariable(packageBase, variable) {
|
|
const variableInput = document.createElement("div");
|
|
variableInput.classList.add("input-group");
|
|
|
|
const variableNameInput = document.createElement("input");
|
|
variableNameInput.classList.add("form-control");
|
|
variableNameInput.readOnly = true;
|
|
variableNameInput.value = variable.key;
|
|
|
|
const variableSeparator = document.createElement("span");
|
|
variableSeparator.classList.add("input-group-text")
|
|
variableSeparator.textContent = "=";
|
|
|
|
const variableValueInput = document.createElement("input");
|
|
variableValueInput.classList.add("form-control");
|
|
variableValueInput.readOnly = true;
|
|
variableValueInput.value = JSON.stringify(variable.value);
|
|
|
|
const variableButtonRemove = document.createElement("button");
|
|
variableButtonRemove.type = "button";
|
|
variableButtonRemove.classList.add("btn");
|
|
variableButtonRemove.classList.add("btn-outline-danger");
|
|
variableButtonRemove.innerHTML = "<i class=\"bi bi-trash\"></i>";
|
|
variableButtonRemove.onclick = _ => {
|
|
makeRequest(
|
|
`/api/v1/packages/${packageBase}/patches/${variable.key}`,
|
|
{
|
|
method: "DELETE",
|
|
},
|
|
_ => variableInput.remove(),
|
|
);
|
|
};
|
|
|
|
// bring them together
|
|
variableInput.appendChild(variableNameInput);
|
|
variableInput.appendChild(variableSeparator);
|
|
variableInput.appendChild(variableValueInput);
|
|
variableInput.appendChild(variableButtonRemove);
|
|
|
|
packageInfoVariablesDiv.appendChild(variableInput);
|
|
}
|
|
|
|
function loadChanges(packageBase, onFailure) {
|
|
makeRequest(
|
|
`/api/v1/packages/${packageBase}/changes`,
|
|
{
|
|
query: {
|
|
architecture: repository.architecture,
|
|
repository: repository.repository,
|
|
},
|
|
convert: response => response.json(),
|
|
},
|
|
data => {
|
|
const changes = data.changes;
|
|
packageInfoChangesInput.textContent = changes ?? "";
|
|
highlight(packageInfoChangesInput);
|
|
},
|
|
onFailure,
|
|
);
|
|
}
|
|
|
|
function loadDependencies(packageBase, onFailure) {
|
|
makeRequest(
|
|
`/api/v1/packages/${packageBase}/dependencies`,
|
|
{
|
|
query: {
|
|
architecture: repository.architecture,
|
|
repository: repository.repository,
|
|
},
|
|
convert: response => response.json(),
|
|
},
|
|
data => {
|
|
packageInfoImplicitlyDepends.innerHTML = listToTable(
|
|
Object.values(data.paths)
|
|
.reduce((accumulator, currentValue) => accumulator.concat(currentValue), [])
|
|
);
|
|
},
|
|
onFailure,
|
|
);
|
|
}
|
|
|
|
function loadEvents(packageBase, onFailure) {
|
|
packageInfoEventsTable.bootstrapTable("showLoading");
|
|
clearChart();
|
|
|
|
makeRequest(
|
|
"/api/v1/events",
|
|
{
|
|
query: {
|
|
architecture: repository.architecture,
|
|
repository: repository.repository,
|
|
object_id: packageBase,
|
|
limit: 30,
|
|
},
|
|
convert: response => response.json(),
|
|
},
|
|
data => {
|
|
const events = data.map(event => {
|
|
return {
|
|
timestamp: new Date(1000 * event.created).toISOStringShort(),
|
|
event: event.event,
|
|
message: event.message || "",
|
|
};
|
|
});
|
|
const chart = data.filter(event => event.event === "package-updated");
|
|
|
|
packageInfoEventsTable.bootstrapTable("load", events);
|
|
packageInfoEventsTable.bootstrapTable("hideLoading");
|
|
|
|
if (packageInfoEventsUpdateChart) {
|
|
packageInfoEventsUpdateChart.config.data = {
|
|
labels: chart.map(event => new Date(1000 * event.created).toISOStringShort()),
|
|
datasets: [{
|
|
label: "update duration, s",
|
|
data: chart.map(event => event.data.took),
|
|
cubicInterpolationMode: "monotone",
|
|
tension: 0.4,
|
|
}],
|
|
};
|
|
packageInfoEventsUpdateChart.update();
|
|
}
|
|
packageInfoEventsUpdateChartCanvas.hidden = !chart.length;
|
|
},
|
|
onFailure,
|
|
);
|
|
}
|
|
|
|
function loadLogs(packageBase, onFailure) {
|
|
makeRequest(
|
|
`/api/v2/packages/${packageBase}/logs`,
|
|
{
|
|
query: {
|
|
architecture: repository.architecture,
|
|
repository: repository.repository,
|
|
},
|
|
convert: response => response.json(),
|
|
},
|
|
data => {
|
|
const selectors = Object
|
|
.values(
|
|
data.reduce((acc, log_record) => {
|
|
const id = `${log_record.version}-${log_record.process_id}`;
|
|
if (acc[id])
|
|
acc[id].created = Math.min(log_record.created, acc[id].created);
|
|
else
|
|
acc[id] = log_record;
|
|
return acc;
|
|
}, {})
|
|
)
|
|
.sort(({created: left}, {created: right}) =>
|
|
right - left
|
|
)
|
|
.map(version => {
|
|
const link = document.createElement("a");
|
|
link.classList.add("nav-link");
|
|
|
|
link.textContent = new Date(1000 * version.created).toISOStringShort();
|
|
link.href = "#";
|
|
link.onclick = _ => {
|
|
const logs = data
|
|
.filter(log_record => log_record.version === version.version && log_record.process_id === version.process_id)
|
|
.map(log_record => `[${new Date(1000 * log_record.created).toISOString()}] ${log_record.message}`);
|
|
|
|
packageInfoLogsInput.textContent = logs.join("\n");
|
|
highlight(packageInfoLogsInput);
|
|
|
|
Array.from(packageInfoLogsVersions.children).forEach(el => el.classList.remove("active"));
|
|
link.classList.add("active");
|
|
|
|
return false;
|
|
};
|
|
|
|
return link;
|
|
});
|
|
|
|
packageInfoLogsVersions.replaceChildren(...selectors);
|
|
selectors.find(Boolean)?.click();
|
|
},
|
|
onFailure,
|
|
);
|
|
}
|
|
|
|
function loadPackage(packageBase, onFailure) {
|
|
makeRequest(
|
|
`/api/v1/packages/${packageBase}`,
|
|
{
|
|
query: {
|
|
architecture: repository.architecture,
|
|
repository: repository.repository,
|
|
},
|
|
convert: response => response.json(),
|
|
},
|
|
data => {
|
|
const description = data.find(Boolean);
|
|
const packages = description.package.packages;
|
|
const aurUrl = description.package.remote.web_url;
|
|
const upstreamUrls = Array.from(new Set(Object.values(packages).map(single => single.url))).sort();
|
|
|
|
packageInfo.textContent = `${description.package.base} ${description.status.status} at ${new Date(1000 * description.status.timestamp).toISOStringShort()}`;
|
|
|
|
packageInfoModalHeader.classList.remove(...packageInfoModalHeader.classList);
|
|
packageInfoModalHeader.classList.add("modal-header");
|
|
headerClass(description.status.status).forEach(clz => packageInfoModalHeader.classList.add(clz));
|
|
|
|
packageInfoAurUrl.innerHTML = aurUrl ? safeLink(aurUrl, aurUrl, "AUR link").outerHTML : "";
|
|
packageInfoDepends.innerHTML = listToTable(
|
|
Object.values(packages)
|
|
.reduce((accumulator, currentValue) => {
|
|
return accumulator.concat(currentValue.depends.filter(v => !packages.hasOwnProperty(v)))
|
|
.concat(currentValue.make_depends.filter(v => !packages.hasOwnProperty(v)).map(v => `${v} (make)`))
|
|
.concat(currentValue.opt_depends.filter(v => !packages.hasOwnProperty(v)).map(v => `${v} (optional)`));
|
|
}, [])
|
|
);
|
|
packageInfoGroups.innerHTML = listToTable(extractListProperties(description.package, "groups"));
|
|
packageInfoLicenses.innerHTML = listToTable(extractListProperties(description.package, "licenses"));
|
|
packageInfoPackager.textContent = description.package.packager;
|
|
packageInfoPackages.innerHTML = listToTable(Object.entries(packages).map(([key, value]) => `${key} (${value.description})`));
|
|
packageInfoUpstreamUrl.innerHTML = upstreamUrls.map(url => safeLink(url, url, "upstream link").outerHTML).join("<br>");
|
|
packageInfoVersion.textContent = description.package.version;
|
|
},
|
|
onFailure,
|
|
);
|
|
}
|
|
|
|
function loadPatches(packageBase, onFailure) {
|
|
makeRequest(
|
|
`/api/v1/packages/${packageBase}/patches`,
|
|
{
|
|
convert: response => response.json(),
|
|
},
|
|
data => {
|
|
packageInfoVariablesDiv.replaceChildren();
|
|
data.map(patch => insertVariable(packageBase, patch));
|
|
packageInfoVariablesBlock.hidden = !data.length;
|
|
},
|
|
onFailure,
|
|
);
|
|
}
|
|
|
|
function packageInfoRemove() {
|
|
const packageBase = packageInfoModal.package;
|
|
packagesRemove([packageBase]);
|
|
}
|
|
|
|
function packageInfoUpdate() {
|
|
const packageBase = packageInfoModal.package;
|
|
packagesAdd(packageBase, [], repository);
|
|
}
|
|
|
|
function showPackageInfo(packageBase) {
|
|
const isPackageBaseSet = packageBase !== undefined;
|
|
if (isPackageBaseSet) {
|
|
// set package base as currently used
|
|
packageInfoModal.package = packageBase;
|
|
} else {
|
|
// read package base from the current window attribute
|
|
packageBase = packageInfoModal.package;
|
|
}
|
|
|
|
const onFailure = error => {
|
|
if (isPackageBaseSet) {
|
|
const message = details => `Could not load package ${packageBase} info: ${details}`;
|
|
showFailure("Load failure", message, error);
|
|
}
|
|
};
|
|
|
|
loadPackage(packageBase, onFailure);
|
|
loadDependencies(packageBase, onFailure);
|
|
loadPatches(packageBase, onFailure);
|
|
loadLogs(packageBase, onFailure);
|
|
loadChanges(packageBase, onFailure);
|
|
loadEvents(packageBase, onFailure);
|
|
|
|
if (isPackageBaseSet) {
|
|
bootstrap.Modal.getOrCreateInstance(packageInfoModal).show();
|
|
}
|
|
}
|
|
|
|
ready(_ => {
|
|
packageInfoEventsUpdateChart = new Chart(packageInfoEventsUpdateChartCanvas, {
|
|
type: "line",
|
|
data: {},
|
|
options: {
|
|
responsive: true,
|
|
},
|
|
});
|
|
|
|
packageInfoModal.addEventListener("hidden.bs.modal", _ => {
|
|
packageInfoAurUrl.textContent = "";
|
|
packageInfoDepends.textContent = "";
|
|
packageInfoGroups.textContent = "";
|
|
packageInfoImplicitlyDepends.textContent = "";
|
|
packageInfoLicenses.textContent = "";
|
|
packageInfoPackager.textContent = "";
|
|
packageInfoPackages.textContent = "";
|
|
packageInfoUpstreamUrl.textContent = "";
|
|
packageInfoVersion.textContent = "";
|
|
|
|
packageInfoVariablesBlock.hidden = true;
|
|
packageInfoVariablesDiv.replaceChildren();
|
|
|
|
packageInfoLogsInput.textContent = "";
|
|
packageInfoChangesInput.textContent = "";
|
|
packageInfoEventsTable.bootstrapTable("load", []);
|
|
clearChart();
|
|
});
|
|
});
|
|
</script>
|