mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-05-06 05:03:49 +00:00
Compare commits
20 Commits
1d85a61cc4
...
013ba3d3ab
Author | SHA1 | Date | |
---|---|---|---|
013ba3d3ab | |||
6099a5957d | |||
41343fd9e1 | |||
a576a0b612 | |||
05562d2ee5 | |||
3098132de2 | |||
4e246d3a67 | |||
6577ca9db1 | |||
6e37a60cf0 | |||
a23a1bc613 | |||
fc508e19b8 | |||
09c8fd945d | |||
b90d93f3c0 | |||
cd98b7f6e6 | |||
08c1b08902 | |||
a9003993fa | |||
54a331cc96 | |||
5f79cbc34b | |||
ea4193eef4 | |||
40fa94afbb |
File diff suppressed because it is too large
Load Diff
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.2 MiB |
@ -1,7 +1,7 @@
|
|||||||
# Maintainer: Evgeniy Alekseev
|
# Maintainer: Evgeniy Alekseev
|
||||||
|
|
||||||
pkgname='ahriman'
|
pkgname='ahriman'
|
||||||
pkgver=2.14.1
|
pkgver=2.14.0
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="ArcH linux ReposItory MANager"
|
pkgdesc="ArcH linux ReposItory MANager"
|
||||||
arch=('any')
|
arch=('any')
|
||||||
|
@ -44,28 +44,28 @@
|
|||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
<li>
|
<li>
|
||||||
<button id="package-add-button" class="btn dropdown-item" data-bs-toggle="modal" data-bs-target="#package-add-modal">
|
<button id="package-add-button" class="btn dropdown-item" data-bs-toggle="modal" data-bs-target="#package-add-modal" hidden>
|
||||||
<i class="bi bi-plus"></i> add
|
<i class="bi bi-plus"></i> add
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<button id="package-update-button" class="btn dropdown-item" onclick="packagesUpdate()">
|
<button id="package-update-button" class="btn dropdown-item" onclick="packagesUpdate()" hidden>
|
||||||
<i class="bi bi-play"></i> update
|
<i class="bi bi-play"></i> update
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<button id="package-rebuild-button" class="btn dropdown-item" data-bs-toggle="modal" data-bs-target="#package-rebuild-modal">
|
<button id="package-rebuild-button" class="btn dropdown-item" data-bs-toggle="modal" data-bs-target="#package-rebuild-modal" hidden>
|
||||||
<i class="bi bi-arrow-clockwise"></i> rebuild
|
<i class="bi bi-arrow-clockwise"></i> rebuild
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<button id="package-remove-button" class="btn dropdown-item" onclick="packagesRemove()" disabled>
|
<button id="package-remove-button" class="btn dropdown-item" onclick="packagesRemove()" disabled hidden>
|
||||||
<i class="bi bi-trash"></i> remove
|
<i class="bi bi-trash"></i> remove
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<button id="key-import-button" type="button" class="btn btn-info" data-bs-toggle="modal" data-bs-target="#key-import-modal">
|
<button id="key-import-button" type="button" class="btn btn-info" data-bs-toggle="modal" data-bs-target="#key-import-modal" hidden>
|
||||||
<i class="bi bi-key"></i><span class="d-none d-sm-inline"> import key</span>
|
<i class="bi bi-key"></i><span class="d-none d-sm-inline"> import key</span>
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
<script>
|
<script>
|
||||||
const alertPlaceholder = document.getElementById("alert-placeholder");
|
const alertPlaceholder = $("#alert-placeholder");
|
||||||
|
|
||||||
function createAlert(title, message, clz, action, id) {
|
function createAlert(title, message, clz, action, id) {
|
||||||
id ??= md5(title + message); // MD5 id from the content
|
if (!id) id = $.md5(title + message); // MD5 id from the content
|
||||||
if (alertPlaceholder.querySelector(`#alert-${id}`)) return; // check if there are duplicates
|
if (alertPlaceholder.find(`#${id}`).length > 0) return; // check if there are duplicates
|
||||||
|
|
||||||
const wrapper = document.createElement("div");
|
const wrapper = document.createElement("div");
|
||||||
wrapper.id = `alert-${id}`;
|
wrapper.id = id;
|
||||||
wrapper.classList.add("toast", clz);
|
wrapper.classList.add("toast", clz);
|
||||||
wrapper.role = "alert";
|
wrapper.role = "alert";
|
||||||
wrapper.ariaLive = "assertive";
|
wrapper.ariaLive = "assertive";
|
||||||
@ -23,7 +23,7 @@
|
|||||||
body.innerText = message;
|
body.innerText = message;
|
||||||
wrapper.appendChild(body);
|
wrapper.appendChild(body);
|
||||||
|
|
||||||
alertPlaceholder.appendChild(wrapper);
|
alertPlaceholder.append(wrapper);
|
||||||
const toast = new bootstrap.Toast(wrapper);
|
const toast = new bootstrap.Toast(wrapper);
|
||||||
wrapper.addEventListener("hidden.bs.toast", _ => {
|
wrapper.addEventListener("hidden.bs.toast", _ => {
|
||||||
wrapper.remove(); // bootstrap doesn't remove elements
|
wrapper.remove(); // bootstrap doesn't remove elements
|
||||||
@ -32,12 +32,12 @@
|
|||||||
toast.show();
|
toast.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
function showFailure(title, description, error) {
|
function showFailure(title, description, jqXHR, errorThrown) {
|
||||||
let details;
|
let details;
|
||||||
try {
|
try {
|
||||||
details = JSON.parse(error.text).error; // execution handler json error response
|
details = $.parseJSON(jqXHR.responseText).error; // execution handler json error response
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
details = error.text ?? error.message ?? error;
|
details = errorThrown;
|
||||||
}
|
}
|
||||||
createAlert(title, description(details), "text-bg-danger");
|
createAlert(title, description(details), "text-bg-danger");
|
||||||
}
|
}
|
||||||
|
@ -36,69 +36,61 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const keyImportModal = document.getElementById("key-import-modal");
|
const keyImportModal = $("#key-import-modal");
|
||||||
const keyImportForm = document.getElementById("key-import-form");
|
const keyImportForm = $("#key-import-form");
|
||||||
|
|
||||||
const keyImportBodyInput = document.getElementById("key-import-body-input");
|
const keyImportBodyInput = $("#key-import-body-input");
|
||||||
const keyImportCopyButton = document.getElementById("key-import-copy-button");
|
const keyImportCopyButton = $("#key-import-copy-button");
|
||||||
|
|
||||||
const keyImportFingerprintInput = document.getElementById("key-import-fingerprint-input");
|
const keyImportFingerprintInput = $("#key-import-fingerprint-input");
|
||||||
const keyImportServerInput = document.getElementById("key-import-server-input");
|
const keyImportServerInput = $("#key-import-server-input");
|
||||||
|
|
||||||
async function copyPgpKey() {
|
async function copyPgpKey() {
|
||||||
const key = keyImportBodyInput.textContent;
|
const logs = keyImportBodyInput.text();
|
||||||
await copyToClipboard(key, keyImportCopyButton);
|
await copyToClipboard(logs, keyImportCopyButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
function fetchPgpKey() {
|
function fetchPgpKey() {
|
||||||
const key = keyImportFingerprintInput.value;
|
const key = keyImportFingerprintInput.val();
|
||||||
const server = keyImportServerInput.value;
|
const server = keyImportServerInput.val();
|
||||||
|
|
||||||
if (key && server) {
|
if (key && server) {
|
||||||
makeRequest(
|
$.ajax({
|
||||||
"/api/v1/service/pgp",
|
url: "/api/v1/service/pgp",
|
||||||
{
|
data: {"key": key, "server": server},
|
||||||
query: {
|
type: "GET",
|
||||||
key: key,
|
dataType: "json",
|
||||||
server: server,
|
success: response => { keyImportBodyInput.text(response.key); },
|
||||||
},
|
});
|
||||||
convert: response => response.json(),
|
|
||||||
},
|
|
||||||
data => { keyImportBodyInput.textContent = data.key; },
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function importPgpKey() {
|
function importPgpKey() {
|
||||||
const key = keyImportFingerprintInput.value;
|
const key = keyImportFingerprintInput.val();
|
||||||
const server = keyImportServerInput.value;
|
const server = keyImportServerInput.val();
|
||||||
|
|
||||||
if (key && server) {
|
if (key && server) {
|
||||||
makeRequest(
|
$.ajax({
|
||||||
"/api/v1/service/pgp",
|
url: "/api/v1/service/pgp",
|
||||||
{
|
data: JSON.stringify({key: key, server: server}),
|
||||||
method: "POST",
|
type: "POST",
|
||||||
json: {
|
contentType: "application/json",
|
||||||
key: key,
|
success: _ => {
|
||||||
server: server,
|
keyImportModal.modal("hide");
|
||||||
},
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
bootstrap.Modal.getOrCreateInstance(keyImportModal).hide();
|
|
||||||
showSuccess("Success", `Key ${key} has been imported`);
|
showSuccess("Success", `Key ${key} has been imported`);
|
||||||
},
|
},
|
||||||
error => {
|
error: (jqXHR, _, errorThrown) => {
|
||||||
const message = _ => `Could not import key ${key} from ${server}`;
|
const message = _ => `Could not import key ${key} from ${server}`;
|
||||||
showFailure("Action failed", message, error);
|
showFailure("Action failed", message, jqXHR, errorThrown);
|
||||||
},
|
},
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ready(_ => {
|
$(_ => {
|
||||||
keyImportModal.addEventListener("hidden.bs.modal", _ => {
|
keyImportModal.on("hidden.bs.modal", _ => {
|
||||||
keyImportBodyInput.textContent = "";
|
keyImportBodyInput.text("");
|
||||||
keyImportForm.reset();
|
keyImportForm.trigger("reset");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -34,57 +34,53 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const loginModal = document.getElementById("login-modal");
|
const loginModal = $("#login-modal");
|
||||||
const loginForm = document.getElementById("login-form");
|
const loginForm = $("#login-form");
|
||||||
|
|
||||||
const loginPasswordInput = document.getElementById("login-password");
|
const loginPasswordInput = $("#login-password");
|
||||||
const loginUsernameInput = document.getElementById("login-username");
|
const loginUsernameInput = $("#login-username");
|
||||||
const showHidePasswordButton = document.getElementById("login-show-hide-password-button");
|
const showHidePasswordButton = $("#login-show-hide-password-button");
|
||||||
|
|
||||||
function login() {
|
function login() {
|
||||||
const password = loginPasswordInput.value;
|
const password = loginPasswordInput.val();
|
||||||
const username = loginUsernameInput.value;
|
const username = loginUsernameInput.val();
|
||||||
|
|
||||||
if (username && password) {
|
if (username && password) {
|
||||||
makeRequest(
|
$.ajax({
|
||||||
"/api/v1/login",
|
url: "/api/v1/login",
|
||||||
{
|
data: JSON.stringify({username: username, password: password}),
|
||||||
method: "POST",
|
type: "POST",
|
||||||
json: {
|
contentType: "application/json",
|
||||||
username: username,
|
success: _ => {
|
||||||
password: password,
|
loginModal.modal("hide");
|
||||||
},
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
bootstrap.Modal.getOrCreateInstance(loginModal).hide();
|
|
||||||
showSuccess("Logged in", `Successfully logged in as ${username}`, _ => location.href = "/");
|
showSuccess("Logged in", `Successfully logged in as ${username}`, _ => location.href = "/");
|
||||||
},
|
},
|
||||||
error => {
|
error: (jqXHR, _, errorThrown) => {
|
||||||
const message = _ =>
|
const message = _ =>
|
||||||
username === "admin" && password === "admin"
|
username === "admin" && password === "admin"
|
||||||
? "You've entered a password for user \"root\", did you make a typo in username?"
|
? "You've entered a password for user \"root\", did you make a typo in username?"
|
||||||
: `Could not login as ${username}`;
|
: `Could not login as ${username}`;
|
||||||
showFailure("Login error", message, error);
|
showFailure("Login error", message, jqXHR, errorThrown);
|
||||||
},
|
},
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function showPassword() {
|
function showPassword() {
|
||||||
if (loginPasswordInput.getAttribute("type") === "password") {
|
if (loginPasswordInput.attr("type") === "password") {
|
||||||
loginPasswordInput.setAttribute("type", "text");
|
loginPasswordInput.attr("type", "text");
|
||||||
showHidePasswordButton.classList.remove("bi-eye");
|
showHidePasswordButton.removeClass("bi-eye");
|
||||||
showHidePasswordButton.classList.add("bi-eye-slash");
|
showHidePasswordButton.addClass("bi-eye-slash");
|
||||||
} else {
|
} else {
|
||||||
loginPasswordInput.setAttribute("type", "password");
|
loginPasswordInput.attr("type", "password");
|
||||||
showHidePasswordButton.classList.remove("bi-eye-slash");
|
showHidePasswordButton.removeClass("bi-eye-slash");
|
||||||
showHidePasswordButton.classList.add("bi-eye");
|
showHidePasswordButton.addClass("bi-eye");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ready(_ => {
|
$(_ => {
|
||||||
loginModal.addEventListener("hidden.bs.modal", _ => {
|
loginModal.on("hidden.bs.modal", _ => {
|
||||||
loginForm.reset();
|
loginForm.trigger("reset");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -41,14 +41,14 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const packageAddModal = document.getElementById("package-add-modal");
|
const packageAddModal = $("#package-add-modal");
|
||||||
const packageAddForm = document.getElementById("package-add-form");
|
const packageAddForm = $("#package-add-form");
|
||||||
|
|
||||||
const packageAddInput = document.getElementById("package-add-input");
|
const packageAddInput = $("#package-add-input");
|
||||||
const packageAddRepositoryInput = document.getElementById("package-add-repository-input");
|
const packageAddRepositoryInput = $("#package-add-repository-input");
|
||||||
const packageAddKnownPackagesList = document.getElementById("package-add-known-packages-dlist");
|
const packageAddKnownPackagesList = $("#package-add-known-packages-dlist");
|
||||||
|
|
||||||
const packageAddVariablesDiv = document.getElementById("package-add-variables-div");
|
const packageAddVariablesDiv = $("#package-add-variables-div");
|
||||||
|
|
||||||
function packageAddVariableInputCreate() {
|
function packageAddVariableInputCreate() {
|
||||||
const variableInput = document.createElement("div");
|
const variableInput = document.createElement("div");
|
||||||
@ -78,7 +78,7 @@
|
|||||||
variableButtonRemove.classList.add("btn");
|
variableButtonRemove.classList.add("btn");
|
||||||
variableButtonRemove.classList.add("btn-outline-danger");
|
variableButtonRemove.classList.add("btn-outline-danger");
|
||||||
variableButtonRemove.innerHTML = "<i class=\"bi bi-trash\"></i>";
|
variableButtonRemove.innerHTML = "<i class=\"bi bi-trash\"></i>";
|
||||||
variableButtonRemove.onclick = _ => { variableInput.remove(); };
|
variableButtonRemove.onclick = _ => { return variableInput.remove(); };
|
||||||
|
|
||||||
// bring them together
|
// bring them together
|
||||||
variableInput.appendChild(variableNameInput);
|
variableInput.appendChild(variableNameInput);
|
||||||
@ -86,26 +86,27 @@
|
|||||||
variableInput.appendChild(variableValueInput);
|
variableInput.appendChild(variableValueInput);
|
||||||
variableInput.appendChild(variableButtonRemove);
|
variableInput.appendChild(variableButtonRemove);
|
||||||
|
|
||||||
packageAddVariablesDiv.appendChild(variableInput);
|
packageAddVariablesDiv.append(variableInput);
|
||||||
}
|
}
|
||||||
|
|
||||||
function patchesParse() {
|
function patchesParse() {
|
||||||
const patches = Array.from(packageAddVariablesDiv.getElementsByClassName("package-add-variable")).map(element => {
|
const patches = packageAddVariablesDiv.find(".package-add-variable").map((_, element) => {
|
||||||
|
const richElement = $(element);
|
||||||
return {
|
return {
|
||||||
key: element.querySelector(".package-add-variable-name").value,
|
key: richElement.find(".package-add-variable-name").val(),
|
||||||
value: element.querySelector(".package-add-variable-value").value,
|
value: richElement.find(".package-add-variable-value").val(),
|
||||||
};
|
};
|
||||||
}).filter(patch => patch.key);
|
}).filter((_, patch) => patch.key).get();
|
||||||
return {patches: patches};
|
return {patches: patches};
|
||||||
}
|
}
|
||||||
|
|
||||||
function packagesAdd(packages, patches, repository) {
|
function packagesAdd(packages, patches, repository) {
|
||||||
packages = packages ?? packageAddInput.value;
|
packages = packages ?? packageAddInput.val();
|
||||||
patches = patches ?? patchesParse();
|
patches = patches ?? patchesParse();
|
||||||
repository = repository ?? getRepositorySelector(packageAddRepositoryInput);
|
repository = repository ?? getRepositorySelector(packageAddRepositoryInput);
|
||||||
|
|
||||||
if (packages) {
|
if (packages) {
|
||||||
bootstrap.Modal.getOrCreateInstance(packageAddModal).hide();
|
packageAddModal.modal("hide");
|
||||||
const onSuccess = update => `Packages ${update} have been added`;
|
const onSuccess = update => `Packages ${update} have been added`;
|
||||||
const onFailure = error => `Package addition failed: ${error}`;
|
const onFailure = error => `Package addition failed: ${error}`;
|
||||||
doPackageAction("/api/v1/service/add", [packages], repository, onSuccess, onFailure, patches);
|
doPackageAction("/api/v1/service/add", [packages], repository, onSuccess, onFailure, patches);
|
||||||
@ -113,54 +114,50 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function packagesRequest(packages, patches) {
|
function packagesRequest(packages, patches) {
|
||||||
packages = packages ?? packageAddInput.value;
|
packages = packages ?? packageAddInput.val();
|
||||||
patches = patches ?? patchesParse();
|
patches = patches ?? patchesParse();
|
||||||
const repository = getRepositorySelector(packageAddRepositoryInput);
|
const repository = getRepositorySelector(packageAddRepositoryInput);
|
||||||
|
|
||||||
if (packages) {
|
if (packages) {
|
||||||
bootstrap.Modal.getOrCreateInstance(packageAddModal).hide();
|
packageAddModal.modal("hide");
|
||||||
const onSuccess = update => `Packages ${update} have been requested`;
|
const onSuccess = update => `Packages ${update} have been requested`;
|
||||||
const onFailure = error => `Package request failed: ${error}`;
|
const onFailure = error => `Package request failed: ${error}`;
|
||||||
doPackageAction("/api/v1/service/request", [packages], repository, onSuccess, onFailure, patches);
|
doPackageAction("/api/v1/service/request", [packages], repository, onSuccess, onFailure, patches);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ready(_ => {
|
$(_ => {
|
||||||
packageAddModal.addEventListener("shown.bs.modal", _ => {
|
packageAddModal.on("shown.bs.modal", _ => {
|
||||||
const option = packageAddRepositoryInput.querySelector(`option[value="${repository.architecture}-${repository.repository}"]`);
|
$(`#package-add-repository-input option[value="${repository.architecture}-${repository.repository}"]`).prop("selected", true);
|
||||||
option.selected = "selected";
|
|
||||||
});
|
});
|
||||||
packageAddModal.addEventListener("hidden.bs.modal", _ => {
|
packageAddModal.on("hidden.bs.modal", _ => {
|
||||||
packageAddVariablesDiv.replaceChildren();
|
packageAddVariablesDiv.empty();
|
||||||
packageAddForm.reset();
|
packageAddForm.trigger("reset");
|
||||||
});
|
});
|
||||||
|
|
||||||
packageAddInput.addEventListener("keyup", _ => {
|
packageAddInput.keyup(_ => {
|
||||||
clearTimeout(packageAddInput.requestTimeout);
|
clearTimeout(packageAddInput.data("timeout"));
|
||||||
packageAddInput.requestTimeout = setTimeout(_ => {
|
packageAddInput.data("timeout", setTimeout($.proxy(_ => {
|
||||||
const value = packageAddInput.value;
|
const value = packageAddInput.val();
|
||||||
|
|
||||||
if (value.length >= 3) {
|
if (value.length >= 3) {
|
||||||
makeRequest(
|
$.ajax({
|
||||||
"/api/v1/service/search",
|
url: "/api/v1/service/search",
|
||||||
{
|
data: {"for": value},
|
||||||
query: {
|
type: "GET",
|
||||||
for: value,
|
dataType: "json",
|
||||||
},
|
success: response => {
|
||||||
convert: response => response.json(),
|
const options = response.map(pkg => {
|
||||||
},
|
|
||||||
data => {
|
|
||||||
const options = data.map(pkg => {
|
|
||||||
const option = document.createElement("option");
|
const option = document.createElement("option");
|
||||||
option.value = pkg.package;
|
option.value = pkg.package;
|
||||||
option.innerText = `${pkg.package} (${pkg.description})`;
|
option.innerText = `${pkg.package} (${pkg.description})`;
|
||||||
return option;
|
return option;
|
||||||
});
|
});
|
||||||
packageAddKnownPackagesList.replaceChildren(...options);
|
packageAddKnownPackagesList.empty().append(options);
|
||||||
},
|
},
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
}, 500);
|
}, this), 500));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -58,7 +58,7 @@
|
|||||||
<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>
|
<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>
|
||||||
<div id="package-info-events" class="tab-pane fade" role="tabpanel" aria-labelledby="package-info-events-button" tabindex="0">
|
<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>
|
<canvas id="package-info-events-update-chart"></canvas>
|
||||||
<table id="package-info-events-table"
|
<table id="package-info-events-table"
|
||||||
data-classes="table table-hover"
|
data-classes="table table-hover"
|
||||||
data-sortable="true"
|
data-sortable="true"
|
||||||
@ -77,10 +77,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<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" hidden><i class="bi bi-play"></i><span class="d-none d-sm-inline"> update</span></button>
|
||||||
<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" hidden><i class="bi bi-trash"></i><span class="d-none d-sm-inline"> remove</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-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>
|
<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>
|
||||||
@ -89,35 +87,33 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const packageInfoModal = document.getElementById("package-info-modal");
|
const packageInfoModal = $("#package-info-modal");
|
||||||
const packageInfoModalHeader = document.getElementById("package-info-modal-header");
|
const packageInfoModalHeader = $("#package-info-modal-header");
|
||||||
const packageInfo = document.getElementById("package-info");
|
const packageInfo = $("#package-info");
|
||||||
|
|
||||||
const packageInfoLogsInput = document.getElementById("package-info-logs-input");
|
const packageInfoLogsInput = $("#package-info-logs-input");
|
||||||
const packageInfoLogsCopyButton = document.getElementById("package-info-logs-copy-button");
|
const packageInfoLogsCopyButton = $("#package-info-logs-copy-button");
|
||||||
|
|
||||||
const packageInfoChangesInput = document.getElementById("package-info-changes-input");
|
const packageInfoChangesInput = $("#package-info-changes-input");
|
||||||
const packageInfoChangesCopyButton = document.getElementById("package-info-changes-copy-button");
|
const packageInfoChangesCopyButton = $("#package-info-changes-copy-button");
|
||||||
|
|
||||||
// so far bootstrap-table only operates with jquery elements
|
const packageInfoEventsTable = $("#package-info-events-table");
|
||||||
const packageInfoEventsTable = $(document.getElementById("package-info-events-table"));
|
|
||||||
const packageInfoEventsUpdateChartCanvas = document.getElementById("package-info-events-update-chart");
|
const packageInfoEventsUpdateChartCanvas = document.getElementById("package-info-events-update-chart");
|
||||||
let packageInfoEventsUpdateChart = null;
|
let packageInfoEventsUpdateChart = null;
|
||||||
|
|
||||||
const packageInfoAurUrl = document.getElementById("package-info-aur-url");
|
const packageInfoAurUrl = $("#package-info-aur-url");
|
||||||
const packageInfoDepends = document.getElementById("package-info-depends");
|
const packageInfoDepends = $("#package-info-depends");
|
||||||
const packageInfoGroups = document.getElementById("package-info-groups");
|
const packageInfoGroups = $("#package-info-groups");
|
||||||
const packageInfoLicenses = document.getElementById("package-info-licenses");
|
const packageInfoLicenses = $("#package-info-licenses");
|
||||||
const packageInfoPackager = document.getElementById("package-info-packager");
|
const packageInfoPackager = $("#package-info-packager");
|
||||||
const packageInfoPackages = document.getElementById("package-info-packages");
|
const packageInfoPackages = $("#package-info-packages");
|
||||||
const packageInfoUpstreamUrl = document.getElementById("package-info-upstream-url");
|
const packageInfoUpstreamUrl = $("#package-info-upstream-url");
|
||||||
const packageInfoVersion = document.getElementById("package-info-version");
|
const packageInfoVersion = $("#package-info-version");
|
||||||
|
|
||||||
const packageInfoVariablesBlock = document.getElementById("package-info-variables-block");
|
const packageInfoVariablesBlock = $("#package-info-variables-block");
|
||||||
const packageInfoVariablesDiv = document.getElementById("package-info-variables-div");
|
const packageInfoVariablesDiv = $("#package-info-variables-div");
|
||||||
|
|
||||||
function clearChart() {
|
function clearChart() {
|
||||||
packageInfoEventsUpdateChartCanvas.hidden = true;
|
|
||||||
if (packageInfoEventsUpdateChart) {
|
if (packageInfoEventsUpdateChart) {
|
||||||
packageInfoEventsUpdateChart.data = {};
|
packageInfoEventsUpdateChart.data = {};
|
||||||
packageInfoEventsUpdateChart.update();
|
packageInfoEventsUpdateChart.update();
|
||||||
@ -125,15 +121,20 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function copyChanges() {
|
async function copyChanges() {
|
||||||
const changes = packageInfoChangesInput.textContent;
|
const changes = packageInfoChangesInput.text();
|
||||||
await copyToClipboard(changes, packageInfoChangesCopyButton);
|
await copyToClipboard(changes, packageInfoChangesCopyButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function copyLogs() {
|
async function copyLogs() {
|
||||||
const logs = packageInfoLogsInput.textContent;
|
const logs = packageInfoLogsInput.text();
|
||||||
await copyToClipboard(logs, packageInfoLogsCopyButton);
|
await copyToClipboard(logs, packageInfoLogsCopyButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hideInfoControls(hidden) {
|
||||||
|
packageInfoRemoveButton.attr("hidden", hidden);
|
||||||
|
packageInfoUpdateButton.attr("hidden", hidden);
|
||||||
|
}
|
||||||
|
|
||||||
function highlight(element) {
|
function highlight(element) {
|
||||||
delete element.dataset.highlighted;
|
delete element.dataset.highlighted;
|
||||||
hljs.highlightElement(element);
|
hljs.highlightElement(element);
|
||||||
@ -163,13 +164,12 @@
|
|||||||
variableButtonRemove.classList.add("btn-outline-danger");
|
variableButtonRemove.classList.add("btn-outline-danger");
|
||||||
variableButtonRemove.innerHTML = "<i class=\"bi bi-trash\"></i>";
|
variableButtonRemove.innerHTML = "<i class=\"bi bi-trash\"></i>";
|
||||||
variableButtonRemove.onclick = _ => {
|
variableButtonRemove.onclick = _ => {
|
||||||
makeRequest(
|
$.ajax({
|
||||||
`/api/v1/packages/${packageBase}/patches/${variable.key}`,
|
url: `/api/v1/packages/${packageBase}/patches/${variable.key}`,
|
||||||
{
|
type: "DELETE",
|
||||||
method: "DELETE",
|
dataType: "json",
|
||||||
},
|
success: _ => variableInput.remove(),
|
||||||
_ => variableInput.remove(),
|
});
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// bring them together
|
// bring them together
|
||||||
@ -178,57 +178,52 @@
|
|||||||
variableInput.appendChild(variableValueInput);
|
variableInput.appendChild(variableValueInput);
|
||||||
variableInput.appendChild(variableButtonRemove);
|
variableInput.appendChild(variableButtonRemove);
|
||||||
|
|
||||||
packageInfoVariablesDiv.appendChild(variableInput);
|
packageInfoVariablesDiv.append(variableInput);
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadChanges(packageBase, onFailure) {
|
function loadChanges(packageBase, onFailure) {
|
||||||
makeRequest(
|
$.ajax({
|
||||||
`/api/v1/packages/${packageBase}/changes`,
|
url: `/api/v1/packages/${packageBase}/changes`,
|
||||||
{
|
data: {
|
||||||
query: {
|
|
||||||
architecture: repository.architecture,
|
architecture: repository.architecture,
|
||||||
repository: repository.repository,
|
repository: repository.repository,
|
||||||
},
|
},
|
||||||
convert: response => response.json(),
|
type: "GET",
|
||||||
|
dataType: "json",
|
||||||
|
success: response => {
|
||||||
|
const changes = response.changes;
|
||||||
|
packageInfoChangesInput.text(changes || "");
|
||||||
|
packageInfoChangesInput.map((_, el) => highlight(el));
|
||||||
},
|
},
|
||||||
data => {
|
error: onFailure,
|
||||||
const changes = data.changes;
|
});
|
||||||
packageInfoChangesInput.textContent = changes ?? "";
|
|
||||||
highlight(packageInfoChangesInput);
|
|
||||||
},
|
|
||||||
onFailure,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadEvents(packageBase, onFailure) {
|
function loadEvents(packageBase, onFailure) {
|
||||||
packageInfoEventsTable.bootstrapTable("showLoading");
|
packageInfoEventsTable.bootstrapTable("showLoading");
|
||||||
clearChart();
|
clearChart();
|
||||||
|
|
||||||
makeRequest(
|
$.ajax({
|
||||||
"/api/v1/events",
|
url: `/api/v1/events`,
|
||||||
{
|
data: {
|
||||||
query: {
|
|
||||||
architecture: repository.architecture,
|
architecture: repository.architecture,
|
||||||
repository: repository.repository,
|
repository: repository.repository,
|
||||||
object_id: packageBase,
|
object_id: packageBase,
|
||||||
limit: 30,
|
limit: 30,
|
||||||
},
|
},
|
||||||
convert: response => response.json(),
|
type: "GET",
|
||||||
},
|
dataType: "json",
|
||||||
data => {
|
success: response => {
|
||||||
const events = data.map(event => {
|
const events = response.map(event => {
|
||||||
return {
|
return {
|
||||||
timestamp: new Date(1000 * event.created).toISOStringShort(),
|
timestamp: new Date(1000 * event.created).toISOStringShort(),
|
||||||
event: event.event,
|
event: event.event,
|
||||||
message: event.message || "",
|
message: event.message || "",
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
const chart = data.filter(event => event.event === "package-updated");
|
|
||||||
|
|
||||||
packageInfoEventsTable.bootstrapTable("load", events);
|
|
||||||
packageInfoEventsTable.bootstrapTable("hideLoading");
|
|
||||||
|
|
||||||
if (packageInfoEventsUpdateChart) {
|
if (packageInfoEventsUpdateChart) {
|
||||||
|
const chart = response.filter(event => event.event === "package-updated");
|
||||||
packageInfoEventsUpdateChart.config.data = {
|
packageInfoEventsUpdateChart.config.data = {
|
||||||
labels: chart.map(event => new Date(1000 * event.created).toISOStringShort()),
|
labels: chart.map(event => new Date(1000 * event.created).toISOStringShort()),
|
||||||
datasets: [{
|
datasets: [{
|
||||||
@ -240,31 +235,32 @@
|
|||||||
};
|
};
|
||||||
packageInfoEventsUpdateChart.update();
|
packageInfoEventsUpdateChart.update();
|
||||||
}
|
}
|
||||||
packageInfoEventsUpdateChartCanvas.hidden = !chart.length;
|
|
||||||
|
packageInfoEventsTable.bootstrapTable("load", events);
|
||||||
|
packageInfoEventsTable.bootstrapTable("hideLoading");
|
||||||
},
|
},
|
||||||
onFailure,
|
error: onFailure,
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadLogs(packageBase, onFailure) {
|
function loadLogs(packageBase, onFailure) {
|
||||||
makeRequest(
|
$.ajax({
|
||||||
`/api/v2/packages/${packageBase}/logs`,
|
url: `/api/v2/packages/${packageBase}/logs`,
|
||||||
{
|
data: {
|
||||||
query: {
|
|
||||||
architecture: repository.architecture,
|
architecture: repository.architecture,
|
||||||
repository: repository.repository,
|
repository: repository.repository,
|
||||||
},
|
},
|
||||||
convert: response => response.json(),
|
type: "GET",
|
||||||
},
|
dataType: "json",
|
||||||
data => {
|
success: response => {
|
||||||
const logs = data.map(log_record => {
|
const logs = response.map(log_record => {
|
||||||
return `[${new Date(1000 * log_record.created).toISOString()}] ${log_record.message}`;
|
return `[${new Date(1000 * log_record.created).toISOString()}] ${log_record.message}`;
|
||||||
});
|
});
|
||||||
packageInfoLogsInput.textContent = logs.join("\n");
|
packageInfoLogsInput.text(logs.join("\n"));
|
||||||
highlight(packageInfoLogsInput);
|
packageInfoLogsInput.map((_, el) => highlight(el));
|
||||||
},
|
},
|
||||||
onFailure,
|
error: onFailure,
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadPackage(packageBase, onFailure) {
|
function loadPackage(packageBase, onFailure) {
|
||||||
@ -276,17 +272,16 @@
|
|||||||
return ["bg-secondary", "text-white"];
|
return ["bg-secondary", "text-white"];
|
||||||
};
|
};
|
||||||
|
|
||||||
makeRequest(
|
$.ajax({
|
||||||
`/api/v1/packages/${packageBase}`,
|
url: `/api/v1/packages/${packageBase}`,
|
||||||
{
|
data: {
|
||||||
query: {
|
|
||||||
architecture: repository.architecture,
|
architecture: repository.architecture,
|
||||||
repository: repository.repository,
|
repository: repository.repository,
|
||||||
},
|
},
|
||||||
convert: response => response.json(),
|
type: "GET",
|
||||||
},
|
dataType: "json",
|
||||||
data => {
|
success: response => {
|
||||||
const description = data.find(Boolean);
|
const description = response.find(Boolean);
|
||||||
const packages = Object.keys(description.package.packages);
|
const packages = Object.keys(description.package.packages);
|
||||||
const aurUrl = description.package.remote.web_url;
|
const aurUrl = description.package.remote.web_url;
|
||||||
const upstreamUrls = Array.from(
|
const upstreamUrls = Array.from(
|
||||||
@ -296,71 +291,72 @@
|
|||||||
)
|
)
|
||||||
).sort();
|
).sort();
|
||||||
|
|
||||||
packageInfo.textContent = `${description.package.base} ${description.status.status} at ${new Date(1000 * description.status.timestamp).toISOStringShort()}`;
|
packageInfo.text(`${description.package.base} ${description.status.status} at ${new Date(1000 * description.status.timestamp).toISOStringShort()}`);
|
||||||
|
|
||||||
packageInfoModalHeader.classList.remove(...packageInfoModalHeader.classList);
|
packageInfoModalHeader.removeClass();
|
||||||
packageInfoModalHeader.classList.add("modal-header");
|
packageInfoModalHeader.addClass("modal-header");
|
||||||
headerClass(description.status.status).forEach(clz => packageInfoModalHeader.classList.add(clz));
|
headerClass(description.status.status).forEach(clz => packageInfoModalHeader.addClass(clz));
|
||||||
|
|
||||||
packageInfoAurUrl.innerHTML = aurUrl ? safeLink(aurUrl, aurUrl, "AUR link").outerHTML : "";
|
packageInfoAurUrl.html(aurUrl ? safeLink(aurUrl, aurUrl, "AUR link").outerHTML : "");
|
||||||
packageInfoDepends.innerHTML = listToTable(
|
packageInfoDepends.html(listToTable(
|
||||||
Object.values(description.package.packages)
|
Object.values(description.package.packages)
|
||||||
.reduce((accumulator, currentValue) => {
|
.reduce((accumulator, currentValue) => {
|
||||||
return accumulator.concat(currentValue.depends.filter(v => packages.indexOf(v) === -1))
|
return accumulator.concat(currentValue.depends.filter(v => packages.indexOf(v) === -1))
|
||||||
.concat(currentValue.make_depends.filter(v => packages.indexOf(v) === -1).map(v => `${v} (make)`))
|
.concat(currentValue.make_depends.filter(v => packages.indexOf(v) === -1).map(v => `${v} (make)`))
|
||||||
.concat(currentValue.opt_depends.filter(v => packages.indexOf(v) === -1).map(v => `${v} (optional)`));
|
.concat(currentValue.opt_depends.filter(v => packages.indexOf(v) === -1).map(v => `${v} (optional)`));
|
||||||
}, [])
|
}, [])
|
||||||
);
|
));
|
||||||
packageInfoGroups.innerHTML = listToTable(extractListProperties(description.package, "groups"));
|
packageInfoGroups.html(listToTable(extractListProperties(description.package, "groups")));
|
||||||
packageInfoLicenses.innerHTML = listToTable(extractListProperties(description.package, "licenses"));
|
packageInfoLicenses.html(listToTable(extractListProperties(description.package, "licenses")));
|
||||||
packageInfoPackager.textContent = description.package.packager;
|
packageInfoPackager.text(description.package.packager);
|
||||||
packageInfoPackages.innerHTML = listToTable(packages);
|
packageInfoPackages.html(listToTable(packages));
|
||||||
packageInfoUpstreamUrl.innerHTML = upstreamUrls.map(url => safeLink(url, url, "upstream link").outerHTML).join("<br>");
|
packageInfoUpstreamUrl.html(upstreamUrls.map(url => safeLink(url, url, "upstream link").outerHTML).join("<br>"));
|
||||||
packageInfoVersion.textContent = description.package.version;
|
packageInfoVersion.text(description.package.version);
|
||||||
|
|
||||||
|
hideInfoControls(false);
|
||||||
},
|
},
|
||||||
onFailure,
|
error: (jqXHR, _, errorThrown) => {
|
||||||
);
|
hideInfoControls(true);
|
||||||
|
onFailure(jqXHR, null, errorThrown);
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadPatches(packageBase, onFailure) {
|
function loadPatches(packageBase, onFailure) {
|
||||||
makeRequest(
|
$.ajax({
|
||||||
`/api/v1/packages/${packageBase}/patches`,
|
url: `/api/v1/packages/${packageBase}/patches`,
|
||||||
{
|
type: "GET",
|
||||||
convert: response => response.json(),
|
dataType: "json",
|
||||||
|
success: response => {
|
||||||
|
packageInfoVariablesDiv.empty();
|
||||||
|
response.map(patch => insertVariable(packageBase, patch));
|
||||||
|
packageInfoVariablesBlock.attr("hidden", response.length === 0);
|
||||||
},
|
},
|
||||||
data => {
|
error: onFailure,
|
||||||
packageInfoVariablesDiv.replaceChildren();
|
});
|
||||||
data.map(patch => insertVariable(packageBase, patch));
|
|
||||||
packageInfoVariablesBlock.hidden = !data.length;
|
|
||||||
},
|
|
||||||
onFailure,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function packageInfoRemove() {
|
function packageInfoRemove() {
|
||||||
const packageBase = packageInfoModal.package;
|
const packageBase = packageInfoModal.data("package");
|
||||||
packagesRemove([packageBase]);
|
if (packageBase) return packagesRemove([packageBase]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function packageInfoUpdate() {
|
function packageInfoUpdate() {
|
||||||
const packageBase = packageInfoModal.package;
|
const packageBase = packageInfoModal.data("package");
|
||||||
packagesAdd(packageBase, [], repository);
|
if (packageBase) return packagesAdd(packageBase, [], repository);
|
||||||
}
|
}
|
||||||
|
|
||||||
function showPackageInfo(packageBase) {
|
function showPackageInfo(packageBase) {
|
||||||
const isPackageBaseSet = packageBase !== undefined;
|
const isPackageBaseSet = packageBase !== undefined;
|
||||||
if (isPackageBaseSet) {
|
if (isPackageBaseSet)
|
||||||
// set package base as currently used
|
packageInfoModal.data("package", packageBase); // set package base as currently used
|
||||||
packageInfoModal.package = packageBase;
|
else
|
||||||
} else {
|
packageBase = packageInfoModal.data("package"); // read package base from the current window attribute
|
||||||
// read package base from the current window attribute
|
|
||||||
packageBase = packageInfoModal.package;
|
|
||||||
}
|
|
||||||
|
|
||||||
const onFailure = error => {
|
const onFailure = (jqXHR, _, errorThrown) => {
|
||||||
if (isPackageBaseSet) {
|
if (isPackageBaseSet) {
|
||||||
const message = details => `Could not load package ${packageBase} info: ${details}`;
|
const message = error => `Could not load package ${packageBase} info: ${error}`;
|
||||||
showFailure("Load failure", message, error);
|
showFailure("Load failure", message, jqXHR, errorThrown);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -370,12 +366,10 @@
|
|||||||
loadChanges(packageBase, onFailure);
|
loadChanges(packageBase, onFailure);
|
||||||
loadEvents(packageBase, onFailure);
|
loadEvents(packageBase, onFailure);
|
||||||
|
|
||||||
if (isPackageBaseSet) {
|
if (isPackageBaseSet) packageInfoModal.modal("show");
|
||||||
bootstrap.Modal.getOrCreateInstance(packageInfoModal).show();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ready(_ => {
|
$(_ => {
|
||||||
packageInfoEventsUpdateChart = new Chart(packageInfoEventsUpdateChartCanvas, {
|
packageInfoEventsUpdateChart = new Chart(packageInfoEventsUpdateChartCanvas, {
|
||||||
type: "line",
|
type: "line",
|
||||||
data: {},
|
data: {},
|
||||||
@ -384,23 +378,27 @@
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
packageInfoModal.addEventListener("hidden.bs.modal", _ => {
|
packageInfoModal.on("hidden.bs.modal", _ => {
|
||||||
packageInfoAurUrl.textContent = "";
|
packageInfoAurUrl.empty();
|
||||||
packageInfoDepends.textContent = "";
|
packageInfoDepends.empty();
|
||||||
packageInfoGroups.textContent = "";
|
packageInfoGroups.empty();
|
||||||
packageInfoLicenses.textContent = "";
|
packageInfoLicenses.empty();
|
||||||
packageInfoPackager.textContent = "";
|
packageInfoPackager.empty();
|
||||||
packageInfoPackages.textContent = "";
|
packageInfoPackages.empty();
|
||||||
packageInfoUpstreamUrl.textContent = "";
|
packageInfoUpstreamUrl.empty();
|
||||||
packageInfoVersion.textContent = "";
|
packageInfoVersion.empty();
|
||||||
|
|
||||||
packageInfoVariablesBlock.hidden = true;
|
packageInfoVariablesBlock.attr("hidden", true);
|
||||||
packageInfoVariablesDiv.replaceChildren();
|
packageInfoVariablesDiv.empty();
|
||||||
|
|
||||||
packageInfoLogsInput.textContent = "";
|
packageInfoLogsInput.empty();
|
||||||
packageInfoChangesInput.textContent = "";
|
packageInfoChangesInput.empty();
|
||||||
packageInfoEventsTable.bootstrapTable("load", []);
|
packageInfoEventsTable.bootstrapTable("load", []);
|
||||||
clearChart();
|
clearChart();
|
||||||
|
|
||||||
|
packageInfoModal.trigger("reset");
|
||||||
|
|
||||||
|
hideInfoControls(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -33,31 +33,28 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const packageRebuildModal = document.getElementById("package-rebuild-modal");
|
const packageRebuildModal = $("#package-rebuild-modal");
|
||||||
const packageRebuildForm = document.getElementById("package-rebuild-form");
|
const packageRebuildForm = $("#package-rebuild-form");
|
||||||
|
|
||||||
const packageRebuildDependencyInput = document.getElementById("package-rebuild-dependency-input");
|
const packageRebuildDependencyInput = $("#package-rebuild-dependency-input");
|
||||||
const packageRebuildRepositoryInput = document.getElementById("package-rebuild-repository-input");
|
const packageRebuildRepositoryInput = $("#package-rebuild-repository-input");
|
||||||
|
|
||||||
function packagesRebuild() {
|
function packagesRebuild() {
|
||||||
const packages = packageRebuildDependencyInput.value;
|
const packages = packageRebuildDependencyInput.val();
|
||||||
const repository = getRepositorySelector(packageRebuildRepositoryInput);
|
const repository = getRepositorySelector(packageRebuildRepositoryInput);
|
||||||
if (packages) {
|
if (packages) {
|
||||||
bootstrap.Modal.getOrCreateInstance(packageRebuildModal).hide();
|
packageRebuildModal.modal("hide");
|
||||||
const onSuccess = update => `Repository rebuild has been run for packages which depend on ${update}`;
|
const onSuccess = update => `Repository rebuild has been run for packages which depend on ${update}`;
|
||||||
const onFailure = error => `Repository rebuild failed: ${error}`;
|
const onFailure = error => `Repository rebuild failed: ${error}`;
|
||||||
doPackageAction("/api/v1/service/rebuild", [packages], repository, onSuccess, onFailure);
|
doPackageAction("/api/v1/service/rebuild", [packages], repository, onSuccess, onFailure);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ready(_ => {
|
$(_ => {
|
||||||
packageRebuildModal.addEventListener("shown.bs.modal", _ => {
|
packageRebuildModal.on("shown.bs.modal", _ => {
|
||||||
const option = packageRebuildRepositoryInput.querySelector(`option[value="${repository.architecture}-${repository.repository}"]`);
|
$(`#package-rebuild-repository-input option[value="${repository.architecture}-${repository.repository}"]`).prop("selected", true);
|
||||||
option.selected = "selected";
|
|
||||||
|
|
||||||
});
|
});
|
||||||
packageRebuildModal.addEventListener("hidden.bs.modal", _ => {
|
packageRebuildModal.on("hidden.bs.modal", _ => { packageRebuildForm.trigger("reset"); });
|
||||||
packageRebuildForm.reset();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,34 +1,39 @@
|
|||||||
<script>
|
<script>
|
||||||
const packageRemoveButton = document.getElementById("package-remove-button");
|
const keyImportButton = $("#key-import-button");
|
||||||
const packageUpdateButton = document.getElementById("package-update-button");
|
const packageAddButton = $("#package-add-button");
|
||||||
|
const packageRebuildButton = $("#package-rebuild-button");
|
||||||
|
const packageRemoveButton = $("#package-remove-button");
|
||||||
|
const packageUpdateButton = $("#package-update-button");
|
||||||
|
|
||||||
|
const packageInfoRemoveButton = $("#package-info-remove-button");
|
||||||
|
const packageInfoUpdateButton = $("#package-info-update-button");
|
||||||
|
|
||||||
let repository = null;
|
let repository = null;
|
||||||
|
|
||||||
// so far bootstrap-table only operates with jquery elements
|
const table = $("#packages");
|
||||||
const table = $(document.getElementById("packages"));
|
|
||||||
|
|
||||||
const statusBadge = document.getElementById("badge-status");
|
const statusBadge = $("#badge-status");
|
||||||
const versionBadge = document.getElementById("badge-version");
|
const versionBadge = $("#badge-version");
|
||||||
|
|
||||||
function doPackageAction(uri, packages, repository, successText, failureText, data) {
|
function doPackageAction(uri, packages, repository, successText, failureText, data) {
|
||||||
makeRequest(
|
const queryParams = $.param({
|
||||||
uri,
|
|
||||||
{
|
|
||||||
method: "POST",
|
|
||||||
query: {
|
|
||||||
architecture: repository.architecture,
|
architecture: repository.architecture,
|
||||||
repository: repository.repository,
|
repository: repository.repository,
|
||||||
},
|
}); // it will never be empty btw
|
||||||
json: Object.assign({}, {packages: packages}, data || {}),
|
|
||||||
},
|
$.ajax({
|
||||||
_ => {
|
url: `${uri}?${queryParams}`,
|
||||||
|
data: JSON.stringify(Object.assign({}, {packages: packages}, data || {})),
|
||||||
|
type: "POST",
|
||||||
|
contentType: "application/json",
|
||||||
|
success: _ => {
|
||||||
const message = successText(packages.join(", "));
|
const message = successText(packages.join(", "));
|
||||||
showSuccess("Success", message);
|
showSuccess("Success", message);
|
||||||
},
|
},
|
||||||
error => {
|
error: (jqXHR, _, errorThrown) => {
|
||||||
showFailure("Action failed", failureText, error);
|
showFailure("Action failed", failureText, jqXHR, errorThrown);
|
||||||
},
|
},
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function filterListGroups() {
|
function filterListGroups() {
|
||||||
@ -44,10 +49,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getRepositorySelector(selector) {
|
function getRepositorySelector(selector) {
|
||||||
const selected = selector.options[selector.selectedIndex];
|
const selected = selector.find(":selected");
|
||||||
return {
|
return {
|
||||||
architecture: selected.getAttribute("data-architecture"),
|
architecture: selected.data("architecture"),
|
||||||
repository: selected.getAttribute("data-repository"),
|
repository: selected.data("repository"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,6 +60,14 @@
|
|||||||
return table.bootstrapTable("getSelections").map(row => row.id);
|
return table.bootstrapTable("getSelections").map(row => row.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hideControls(hidden) {
|
||||||
|
keyImportButton.attr("hidden", hidden);
|
||||||
|
packageAddButton.attr("hidden", hidden);
|
||||||
|
packageRebuildButton.attr("hidden", hidden);
|
||||||
|
packageRemoveButton.attr("hidden", hidden);
|
||||||
|
packageUpdateButton.attr("hidden", hidden);
|
||||||
|
}
|
||||||
|
|
||||||
function packagesRemove(packages) {
|
function packagesRemove(packages) {
|
||||||
packages = packages ?? getSelection();
|
packages = packages ?? getSelection();
|
||||||
const onSuccess = update => `Packages ${update} have been removed`;
|
const onSuccess = update => `Packages ${update} have been removed`;
|
||||||
@ -84,17 +97,16 @@
|
|||||||
return "btn-outline-secondary";
|
return "btn-outline-secondary";
|
||||||
};
|
};
|
||||||
|
|
||||||
makeRequest(
|
$.ajax({
|
||||||
"/api/v1/packages",
|
url: "/api/v1/packages",
|
||||||
{
|
data: {
|
||||||
query: {
|
|
||||||
architecture: repository.architecture,
|
architecture: repository.architecture,
|
||||||
repository: repository.repository,
|
repository: repository.repository,
|
||||||
},
|
},
|
||||||
convert: response => response.json(),
|
type: "GET",
|
||||||
},
|
dataType: "json",
|
||||||
data => {
|
success: response => {
|
||||||
const payload = data.map(description => {
|
const payload = response.map(description => {
|
||||||
const package_base = description.package.base;
|
const package_base = description.package.base;
|
||||||
const web_url = description.package.remote.web_url;
|
const web_url = description.package.remote.web_url;
|
||||||
return {
|
return {
|
||||||
@ -113,9 +125,10 @@
|
|||||||
table.bootstrapTable("load", payload);
|
table.bootstrapTable("load", payload);
|
||||||
table.bootstrapTable("uncheckAll");
|
table.bootstrapTable("uncheckAll");
|
||||||
table.bootstrapTable("hideLoading");
|
table.bootstrapTable("hideLoading");
|
||||||
|
hideControls(false);
|
||||||
},
|
},
|
||||||
error => {
|
error: (jqXHR, _, errorThrown) => {
|
||||||
if ((error.status === 401) || (error.status === 403)) {
|
if ((jqXHR.status === 401) || (jqXHR.status === 403)) {
|
||||||
// authorization error
|
// authorization error
|
||||||
const text = "In order to see statuses you must login first.";
|
const text = "In order to see statuses you must login first.";
|
||||||
table.find("tr.unauthorized").remove();
|
table.find("tr.unauthorized").remove();
|
||||||
@ -123,39 +136,39 @@
|
|||||||
table.bootstrapTable("hideLoading");
|
table.bootstrapTable("hideLoading");
|
||||||
} else {
|
} else {
|
||||||
// other errors
|
// other errors
|
||||||
const message = details => `Could not load list of packages: ${details}`;
|
const message = error => `Could not load list of packages: ${error}`;
|
||||||
showFailure("Load failure", message, error);
|
showFailure("Load failure", message, jqXHR, errorThrown);
|
||||||
}
|
}
|
||||||
|
hideControls(true);
|
||||||
},
|
},
|
||||||
);
|
});
|
||||||
|
|
||||||
makeRequest(
|
$.ajax({
|
||||||
"/api/v1/status",
|
url: "/api/v1/status",
|
||||||
{
|
data: {
|
||||||
query: {
|
|
||||||
architecture: repository.architecture,
|
architecture: repository.architecture,
|
||||||
repository: repository.repository,
|
repository: repository.repository,
|
||||||
},
|
},
|
||||||
convert: response => response.json(),
|
type: "GET",
|
||||||
},
|
dataType: "json",
|
||||||
data => {
|
success: response => {
|
||||||
versionBadge.innerHTML = `<i class="bi bi-github"></i> ahriman ${safe(data.version)}`;
|
versionBadge.html(`<i class="bi bi-github"></i> ahriman ${safe(response.version)}`);
|
||||||
|
|
||||||
statusBadge.classList.remove(...statusBadge.classList);
|
statusBadge
|
||||||
statusBadge.classList.add("btn");
|
.popover("dispose")
|
||||||
statusBadge.classList.add(badgeClass(data.status.status));
|
.attr("data-bs-content", `${response.status.status} at ${new Date(1000 * response.status.timestamp).toISOStringShort()}`)
|
||||||
|
.popover();
|
||||||
const popover = bootstrap.Popover.getOrCreateInstance(statusBadge);
|
statusBadge.removeClass();
|
||||||
popover.dispose();
|
statusBadge.addClass("btn");
|
||||||
statusBadge.dataset.bsContent = `${data.status.status} at ${new Date(1000 * data.status.timestamp).toISOStringShort()}`;
|
statusBadge.addClass(badgeClass(response.status.status));
|
||||||
bootstrap.Popover.getOrCreateInstance(statusBadge);
|
|
||||||
},
|
},
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectRepository() {
|
function selectRepository() {
|
||||||
const fragment = window.location.hash.replace("#", "") || "{{ repositories[0].id }}";
|
const fragment = window.location.hash.replace("#", "") || "{{ repositories[0].id }}";
|
||||||
document.getElementById(`${fragment}-link`).click();
|
const element = $(`#${fragment}-link`);
|
||||||
|
element.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
function statusFormat(value) {
|
function statusFormat(value) {
|
||||||
@ -169,25 +182,20 @@
|
|||||||
return {classes: cellClass(value)};
|
return {classes: cellClass(value)};
|
||||||
}
|
}
|
||||||
|
|
||||||
ready(_ => {
|
$(_ => {
|
||||||
document.querySelectorAll("#repositories a").forEach(element => {
|
$("#repositories a").on("click", event => {
|
||||||
element.onclick = _ => {
|
const element = event.target;
|
||||||
repository = {
|
repository = {
|
||||||
architecture: element.dataset.architecture,
|
architecture: element.dataset.architecture,
|
||||||
repository: element.dataset.repository,
|
repository: element.dataset.repository,
|
||||||
};
|
};
|
||||||
if (packageUpdateButton) {
|
packageUpdateButton.html(`<i class="bi bi-play"></i> update<span class="d-none d-sm-inline"> ${safe(repository.repository)} (${safe(repository.architecture)})</span>`);
|
||||||
packageUpdateButton.innerHTML = `<i class="bi bi-play"></i> update<span class="d-none d-sm-inline"> ${safe(repository.repository)} (${safe(repository.architecture)})</span>`;
|
$(`#${element.id}`).tab("show");
|
||||||
}
|
|
||||||
bootstrap.Tab.getOrCreateInstance(document.getElementById(element.id)).show();
|
|
||||||
reload();
|
reload();
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
table.on("check.bs.table uncheck.bs.table check-all.bs.table uncheck-all.bs.table", _ => {
|
table.on("check.bs.table uncheck.bs.table check-all.bs.table uncheck-all.bs.table", _ => {
|
||||||
if (packageRemoveButton) {
|
packageRemoveButton.prop("disabled", !table.bootstrapTable("getSelections").length);
|
||||||
packageRemoveButton.disabled = !table.bootstrapTable("getSelections").length;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
table.on("click-row.bs.table", (self, data, row, cell) => {
|
table.on("click-row.bs.table", (self, data, row, cell) => {
|
||||||
if (0 === cell || "base" === cell) {
|
if (0 === cell || "base" === cell) {
|
||||||
@ -196,38 +204,26 @@
|
|||||||
} else showPackageInfo(data.id);
|
} else showPackageInfo(data.id);
|
||||||
});
|
});
|
||||||
table.on("created-controls.bs.table", _ => {
|
table.on("created-controls.bs.table", _ => {
|
||||||
new easepick.create({
|
const pickerInput = $(".bootstrap-table-filter-control-timestamp");
|
||||||
element: document.querySelector(".bootstrap-table-filter-control-timestamp"),
|
pickerInput.daterangepicker({
|
||||||
css: [
|
autoUpdateInput: false,
|
||||||
"https://cdn.jsdelivr.net/npm/@easepick/bundle@1.2.1/dist/index.css",
|
|
||||||
],
|
|
||||||
grid: 2,
|
|
||||||
calendars: 2,
|
|
||||||
autoApply: false,
|
|
||||||
locale: {
|
locale: {
|
||||||
cancel: "Clear",
|
cancelLabel: "Clear",
|
||||||
},
|
},
|
||||||
RangePlugin: {
|
});
|
||||||
tooltip: false,
|
|
||||||
},
|
pickerInput.on("apply.daterangepicker", (event, picker) => {
|
||||||
plugins: [
|
pickerInput.val(`${picker.startDate.format("YYYY-MM-DD")} - ${picker.endDate.format("YYYY-MM-DD")}`);
|
||||||
"RangePlugin",
|
table.bootstrapTable("triggerSearch");
|
||||||
],
|
});
|
||||||
setup: picker => {
|
|
||||||
picker.on("select", _ => { table.bootstrapTable("triggerSearch"); });
|
pickerInput.on("cancel.daterangepicker", _ => {
|
||||||
// replace "Cancel" behaviour to "Clear"
|
pickerInput.val("");
|
||||||
picker.onClickCancelButton = element => {
|
|
||||||
if (picker.isCancelButton(element)) {
|
|
||||||
picker.clear();
|
|
||||||
picker.hide();
|
|
||||||
table.bootstrapTable("triggerSearch");
|
table.bootstrapTable("triggerSearch");
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
bootstrap.Popover.getOrCreateInstance(statusBadge);
|
statusBadge.popover();
|
||||||
selectRepository();
|
selectRepository();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -105,13 +105,13 @@ SigLevel = Database{% if has_repo_signed %}Required{% else %}Never{% endif %} Pa
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const table = $(document.getElementById("packages"));
|
const table = $("#packages");
|
||||||
|
|
||||||
const pacmanConf = document.getElementById("pacman-conf");
|
const pacmanConf = $("#pacman-conf");
|
||||||
const pacmanConfCopyButton = document.getElementById("copy-btn");
|
const pacmanConfCopyButton = $("#copy-btn");
|
||||||
|
|
||||||
async function copyPacmanConf() {
|
async function copyPacmanConf() {
|
||||||
const conf = pacmanConf.textContent;
|
const conf = pacmanConf.text();
|
||||||
await copyToClipboard(conf, pacmanConfCopyButton);
|
await copyToClipboard(conf, pacmanConfCopyButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,36 +127,24 @@ SigLevel = Database{% if has_repo_signed %}Required{% else %}Never{% endif %} Pa
|
|||||||
return extractDataList(table.bootstrapTable("getData"), "licenses");
|
return extractDataList(table.bootstrapTable("getData"), "licenses");
|
||||||
}
|
}
|
||||||
|
|
||||||
ready(_ => {
|
$(_ => {
|
||||||
table.on("created-controls.bs.table", _ => {
|
table.on("created-controls.bs.table", _ => {
|
||||||
new easepick.create({
|
const pickerInput = $(".bootstrap-table-filter-control-timestamp");
|
||||||
element: document.querySelector(".bootstrap-table-filter-control-timestamp"),
|
pickerInput.daterangepicker({
|
||||||
css: [
|
autoUpdateInput: false,
|
||||||
"https://cdn.jsdelivr.net/npm/@easepick/bundle@1.2.1/dist/index.css",
|
|
||||||
],
|
|
||||||
grid: 2,
|
|
||||||
calendars: 2,
|
|
||||||
autoApply: false,
|
|
||||||
locale: {
|
locale: {
|
||||||
cancel: "Clear",
|
cancelLabel: "Clear",
|
||||||
},
|
},
|
||||||
RangePlugin: {
|
});
|
||||||
tooltip: false,
|
|
||||||
},
|
pickerInput.on("apply.daterangepicker", (event, picker) => {
|
||||||
plugins: [
|
pickerInput.val(`${picker.startDate.format("YYYY-MM-DD")} - ${picker.endDate.format("YYYY-MM-DD")}`);
|
||||||
"RangePlugin",
|
table.bootstrapTable("triggerSearch");
|
||||||
],
|
});
|
||||||
setup: picker => {
|
|
||||||
picker.on("select", _ => { table.bootstrapTable("triggerSearch"); });
|
pickerInput.on("cancel.daterangepicker", _ => {
|
||||||
// replace "Cancel" behaviour to "Clear"
|
pickerInput.val("");
|
||||||
picker.onClickCancelButton = element => {
|
|
||||||
if (picker.isCancelButton(element)) {
|
|
||||||
picker.clear();
|
|
||||||
picker.hide();
|
|
||||||
table.bootstrapTable("triggerSearch");
|
table.bootstrapTable("triggerSearch");
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,30 +1,41 @@
|
|||||||
<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/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/jquery.md5@1.0.2/index.min.js" crossorigin="anonymous" type="application/javascript"></script>
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/tableexport.jquery.plugin@1.30.0/tableExport.min.js" crossorigin="anonymous" type="application/javascript"></script>
|
<script src="https://cdn.jsdelivr.net/npm/moment@2.29.4/moment.min.js" crossorigin="anonymous" type="application/javascript"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/daterangepicker@3.1.0/daterangepicker.min.js" crossorigin="anonymous" type="application/javascript"></script>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/tableexport.jquery.plugin@1.28.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/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/@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.3/dist/js/bootstrap.min.js" crossorigin="anonymous" type="application/javascript"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.min.js" crossorigin="anonymous" type="application/javascript"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.2/dist/bootstrap-table.min.js" crossorigin="anonymous" type="application/javascript"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.22.1/dist/bootstrap-table.min.js" crossorigin="anonymous" type="application/javascript"></script>
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.2/dist/extensions/export/bootstrap-table-export.min.js" crossorigin="anonymous" type="application/javascript"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.22.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.23.2/dist/extensions/resizable/bootstrap-table-resizable.js" crossorigin="anonymous" type="application/javascript"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.22.1/dist/extensions/resizable/bootstrap-table-resizable.js" crossorigin="anonymous" type="application/javascript"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.2/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.22.1/dist/extensions/filter-control/bootstrap-table-filter-control.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.9.0/build/highlight.min.js" crossorigin="anonymous" type="application/javascript"></script>
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.10.0/build/highlight.min.js" crossorigin="anonymous" type="application/javascript"></script>
|
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.4/dist/chart.umd.min.js" crossorigin="anonymous" type="application/javascript"></script>
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.4/dist/chart.umd.min.js" crossorigin="anonymous" type="application/javascript"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
async function copyToClipboard(text, button) {
|
async function copyToClipboard(text, button) {
|
||||||
|
if (navigator.clipboard === undefined) {
|
||||||
|
const input = document.createElement("textarea");
|
||||||
|
input.innerHTML = text;
|
||||||
|
document.body.appendChild(input);
|
||||||
|
input.select();
|
||||||
|
document.execCommand("copy");
|
||||||
|
document.body.removeChild(input);
|
||||||
|
} else {
|
||||||
await navigator.clipboard.writeText(text);
|
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";
|
button.html("<i class=\"bi bi-clipboard-check\"></i> copied");
|
||||||
|
setTimeout(()=> {
|
||||||
|
button.html("<i class=\"bi bi-clipboard\"></i> copy");
|
||||||
}, 2000);
|
}, 2000);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,47 +76,6 @@
|
|||||||
.join("<br>");
|
.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 ready(fn) {
|
|
||||||
if (document.readyState === "complete" || document.readyState === "interactive") {
|
|
||||||
setTimeout(fn, 1);
|
|
||||||
} else {
|
|
||||||
document.addEventListener("DOMContentLoaded", fn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function safe(string) {
|
function safe(string) {
|
||||||
return String(string)
|
return String(string)
|
||||||
.replace(/&/g, "&")
|
.replace(/&/g, "&")
|
||||||
@ -119,9 +89,7 @@
|
|||||||
const element = document.createElement("a");
|
const element = document.createElement("a");
|
||||||
element.href = url;
|
element.href = url;
|
||||||
element.innerText = text;
|
element.innerText = text;
|
||||||
if (title) {
|
if (title) element.title = title;
|
||||||
element.title = title;
|
|
||||||
}
|
|
||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,15 +1,17 @@
|
|||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" crossorigin="anonymous" type="text/css">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" crossorigin="anonymous" type="text/css">
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css" crossorigin="anonymous" type="text/css">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css" crossorigin="anonymous" type="text/css">
|
||||||
|
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.2/dist/bootstrap-table.min.css" crossorigin="anonymous" type="text/css">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-table@1.22.1/dist/bootstrap-table.min.css" crossorigin="anonymous" type="text/css">
|
||||||
|
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jquery-resizable-columns@0.2.3/dist/jquery.resizableColumns.css" crossorigin="anonymous" type="text/css">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jquery-resizable-columns@0.2.3/dist/jquery.resizableColumns.css" crossorigin="anonymous" type="text/css">
|
||||||
|
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.2/dist/extensions/filter-control/bootstrap-table-filter-control.css" crossorigin="anonymous" type="text/css">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-table@1.22.1/dist/extensions/filter-control/bootstrap-table-filter-control.css" crossorigin="anonymous" type="text/css">
|
||||||
|
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootswatch@5.3.3/dist/cosmo/bootstrap.min.css" crossorigin="anonymous" type="text/css">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootswatch@5.3.2/dist/cosmo/bootstrap.min.css" crossorigin="anonymous" type="text/css">
|
||||||
|
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.10.0/build/styles/github.min.css" crossorigin="anonymous" type="text/css">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/daterangepicker@3.1.0/daterangepicker.css" crossorigin="anonymous" type="text/css">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/styles/github.min.css" crossorigin="anonymous" type="text/css">
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.pre-scrollable {
|
.pre-scrollable {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
.TH AHRIMAN "1" "2024\-09\-04" "ahriman" "Generated Python Manual"
|
.TH AHRIMAN "1" "2024\-08\-23" "ahriman" "Generated Python Manual"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
ahriman
|
ahriman
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
|
@ -17,4 +17,4 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
__version__ = "2.14.1"
|
__version__ = "2.14.0"
|
||||||
|
@ -75,9 +75,7 @@ class Lock(LazyLogging):
|
|||||||
"""
|
"""
|
||||||
self.path: Path | None = None
|
self.path: Path | None = None
|
||||||
if args.lock is not None:
|
if args.lock is not None:
|
||||||
self.path = args.lock
|
self.path = args.lock.with_stem(f"{args.lock.stem}_{repository_id.id}")
|
||||||
if not repository_id.is_empty:
|
|
||||||
self.path = self.path.with_stem(f"{args.lock.stem}_{repository_id.id}")
|
|
||||||
if not self.path.is_absolute():
|
if not self.path.is_absolute():
|
||||||
# prepend full path to the lock file
|
# prepend full path to the lock file
|
||||||
self.path = Path("/") / "run" / "ahriman" / self.path
|
self.path = Path("/") / "run" / "ahriman" / self.path
|
||||||
|
@ -27,7 +27,6 @@ from ahriman.core.configuration import Configuration
|
|||||||
from ahriman.core.database.migrations import Migrations
|
from ahriman.core.database.migrations import Migrations
|
||||||
from ahriman.core.database.operations import AuthOperations, BuildOperations, ChangesOperations, \
|
from ahriman.core.database.operations import AuthOperations, BuildOperations, ChangesOperations, \
|
||||||
DependenciesOperations, EventOperations, LogsOperations, PackageOperations, PatchOperations
|
DependenciesOperations, EventOperations, LogsOperations, PackageOperations, PatchOperations
|
||||||
from ahriman.models.repository_id import RepositoryId
|
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=too-many-ancestors
|
# pylint: disable=too-many-ancestors
|
||||||
@ -104,26 +103,23 @@ class SQLite(
|
|||||||
self.with_connection(lambda connection: Migrations.migrate(connection, configuration))
|
self.with_connection(lambda connection: Migrations.migrate(connection, configuration))
|
||||||
paths.chown(self.path)
|
paths.chown(self.path)
|
||||||
|
|
||||||
def package_clear(self, package_base: str, repository_id: RepositoryId | None = None) -> None:
|
def package_clear(self, package_base: str) -> None:
|
||||||
"""
|
"""
|
||||||
completely remove package from all tables
|
completely remove package from all tables
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
package_base(str): package base to remove
|
package_base(str): package base to remove
|
||||||
repository_id(RepositoryId, optional): repository unique identifier override (Default value = None)
|
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
This method completely removes the package from all tables and must be used, e.g. on package removal::
|
This method completely removes the package from all tables and must be used, e.g. on package removal::
|
||||||
|
|
||||||
>>> database.package_clear("ahriman")
|
>>> database.package_clear("ahriman")
|
||||||
"""
|
"""
|
||||||
self.build_queue_clear(package_base, repository_id)
|
self.build_queue_clear(package_base)
|
||||||
self.patches_remove(package_base, None)
|
self.patches_remove(package_base, [])
|
||||||
self.logs_remove(package_base, None, repository_id)
|
self.logs_remove(package_base, None)
|
||||||
self.changes_remove(package_base, repository_id)
|
self.changes_remove(package_base)
|
||||||
self.dependencies_remove(package_base, repository_id)
|
self.dependencies_remove(package_base)
|
||||||
|
|
||||||
self.package_remove(package_base, repository_id)
|
|
||||||
|
|
||||||
# remove local cache too
|
# remove local cache too
|
||||||
self._repository_paths.tree_clear(package_base)
|
self._repository_paths.tree_clear(package_base)
|
||||||
|
@ -213,7 +213,7 @@ class LocalClient(Client):
|
|||||||
Args:
|
Args:
|
||||||
package_base(str): package base to remove
|
package_base(str): package base to remove
|
||||||
"""
|
"""
|
||||||
self.database.package_clear(package_base, self.repository_id)
|
self.database.package_clear(package_base)
|
||||||
|
|
||||||
def package_status_update(self, package_base: str, status: BuildStatusEnum) -> None:
|
def package_status_update(self, package_base: str, status: BuildStatusEnum) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -145,6 +145,7 @@ class Watcher(LazyLogging):
|
|||||||
with self._lock:
|
with self._lock:
|
||||||
self._known.pop(package_base, None)
|
self._known.pop(package_base, None)
|
||||||
self.client.package_remove(package_base)
|
self.client.package_remove(package_base)
|
||||||
|
self.package_logs_remove(package_base, None)
|
||||||
|
|
||||||
def package_status_update(self, package_base: str, status: BuildStatusEnum) -> None:
|
def package_status_update(self, package_base: str, status: BuildStatusEnum) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -41,12 +41,9 @@ class RepositoryId:
|
|||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
str: unique id for this repository
|
str: unique id for this repository
|
||||||
|
|
||||||
Raises:
|
|
||||||
ValueError: if repository identifier is empty
|
|
||||||
"""
|
"""
|
||||||
if self.is_empty:
|
if self.is_empty:
|
||||||
raise ValueError("Repository ID is called on empty repository identifier")
|
return ""
|
||||||
return f"{self.architecture}-{self.name}" # basically the same as used for command line
|
return f"{self.architecture}-{self.name}" # basically the same as used for command line
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -14,7 +14,6 @@ from ahriman.core.configuration import Configuration
|
|||||||
from ahriman.core.exceptions import DuplicateRunError, UnsafeRunError
|
from ahriman.core.exceptions import DuplicateRunError, UnsafeRunError
|
||||||
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
|
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
|
||||||
from ahriman.models.internal_status import InternalStatus
|
from ahriman.models.internal_status import InternalStatus
|
||||||
from ahriman.models.repository_id import RepositoryId
|
|
||||||
|
|
||||||
|
|
||||||
def test_path(args: argparse.Namespace, configuration: Configuration) -> None:
|
def test_path(args: argparse.Namespace, configuration: Configuration) -> None:
|
||||||
@ -31,8 +30,6 @@ def test_path(args: argparse.Namespace, configuration: Configuration) -> None:
|
|||||||
args.lock = Path("ahriman.pid")
|
args.lock = Path("ahriman.pid")
|
||||||
assert Lock(args, repository_id, configuration).path == Path("/run/ahriman/ahriman_x86_64-aur-clone.pid")
|
assert Lock(args, repository_id, configuration).path == Path("/run/ahriman/ahriman_x86_64-aur-clone.pid")
|
||||||
|
|
||||||
assert Lock(args, RepositoryId("", ""), configuration).path == Path("/run/ahriman/ahriman.pid")
|
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
args.lock = Path("/")
|
args.lock = Path("/")
|
||||||
assert Lock(args, repository_id, configuration).path # special case
|
assert Lock(args, repository_id, configuration).path # special case
|
||||||
|
@ -4,7 +4,6 @@ from pytest_mock import MockerFixture
|
|||||||
|
|
||||||
from ahriman.core.configuration import Configuration
|
from ahriman.core.configuration import Configuration
|
||||||
from ahriman.core.database import SQLite
|
from ahriman.core.database import SQLite
|
||||||
from ahriman.models.repository_id import RepositoryId
|
|
||||||
|
|
||||||
|
|
||||||
def test_load(configuration: Configuration, mocker: MockerFixture) -> None:
|
def test_load(configuration: Configuration, mocker: MockerFixture) -> None:
|
||||||
@ -36,7 +35,7 @@ def test_init_skip_migration(database: SQLite, configuration: Configuration, moc
|
|||||||
migrate_schema_mock.assert_not_called()
|
migrate_schema_mock.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
def test_package_clear(database: SQLite, repository_id: RepositoryId, mocker: MockerFixture) -> None:
|
def test_package_clear(database: SQLite, mocker: MockerFixture) -> None:
|
||||||
"""
|
"""
|
||||||
must clear package data
|
must clear package data
|
||||||
"""
|
"""
|
||||||
@ -45,14 +44,12 @@ def test_package_clear(database: SQLite, repository_id: RepositoryId, mocker: Mo
|
|||||||
logs_mock = mocker.patch("ahriman.core.database.SQLite.logs_remove")
|
logs_mock = mocker.patch("ahriman.core.database.SQLite.logs_remove")
|
||||||
changes_mock = mocker.patch("ahriman.core.database.SQLite.changes_remove")
|
changes_mock = mocker.patch("ahriman.core.database.SQLite.changes_remove")
|
||||||
dependencies_mock = mocker.patch("ahriman.core.database.SQLite.dependencies_remove")
|
dependencies_mock = mocker.patch("ahriman.core.database.SQLite.dependencies_remove")
|
||||||
package_mock = mocker.patch("ahriman.core.database.SQLite.package_remove")
|
|
||||||
tree_clear_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_clear")
|
tree_clear_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_clear")
|
||||||
|
|
||||||
database.package_clear("package", repository_id)
|
database.package_clear("package")
|
||||||
build_queue_mock.assert_called_once_with("package", repository_id)
|
build_queue_mock.assert_called_once_with("package")
|
||||||
patches_mock.assert_called_once_with("package", None)
|
patches_mock.assert_called_once_with("package", [])
|
||||||
logs_mock.assert_called_once_with("package", None, repository_id)
|
logs_mock.assert_called_once_with("package", None)
|
||||||
changes_mock.assert_called_once_with("package", repository_id)
|
changes_mock.assert_called_once_with("package")
|
||||||
dependencies_mock.assert_called_once_with("package", repository_id)
|
dependencies_mock.assert_called_once_with("package")
|
||||||
package_mock.assert_called_once_with("package", repository_id)
|
|
||||||
tree_clear_mock.assert_called_once_with("package")
|
tree_clear_mock.assert_called_once_with("package")
|
||||||
|
@ -180,7 +180,7 @@ def test_package_remove(local_client: LocalClient, package_ahriman: Package, moc
|
|||||||
"""
|
"""
|
||||||
package_mock = mocker.patch("ahriman.core.database.SQLite.package_clear")
|
package_mock = mocker.patch("ahriman.core.database.SQLite.package_clear")
|
||||||
local_client.package_remove(package_ahriman.base)
|
local_client.package_remove(package_ahriman.base)
|
||||||
package_mock.assert_called_once_with(package_ahriman.base, local_client.repository_id)
|
package_mock.assert_called_once_with(package_ahriman.base)
|
||||||
|
|
||||||
|
|
||||||
def test_package_status_update(local_client: LocalClient, package_ahriman: Package, mocker: MockerFixture) -> None:
|
def test_package_status_update(local_client: LocalClient, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||||
|
@ -101,11 +101,13 @@ def test_package_remove(watcher: Watcher, package_ahriman: Package, mocker: Mock
|
|||||||
must remove package base
|
must remove package base
|
||||||
"""
|
"""
|
||||||
cache_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_remove")
|
cache_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_remove")
|
||||||
|
logs_mock = mocker.patch("ahriman.core.status.watcher.Watcher.package_logs_remove", create=True)
|
||||||
watcher._known = {package_ahriman.base: (package_ahriman, BuildStatus())}
|
watcher._known = {package_ahriman.base: (package_ahriman, BuildStatus())}
|
||||||
|
|
||||||
watcher.package_remove(package_ahriman.base)
|
watcher.package_remove(package_ahriman.base)
|
||||||
assert not watcher._known
|
assert not watcher._known
|
||||||
cache_mock.assert_called_once_with(package_ahriman.base)
|
cache_mock.assert_called_once_with(package_ahriman.base)
|
||||||
|
logs_mock.assert_called_once_with(package_ahriman.base, None)
|
||||||
|
|
||||||
|
|
||||||
def test_package_remove_unknown(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None:
|
def test_package_remove_unknown(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||||
|
@ -7,17 +7,10 @@ def test_id() -> None:
|
|||||||
"""
|
"""
|
||||||
must correctly generate id
|
must correctly generate id
|
||||||
"""
|
"""
|
||||||
|
assert RepositoryId("", "").id == ""
|
||||||
assert RepositoryId("arch", "repo").id == "arch-repo"
|
assert RepositoryId("arch", "repo").id == "arch-repo"
|
||||||
|
|
||||||
|
|
||||||
def test_id_empty() -> None:
|
|
||||||
"""
|
|
||||||
must raise exception on empty identifier
|
|
||||||
"""
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
assert RepositoryId("", "").id
|
|
||||||
|
|
||||||
|
|
||||||
def test_is_empty() -> None:
|
def test_is_empty() -> None:
|
||||||
"""
|
"""
|
||||||
must check if repository id is empty or not
|
must check if repository id is empty or not
|
||||||
|
@ -201,7 +201,7 @@ def test_service_not_found(base: BaseView) -> None:
|
|||||||
must raise HTTPNotFound if no repository found
|
must raise HTTPNotFound if no repository found
|
||||||
"""
|
"""
|
||||||
with pytest.raises(HTTPNotFound):
|
with pytest.raises(HTTPNotFound):
|
||||||
base.service(RepositoryId("repo", "arch"))
|
base.service(RepositoryId("", ""))
|
||||||
|
|
||||||
|
|
||||||
def test_service_package(base: BaseView, repository_id: RepositoryId, mocker: MockerFixture) -> None:
|
def test_service_package(base: BaseView, repository_id: RepositoryId, mocker: MockerFixture) -> None:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user