Compare commits

...

4 Commits
2.4.0 ... 2.4.1

17 changed files with 122 additions and 136 deletions

View File

@ -52,7 +52,7 @@ fi
# the build itself does not really work in the container # the build itself does not really work in the container
sudo -u ahriman -- ahriman package-add --now yay sudo -u ahriman -- ahriman package-add --now yay
# check if package was actually installed # check if package was actually installed
#test -n "$(find "/var/lib/ahriman/repository/x86_64" -name "yay*pkg*")" test -n "$(find "/var/lib/ahriman/repository/x86_64" -name "yay*pkg*")"
# run package check # run package check
sudo -u ahriman -- ahriman repo-update sudo -u ahriman -- ahriman repo-update
# stop web service lol # stop web service lol

View File

@ -1,4 +1,4 @@
.TH AHRIMAN "1" "2022\-12\-05" "ahriman" "Generated Python Manual" .TH AHRIMAN "1" "2022\-12\-11" "ahriman" "Generated Python Manual"
.SH NAME .SH NAME
ahriman ahriman
.SH SYNOPSIS .SH SYNOPSIS

View File

@ -1,7 +1,7 @@
# Maintainer: Evgeniy Alekseev # Maintainer: Evgeniy Alekseev
pkgname='ahriman' pkgname='ahriman'
pkgver=2.4.0 pkgver=2.4.1
pkgrel=1 pkgrel=1
pkgdesc="ArcH linux ReposItory MANager" pkgdesc="ArcH linux ReposItory MANager"
arch=('any') arch=('any')

View File

@ -15,16 +15,15 @@
{% include "utils/bootstrap-scripts.jinja2" %} {% include "utils/bootstrap-scripts.jinja2" %}
<div class="container"> <div class="container">
<h1>ahriman <h1 id="badge-repository">ahriman</h1>
<img id="badge-version" src="https://img.shields.io/badge/version-unknown-informational" alt="unknown">
<img id="badge-repository" src="https://img.shields.io/badge/repository-unknown-informational" alt="unknown">
<img id="badge-architecture" src="https://img.shields.io/badge/architecture-unknown-informational" alt="unknown">
<img id="badge-status" src="https://img.shields.io/badge/service%20status-unknown-inactive" alt="unknown">
</h1>
</div> </div>
<div id="alert-placeholder" class="toast-container p3 top-0 start-50 translate-middle-x"></div>
<div class="container"> <div class="container">
<div id="toolbar" class="dropdown"> <div id="toolbar" class="dropdown">
<a id="badge-status" tabindex="0" role="button" class="btn btn-outline-secondary" data-bs-toggle="popover" data-bs-trigger="focus" data-bs-content="no run data"><i class="bi bi-info-circle"></i></a>
{% if not auth.enabled or auth.username is not none %} {% if not auth.enabled or auth.username is not none %}
<button type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false"> <button type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-box"></i> packages <i class="bi bi-box"></i> packages
@ -51,11 +50,13 @@
</button> </button>
</li> </li>
</ul> </ul>
<button id="key-import-btn" class="btn btn-info" data-bs-toggle="modal" data-bs-target="#key-import-modal" hidden>
<button id="key-import-btn" type="button" class="btn btn-info" data-bs-toggle="modal" data-bs-target="#key-import-modal" hidden>
<i class="bi bi-key"></i> import key <i class="bi bi-key"></i> import key
</button> </button>
{% endif %} {% endif %}
<button class="btn btn-secondary" onclick="reload()">
<button type="button" class="btn btn-secondary" onclick="reload()">
<i class="bi bi-arrow-clockwise"></i> reload <i class="bi bi-arrow-clockwise"></i> reload
</button> </button>
</div> </div>
@ -96,7 +97,7 @@
<div class="container"> <div class="container">
<footer class="d-flex flex-wrap justify-content-between align-items-center border-top"> <footer class="d-flex flex-wrap justify-content-between align-items-center border-top">
<ul class="nav"> <ul class="nav">
<li><a class="nav-link" href="https://github.com/arcan1s/ahriman" title="sources"><i class="bi bi-github"></i> ahriman</a></li> <li><a id="badge-version" class="nav-link" href="https://github.com/arcan1s/ahriman" title="sources"><i class="bi bi-github"></i> ahriman</a></li>
<li><a class="nav-link" href="https://github.com/arcan1s/ahriman/releases" title="releases list">releases</a></li> <li><a class="nav-link" href="https://github.com/arcan1s/ahriman/releases" title="releases list">releases</a></li>
<li><a class="nav-link" href="https://github.com/arcan1s/ahriman/issues" title="issues tracker">report a bug</a></li> <li><a class="nav-link" href="https://github.com/arcan1s/ahriman/issues" title="issues tracker">report a bug</a></li>
</ul> </ul>
@ -127,8 +128,7 @@
{% include "build-status/login-modal.jinja2" %} {% include "build-status/login-modal.jinja2" %}
{% endif %} {% endif %}
{% include "build-status/failed-modal.jinja2" %} {% include "build-status/alerts.jinja2" %}
{% include "build-status/success-modal.jinja2" %}
{% include "build-status/package-add-modal.jinja2" %} {% include "build-status/package-add-modal.jinja2" %}
{% include "build-status/package-rebuild-modal.jinja2" %} {% include "build-status/package-rebuild-modal.jinja2" %}

View File

@ -0,0 +1,45 @@
<script>
const alertPlaceholder = $("#alert-placeholder");
function createAlert(title, message, clz) {
const wrapper = document.createElement("div");
wrapper.classList.add("toast", clz);
wrapper.role = "alert";
wrapper.ariaLive = "assertive";
wrapper.ariaAtomic = "true";
wrapper.style.width = "500px"; // 500px is default modal size
const header = document.createElement("div");
header.classList.add("toast-header");
header.innerHTML = `<strong class="me-auto">${safe(title)}</strong> <button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="close"></button>`;
wrapper.appendChild(header);
const body = document.createElement("div");
body.classList.add("toast-body", "text-bg-light");
body.innerText = message;
wrapper.appendChild(body);
alertPlaceholder.append(wrapper);
const toast = new bootstrap.Toast(wrapper);
wrapper.addEventListener("hidden.bs.toast", () => {
wrapper.remove(); // bootstrap doesn't remove elements
reload();
});
toast.show();
}
function showFailure(title, description, jqXHR, errorThrown) {
let details;
try {
details = $.parseJSON(jqXHR.responseText).error; // execution handler json error response
} catch (_) {
details = errorThrown;
}
createAlert(title, description(details), "text-bg-danger");
}
function showSuccess(title, description) {
createAlert(title, description, "text-bg-success");
}
</script>

View File

@ -1,34 +0,0 @@
<div id="failed-modal" tabindex="-1" role="dialog" class="modal fade">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header bg-danger text-white">
<h4 id="failed-title" class="modal-title"></h4>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="close"></button>
</div>
<div class="modal-body">
<p id="failed-description"></p>
<p id="failed-details"></p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" data-bs-dismiss="modal"><i class="bi bi-x"></i> close</button>
</div>
</div>
</div>
</div>
<script>
const failedModal = $("#failed-modal");
failedModal.on("hidden.bs.modal", () => { reload(); });
const failedDescription = $("#failed-description");
const failedDetails = $("#failed-details");
const failedTitle = $("#failed-title");
function showFailure(title, description, details) {
failedTitle.text(title);
failedDescription.text(description);
failedDetails.text(details);
failedModal.modal("show");
}
</script>

View File

@ -22,7 +22,7 @@
<div class="form-group row"> <div class="form-group row">
<div class="col-sm-2"></div> <div class="col-sm-2"></div>
<div class="col-sm-10"> <div class="col-sm-10">
<pre class="language-less"><code id="key-body-input" class="pre-scrollable language-less"></code><button id="key-copy-btn" type="button" class="btn language-less" onclick="copyPgpKey()"><i class="bi bi-clipboard"></i> copy</button></pre> <pre class="language-less"><samp id="key-body-input" class="pre-scrollable language-less"></samp><button id="key-copy-btn" type="button" class="btn language-less" onclick="copyPgpKey()"><i class="bi bi-clipboard"></i> copy</button></pre>
</div> </div>
</div> </div>
</div> </div>
@ -81,10 +81,11 @@
contentType: "application/json", contentType: "application/json",
success: _ => { success: _ => {
keyImportModal.modal("hide"); keyImportModal.modal("hide");
showSuccess("Success", `Key ${key} has been imported`, ""); showSuccess("Success", `Key ${key} has been imported`);
}, },
error: (jqXHR, _, errorThrown) => { error: (jqXHR, _, errorThrown) => {
showFailure("Action failed", `Could not import key ${key} from ${server}`, errorThrown); const message = _ => { return `Could not import key ${key} from ${server}`; };
showFailure("Action failed", message, jqXHR, errorThrown);
}, },
}); });
} }

View File

@ -60,7 +60,9 @@
const packages = packageInput.val(); const packages = packageInput.val();
if (packages) { if (packages) {
packageAddModal.modal("hide"); packageAddModal.modal("hide");
doPackageAction("/api/v1/service/add", [packages], "The following package has been added:", "Package addition failed:"); const onSuccess = update => { return `Packages ${update} have been added`; };
const onFailure = error => { return `Package addition failed: ${error}`; };
doPackageAction("/api/v1/service/add", [packages], onSuccess, onFailure);
} }
} }
@ -68,7 +70,9 @@
const packages = packageInput.val(); const packages = packageInput.val();
if (packages) { if (packages) {
packageAddModal.modal("hide"); packageAddModal.modal("hide");
doPackageAction("/api/v1/service/request", [packages], "The following package has been requested:", "Package request failed:"); const onSuccess = update => { return `Packages ${update} have been requested`; };
const onFailure = error => { return `Package request failed: ${error}`; };
doPackageAction("/api/v1/service/request", [packages], onSuccess, onFailure);
} }
} }
</script> </script>

View File

@ -6,7 +6,7 @@
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="close"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<pre class="language-logs"><code id="package-info-logs-input" class="pre-scrollable language-logs"></code><button id="logs-copy-btn" type="button" class="btn language-logs" onclick="copyLogs()"><i class="bi bi-clipboard"></i> copy</button></pre> <pre class="language-logs"><samp id="package-info-logs-input" class="pre-scrollable language-logs"></samp><button id="logs-copy-btn" type="button" class="btn language-logs" onclick="copyLogs()"><i class="bi bi-clipboard"></i> copy</button></pre>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" onclick="showLogs()"><i class="bi bi-arrow-clockwise"></i> reload</button> <button type="button" class="btn btn-secondary" onclick="showLogs()"><i class="bi bi-arrow-clockwise"></i> reload</button>
@ -60,7 +60,10 @@
}, },
error: (jqXHR, _, errorThrown) => { error: (jqXHR, _, errorThrown) => {
// show failed modal in case if first time loading // show failed modal in case if first time loading
if (isPackageBaseSet) showFailure("Load failure", `Could not load package ${packageBase} logs:`, errorThrown); if (isPackageBaseSet) {
const message = error => { return `Could not load package ${packageBase} logs: ${error}`; };
showFailure("Load failure", message, jqXHR, errorThrown);
}
}, },
}); });
} }

View File

@ -33,7 +33,9 @@
const packages = dependencyInput.val(); const packages = dependencyInput.val();
if (packages) { if (packages) {
packageRebuildModal.modal("hide"); packageRebuildModal.modal("hide");
doPackageAction("/api/v1/service/rebuild", [packages], "Repository rebuild ran for the following dependencies:", "Repository rebuild failed:"); const onSuccess = update => { return `Repository rebuild has been run for packages which depend on ${update}`; };
const onFailure = error => { return `Repository rebuild failed: ${error}`; };
doPackageAction("/api/v1/service/rebuild", [packages], onSuccess, onFailure);
} }
} }
</script> </script>

View File

@ -1,34 +0,0 @@
<div id="success-modal" tabindex="-1" role="dialog" class="modal fade">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header bg-success text-white">
<h4 id="success-title" class="modal-title"></h4>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="close"></button>
</div>
<div class="modal-body">
<p id="success-description"></p>
<ul id="success-details"></ul>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" data-bs-dismiss="modal"><i class="bi bi-x"></i> close</button>
</div>
</div>
</div>
</div>
<script>
const successModal = $("#success-modal");
successModal.on("hidden.bs.modal", () => { reload(); });
const successDescription = $("#success-description");
const successDetails = $("#success-details");
const successTitle = $("#success-title");
function showSuccess(title, description, details) {
successTitle.text(title);
successDescription.text(description);
successDetails.empty().append(details);
successModal.modal("show");
}
</script>

View File

@ -16,7 +16,6 @@
} else showLogs(data.id); } else showLogs(data.id);
}); });
const architectureBadge = $("#badge-architecture");
const repositoryBadge = $("#badge-repository"); const repositoryBadge = $("#badge-repository");
const statusBadge = $("#badge-status"); const statusBadge = $("#badge-status");
const versionBadge = $("#badge-version"); const versionBadge = $("#badge-version");
@ -28,15 +27,11 @@
type: "POST", type: "POST",
contentType: "application/json", contentType: "application/json",
success: _ => { success: _ => {
const details = packages.map(pkg => { const message = successText(packages.join(", "));
const li = document.createElement("li"); showSuccess("Success", message);
li.innerText = pkg;
return li;
});
showSuccess("Success", successText, details);
}, },
error: (jqXHR, _, errorThrown) => { error: (jqXHR, _, errorThrown) => {
showFailure("Action failed", failureText, errorThrown); showFailure("Action failed", failureText, jqXHR, errorThrown);
}, },
}); });
} }
@ -46,13 +41,18 @@
} }
function removePackages() { function removePackages() {
doPackageAction("/api/v1/service/remove", getSelection(), "The following packages have been removed:", "Packages removal failed:"); const onSuccess = update => { return `Packages ${update} have been removed`; };
const onFailure = error => { return `Could not remove packages: ${error}`; };
doPackageAction("/api/v1/service/remove", getSelection(), onSuccess, onFailure);
} }
function updatePackages() { function updatePackages() {
const currentSelection = getSelection(); const currentSelection = getSelection();
const url = currentSelection.length === 0 ? "/api/v1/service/update" : "/api/v1/service/add"; const [url, onSuccess] = currentSelection.length === 0
doPackageAction(url, getSelection(), "Packages update has been run", "Packages update failed:"); ? ["/api/v1/service/update", _ => { return "Repository update has been run"; }]
: ["/api/v1/service/add", update => { return `Run update for packages ${update}`; }];
const onFailure = error => { return `Packages update failed: ${error}`; };
doPackageAction(url, currentSelection, onSuccess, onFailure);
} }
function hideControls(hidden) { function hideControls(hidden) {
@ -66,6 +66,14 @@
function reload() { function reload() {
table.bootstrapTable("showLoading"); table.bootstrapTable("showLoading");
const badgeClass = status => {
if (status === "pending") return "btn-outline-warning";
if (status === "building") return "btn-outline-warning";
if (status === "failed") return "btn-outline-danger";
if (status === "success") return "btn-outline-success";
return "btn-outline-secondary";
};
$.ajax({ $.ajax({
url: "/api/v1/packages", url: "/api/v1/packages",
type: "GET", type: "GET",
@ -112,7 +120,8 @@
table.bootstrapTable("hideLoading"); table.bootstrapTable("hideLoading");
} else { } else {
// other errors // other errors
showFailure("Load failure", "Could not load list of packages:", errorThrown); const messaga = error => { return `Could not load list of packages: ${error}`; };
showFailure("Load failure", messaga, jqXHR, errorThrown);
} }
hideControls(true); hideControls(true);
}, },
@ -123,39 +132,20 @@
type: "GET", type: "GET",
dataType: "json", dataType: "json",
success: response => { success: response => {
const badgeColor = status => { repositoryBadge.text(`${response.repository} ${response.architecture}`);
if (status === "pending") return "yellow"; versionBadge.html(`<i class="bi bi-github"></i> ahriman ${safe(response.version)}`);
if (status === "building") return "yellow";
if (status === "failed") return "critical";
if (status === "success") return "success";
return "inactive";
};
architectureBadge
.attr("src", `https://img.shields.io/badge/architecture-${response.architecture}-informational`)
.attr("alt", response.architecture);
repositoryBadge
.attr("src", `https://img.shields.io/badge/repository-${response.repository.replace(/-/g, "--")}-informational`)
.attr("alt", response.repository);
statusBadge statusBadge
.attr("src", `https://img.shields.io/badge/service%20status-${response.status.status}-${badgeColor(response.status.status)}`) .popover("dispose")
.attr("alt", response.status.status) .attr("data-bs-content", `${response.status.status} at ${new Date(1000 * response.status.timestamp).toISOString()}`)
.attr("title", `at ${new Date(1000 * response.status.timestamp).toISOString()}`); .popover();
versionBadge statusBadge.removeClass();
.attr("src", `https://img.shields.io/badge/version-${response.version}-informational`) statusBadge.addClass("btn");
.attr("alt", response.version); statusBadge.addClass(badgeClass(response.status.status));
}, },
}); });
} }
function safe(string) {
return String(string)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;");
}
function statusFormat(value) { function statusFormat(value) {
const cellClass = status => { const cellClass = status => {
if (status === "pending") return "table-warning"; if (status === "pending") return "table-warning";
@ -169,6 +159,7 @@
$(() => { $(() => {
table.bootstrapTable({}); table.bootstrapTable({});
statusBadge.popover();
reload(); reload();
}); });
</script> </script>

View File

@ -30,4 +30,12 @@
button.html("<i class=\"bi bi-clipboard\"></i> copy"); button.html("<i class=\"bi bi-clipboard\"></i> copy");
}, 2000); }, 2000);
} }
function safe(string) {
return String(string)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;");
}
</script> </script>

View File

@ -3,7 +3,9 @@
<link rel="stylesheet" href="https://unpkg.com/bootstrap-table@1.21.1/dist/bootstrap-table.min.css" type="text/css"> <link rel="stylesheet" href="https://unpkg.com/bootstrap-table@1.21.1/dist/bootstrap-table.min.css" type="text/css">
<link href="https://unpkg.com/jquery-resizable-columns@0.2.3/dist/jquery.resizableColumns.css" rel="stylesheet"> <link rel="stylesheet" href="https://unpkg.com/jquery-resizable-columns@0.2.3/dist/jquery.resizableColumns.css" type="text/css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootswatch@5.2.2/dist/cosmo/bootstrap.min.css" integrity="sha256-5t++JZpgVLzo9vF7snO5Qw0y3fA5/NkoJENWB7kpg0E=" crossorigin="anonymous" type="text/css">
<style> <style>
.pre-scrollable { .pre-scrollable {
@ -16,7 +18,7 @@
position: relative; position: relative;
} }
pre[class*="language-"] button{ pre[class*="language-"] button {
position: absolute; position: absolute;
top: 0; top: 0;
right: 5px; right: 5px;

View File

@ -72,13 +72,12 @@ setup(
"package/share/ahriman/templates/telegram-index.jinja2", "package/share/ahriman/templates/telegram-index.jinja2",
]), ]),
("share/ahriman/templates/build-status", [ ("share/ahriman/templates/build-status", [
"package/share/ahriman/templates/build-status/failed-modal.jinja2", "package/share/ahriman/templates/build-status/alerts.jinja2",
"package/share/ahriman/templates/build-status/key-import-modal.jinja2", "package/share/ahriman/templates/build-status/key-import-modal.jinja2",
"package/share/ahriman/templates/build-status/login-modal.jinja2", "package/share/ahriman/templates/build-status/login-modal.jinja2",
"package/share/ahriman/templates/build-status/package-add-modal.jinja2", "package/share/ahriman/templates/build-status/package-add-modal.jinja2",
"package/share/ahriman/templates/build-status/package-info-modal.jinja2", "package/share/ahriman/templates/build-status/package-info-modal.jinja2",
"package/share/ahriman/templates/build-status/package-rebuild-modal.jinja2", "package/share/ahriman/templates/build-status/package-rebuild-modal.jinja2",
"package/share/ahriman/templates/build-status/success-modal.jinja2",
"package/share/ahriman/templates/build-status/table.jinja2", "package/share/ahriman/templates/build-status/table.jinja2",
]), ]),
("share/ahriman/templates/static", [ ("share/ahriman/templates/static", [

View File

@ -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.4.0" __version__ = "2.4.1"

View File

@ -339,13 +339,12 @@ def test_walk(resource_path_root: Path) -> None:
resource_path_root / "models" / "package_gcc10_srcinfo", resource_path_root / "models" / "package_gcc10_srcinfo",
resource_path_root / "models" / "package_tpacpi-bat-git_srcinfo", resource_path_root / "models" / "package_tpacpi-bat-git_srcinfo",
resource_path_root / "models" / "package_yay_srcinfo", resource_path_root / "models" / "package_yay_srcinfo",
resource_path_root / "web" / "templates" / "build-status" / "failed-modal.jinja2", resource_path_root / "web" / "templates" / "build-status" / "alerts.jinja2",
resource_path_root / "web" / "templates" / "build-status" / "key-import-modal.jinja2", resource_path_root / "web" / "templates" / "build-status" / "key-import-modal.jinja2",
resource_path_root / "web" / "templates" / "build-status" / "login-modal.jinja2", resource_path_root / "web" / "templates" / "build-status" / "login-modal.jinja2",
resource_path_root / "web" / "templates" / "build-status" / "package-add-modal.jinja2", resource_path_root / "web" / "templates" / "build-status" / "package-add-modal.jinja2",
resource_path_root / "web" / "templates" / "build-status" / "package-info-modal.jinja2", resource_path_root / "web" / "templates" / "build-status" / "package-info-modal.jinja2",
resource_path_root / "web" / "templates" / "build-status" / "package-rebuild-modal.jinja2", resource_path_root / "web" / "templates" / "build-status" / "package-rebuild-modal.jinja2",
resource_path_root / "web" / "templates" / "build-status" / "success-modal.jinja2",
resource_path_root / "web" / "templates" / "build-status" / "table.jinja2", resource_path_root / "web" / "templates" / "build-status" / "table.jinja2",
resource_path_root / "web" / "templates" / "static" / "favicon.ico", resource_path_root / "web" / "templates" / "static" / "favicon.ico",
resource_path_root / "web" / "templates" / "utils" / "bootstrap-scripts.jinja2", resource_path_root / "web" / "templates" / "utils" / "bootstrap-scripts.jinja2",