feat: get rid of jquery (#133)

This commit is contained in:
Evgenii Alekseev 2024-09-05 02:26:52 +03:00 committed by GitHub
parent f43ee2fd1d
commit c4f4e37731
11 changed files with 481 additions and 415 deletions

View File

@ -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" hidden> <button id="package-add-button" class="btn dropdown-item" data-bs-toggle="modal" data-bs-target="#package-add-modal">
<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()" hidden> <button id="package-update-button" class="btn dropdown-item" onclick="packagesUpdate()">
<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" hidden> <button id="package-rebuild-button" class="btn dropdown-item" data-bs-toggle="modal" data-bs-target="#package-rebuild-modal">
<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 hidden> <button id="package-remove-button" class="btn dropdown-item" onclick="packagesRemove()" disabled>
<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" hidden> <button id="key-import-button" type="button" class="btn btn-info" data-bs-toggle="modal" data-bs-target="#key-import-modal">
<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 %}

View File

@ -1,12 +1,12 @@
<script> <script>
const alertPlaceholder = $("#alert-placeholder"); const alertPlaceholder = document.getElementById("alert-placeholder");
function createAlert(title, message, clz, action, id) { function createAlert(title, message, clz, action, id) {
if (!id) id = $.md5(title + message); // MD5 id from the content id ??= md5(title + message); // MD5 id from the content
if (alertPlaceholder.find(`#${id}`).length > 0) return; // check if there are duplicates if (alertPlaceholder.querySelector(`#alert-${id}`)) return; // check if there are duplicates
const wrapper = document.createElement("div"); const wrapper = document.createElement("div");
wrapper.id = id; wrapper.id = `alert-${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.append(wrapper); alertPlaceholder.appendChild(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, jqXHR, errorThrown) { function showFailure(title, description, error) {
let details; let details;
try { try {
details = $.parseJSON(jqXHR.responseText).error; // execution handler json error response details = JSON.parse(error.text).error; // execution handler json error response
} catch (_) { } catch (_) {
details = errorThrown; details = error.text ?? error.message ?? error;
} }
createAlert(title, description(details), "text-bg-danger"); createAlert(title, description(details), "text-bg-danger");
} }

View File

@ -36,61 +36,69 @@
</div> </div>
<script> <script>
const keyImportModal = $("#key-import-modal"); const keyImportModal = document.getElementById("key-import-modal");
const keyImportForm = $("#key-import-form"); const keyImportForm = document.getElementById("key-import-form");
const keyImportBodyInput = $("#key-import-body-input"); const keyImportBodyInput = document.getElementById("key-import-body-input");
const keyImportCopyButton = $("#key-import-copy-button"); const keyImportCopyButton = document.getElementById("key-import-copy-button");
const keyImportFingerprintInput = $("#key-import-fingerprint-input"); const keyImportFingerprintInput = document.getElementById("key-import-fingerprint-input");
const keyImportServerInput = $("#key-import-server-input"); const keyImportServerInput = document.getElementById("key-import-server-input");
async function copyPgpKey() { async function copyPgpKey() {
const logs = keyImportBodyInput.text(); const key = keyImportBodyInput.textContent;
await copyToClipboard(logs, keyImportCopyButton); await copyToClipboard(key, keyImportCopyButton);
} }
function fetchPgpKey() { function fetchPgpKey() {
const key = keyImportFingerprintInput.val(); const key = keyImportFingerprintInput.value;
const server = keyImportServerInput.val(); const server = keyImportServerInput.value;
if (key && server) { if (key && server) {
$.ajax({ makeRequest(
url: "/api/v1/service/pgp", "/api/v1/service/pgp",
data: {"key": key, "server": server}, {
type: "GET", query: {
dataType: "json", key: key,
success: response => { keyImportBodyInput.text(response.key); }, server: server,
}); },
convert: response => response.json(),
},
data => { keyImportBodyInput.textContent = data.key; },
);
} }
} }
function importPgpKey() { function importPgpKey() {
const key = keyImportFingerprintInput.val(); const key = keyImportFingerprintInput.value;
const server = keyImportServerInput.val(); const server = keyImportServerInput.value;
if (key && server) { if (key && server) {
$.ajax({ makeRequest(
url: "/api/v1/service/pgp", "/api/v1/service/pgp",
data: JSON.stringify({key: key, server: server}), {
type: "POST", method: "POST",
contentType: "application/json", json: {
success: _ => { key: key,
keyImportModal.modal("hide"); server: server,
},
},
_ => {
bootstrap.Modal.getOrCreateInstance(keyImportModal).hide();
showSuccess("Success", `Key ${key} has been imported`); showSuccess("Success", `Key ${key} has been imported`);
}, },
error: (jqXHR, _, errorThrown) => { error => {
const message = _ => `Could not import key ${key} from ${server}`; const message = _ => `Could not import key ${key} from ${server}`;
showFailure("Action failed", message, jqXHR, errorThrown); showFailure("Action failed", message, error);
}, },
}); );
} }
} }
$(_ => { ready(_ => {
keyImportModal.on("hidden.bs.modal", _ => { keyImportModal.addEventListener("hidden.bs.modal", _ => {
keyImportBodyInput.text(""); keyImportBodyInput.textContent = "";
keyImportForm.trigger("reset"); keyImportForm.reset();
}); });
}); });
</script> </script>

View File

@ -34,53 +34,57 @@
</div> </div>
<script> <script>
const loginModal = $("#login-modal"); const loginModal = document.getElementById("login-modal");
const loginForm = $("#login-form"); const loginForm = document.getElementById("login-form");
const loginPasswordInput = $("#login-password"); const loginPasswordInput = document.getElementById("login-password");
const loginUsernameInput = $("#login-username"); const loginUsernameInput = document.getElementById("login-username");
const showHidePasswordButton = $("#login-show-hide-password-button"); const showHidePasswordButton = document.getElementById("login-show-hide-password-button");
function login() { function login() {
const password = loginPasswordInput.val(); const password = loginPasswordInput.value;
const username = loginUsernameInput.val(); const username = loginUsernameInput.value;
if (username && password) { if (username && password) {
$.ajax({ makeRequest(
url: "/api/v1/login", "/api/v1/login",
data: JSON.stringify({username: username, password: password}), {
type: "POST", method: "POST",
contentType: "application/json", json: {
success: _ => { username: username,
loginModal.modal("hide"); password: password,
},
},
_ => {
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: (jqXHR, _, errorThrown) => { error => {
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, jqXHR, errorThrown); showFailure("Login error", message, error);
}, },
}); );
} }
} }
function showPassword() { function showPassword() {
if (loginPasswordInput.attr("type") === "password") { if (loginPasswordInput.getAttribute("type") === "password") {
loginPasswordInput.attr("type", "text"); loginPasswordInput.setAttribute("type", "text");
showHidePasswordButton.removeClass("bi-eye"); showHidePasswordButton.classList.remove("bi-eye");
showHidePasswordButton.addClass("bi-eye-slash"); showHidePasswordButton.classList.add("bi-eye-slash");
} else { } else {
loginPasswordInput.attr("type", "password"); loginPasswordInput.setAttribute("type", "password");
showHidePasswordButton.removeClass("bi-eye-slash"); showHidePasswordButton.classList.remove("bi-eye-slash");
showHidePasswordButton.addClass("bi-eye"); showHidePasswordButton.classList.add("bi-eye");
} }
} }
$(_ => { ready(_ => {
loginModal.on("hidden.bs.modal", _ => { loginModal.addEventListener("hidden.bs.modal", _ => {
loginForm.trigger("reset"); loginForm.reset();
}); });
}); });
</script> </script>

View File

@ -41,14 +41,14 @@
</div> </div>
<script> <script>
const packageAddModal = $("#package-add-modal"); const packageAddModal = document.getElementById("package-add-modal");
const packageAddForm = $("#package-add-form"); const packageAddForm = document.getElementById("package-add-form");
const packageAddInput = $("#package-add-input"); const packageAddInput = document.getElementById("package-add-input");
const packageAddRepositoryInput = $("#package-add-repository-input"); const packageAddRepositoryInput = document.getElementById("package-add-repository-input");
const packageAddKnownPackagesList = $("#package-add-known-packages-dlist"); const packageAddKnownPackagesList = document.getElementById("package-add-known-packages-dlist");
const packageAddVariablesDiv = $("#package-add-variables-div"); const packageAddVariablesDiv = document.getElementById("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 = _ => { return variableInput.remove(); }; variableButtonRemove.onclick = _ => { variableInput.remove(); };
// bring them together // bring them together
variableInput.appendChild(variableNameInput); variableInput.appendChild(variableNameInput);
@ -86,27 +86,26 @@
variableInput.appendChild(variableValueInput); variableInput.appendChild(variableValueInput);
variableInput.appendChild(variableButtonRemove); variableInput.appendChild(variableButtonRemove);
packageAddVariablesDiv.append(variableInput); packageAddVariablesDiv.appendChild(variableInput);
} }
function patchesParse() { function patchesParse() {
const patches = packageAddVariablesDiv.find(".package-add-variable").map((_, element) => { const patches = Array.from(packageAddVariablesDiv.getElementsByClassName("package-add-variable")).map(element => {
const richElement = $(element);
return { return {
key: richElement.find(".package-add-variable-name").val(), key: element.querySelector(".package-add-variable-name").value,
value: richElement.find(".package-add-variable-value").val(), value: element.querySelector(".package-add-variable-value").value,
}; };
}).filter((_, patch) => patch.key).get(); }).filter(patch => patch.key);
return {patches: patches}; return {patches: patches};
} }
function packagesAdd(packages, patches, repository) { function packagesAdd(packages, patches, repository) {
packages = packages ?? packageAddInput.val(); packages = packages ?? packageAddInput.value;
patches = patches ?? patchesParse(); patches = patches ?? patchesParse();
repository = repository ?? getRepositorySelector(packageAddRepositoryInput); repository = repository ?? getRepositorySelector(packageAddRepositoryInput);
if (packages) { if (packages) {
packageAddModal.modal("hide"); bootstrap.Modal.getOrCreateInstance(packageAddModal).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);
@ -114,50 +113,54 @@
} }
function packagesRequest(packages, patches) { function packagesRequest(packages, patches) {
packages = packages ?? packageAddInput.val(); packages = packages ?? packageAddInput.value;
patches = patches ?? patchesParse(); patches = patches ?? patchesParse();
const repository = getRepositorySelector(packageAddRepositoryInput); const repository = getRepositorySelector(packageAddRepositoryInput);
if (packages) { if (packages) {
packageAddModal.modal("hide"); bootstrap.Modal.getOrCreateInstance(packageAddModal).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.on("shown.bs.modal", _ => { packageAddModal.addEventListener("shown.bs.modal", _ => {
$(`#package-add-repository-input option[value="${repository.architecture}-${repository.repository}"]`).prop("selected", true); const option = packageAddRepositoryInput.querySelector(`option[value="${repository.architecture}-${repository.repository}"]`);
option.selected = "selected";
}); });
packageAddModal.on("hidden.bs.modal", _ => { packageAddModal.addEventListener("hidden.bs.modal", _ => {
packageAddVariablesDiv.empty(); packageAddVariablesDiv.replaceChildren();
packageAddForm.trigger("reset"); packageAddForm.reset();
}); });
packageAddInput.keyup(_ => { packageAddInput.addEventListener("keyup", _ => {
clearTimeout(packageAddInput.data("timeout")); clearTimeout(packageAddInput.requestTimeout);
packageAddInput.data("timeout", setTimeout($.proxy(_ => { packageAddInput.requestTimeout = setTimeout(_ => {
const value = packageAddInput.val(); const value = packageAddInput.value;
if (value.length >= 3) { if (value.length >= 3) {
$.ajax({ makeRequest(
url: "/api/v1/service/search", "/api/v1/service/search",
data: {"for": value}, {
type: "GET", query: {
dataType: "json", for: value,
success: response => { },
const options = response.map(pkg => { convert: response => response.json(),
},
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.empty().append(options); packageAddKnownPackagesList.replaceChildren(...options);
}, },
}); );
} }
}, this), 500)); }, 500);
}); });
}); });
</script> </script>

View File

@ -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"></canvas> <canvas id="package-info-events-update-chart" hidden></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,8 +77,10 @@
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<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> {% if not auth.enabled or auth.username is not none %}
<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-update-button" type="submit" class="btn btn-success" onclick="packageInfoUpdate()" data-bs-dismiss="modal"><i class="bi bi-play"></i><span class="d-none d-sm-inline"> update</span></button>
<button id="package-info-remove-button" type="submit" class="btn btn-danger" onclick="packageInfoRemove()" data-bs-dismiss="modal"><i class="bi bi-trash"></i><span class="d-none d-sm-inline"> remove</span></button>
{% endif %}
<button type="button" class="btn btn-secondary" onclick="showPackageInfo()"><i class="bi bi-arrow-clockwise"></i><span class="d-none d-sm-inline"> reload</span></button> <button type="button" class="btn btn-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>
@ -87,33 +89,35 @@
</div> </div>
<script> <script>
const packageInfoModal = $("#package-info-modal"); const packageInfoModal = document.getElementById("package-info-modal");
const packageInfoModalHeader = $("#package-info-modal-header"); const packageInfoModalHeader = document.getElementById("package-info-modal-header");
const packageInfo = $("#package-info"); const packageInfo = document.getElementById("package-info");
const packageInfoLogsInput = $("#package-info-logs-input"); const packageInfoLogsInput = document.getElementById("package-info-logs-input");
const packageInfoLogsCopyButton = $("#package-info-logs-copy-button"); const packageInfoLogsCopyButton = document.getElementById("package-info-logs-copy-button");
const packageInfoChangesInput = $("#package-info-changes-input"); const packageInfoChangesInput = document.getElementById("package-info-changes-input");
const packageInfoChangesCopyButton = $("#package-info-changes-copy-button"); const packageInfoChangesCopyButton = document.getElementById("package-info-changes-copy-button");
const packageInfoEventsTable = $("#package-info-events-table"); // so far bootstrap-table only operates with jquery elements
const packageInfoEventsTable = $(document.getElementById("package-info-events-table"));
const packageInfoEventsUpdateChartCanvas = document.getElementById("package-info-events-update-chart"); const packageInfoEventsUpdateChartCanvas = document.getElementById("package-info-events-update-chart");
let packageInfoEventsUpdateChart = null; let packageInfoEventsUpdateChart = null;
const packageInfoAurUrl = $("#package-info-aur-url"); const packageInfoAurUrl = document.getElementById("package-info-aur-url");
const packageInfoDepends = $("#package-info-depends"); const packageInfoDepends = document.getElementById("package-info-depends");
const packageInfoGroups = $("#package-info-groups"); const packageInfoGroups = document.getElementById("package-info-groups");
const packageInfoLicenses = $("#package-info-licenses"); const packageInfoLicenses = document.getElementById("package-info-licenses");
const packageInfoPackager = $("#package-info-packager"); const packageInfoPackager = document.getElementById("package-info-packager");
const packageInfoPackages = $("#package-info-packages"); const packageInfoPackages = document.getElementById("package-info-packages");
const packageInfoUpstreamUrl = $("#package-info-upstream-url"); const packageInfoUpstreamUrl = document.getElementById("package-info-upstream-url");
const packageInfoVersion = $("#package-info-version"); const packageInfoVersion = document.getElementById("package-info-version");
const packageInfoVariablesBlock = $("#package-info-variables-block"); const packageInfoVariablesBlock = document.getElementById("package-info-variables-block");
const packageInfoVariablesDiv = $("#package-info-variables-div"); const packageInfoVariablesDiv = document.getElementById("package-info-variables-div");
function clearChart() { function clearChart() {
packageInfoEventsUpdateChartCanvas.hidden = true;
if (packageInfoEventsUpdateChart) { if (packageInfoEventsUpdateChart) {
packageInfoEventsUpdateChart.data = {}; packageInfoEventsUpdateChart.data = {};
packageInfoEventsUpdateChart.update(); packageInfoEventsUpdateChart.update();
@ -121,20 +125,15 @@
} }
async function copyChanges() { async function copyChanges() {
const changes = packageInfoChangesInput.text(); const changes = packageInfoChangesInput.textContent;
await copyToClipboard(changes, packageInfoChangesCopyButton); await copyToClipboard(changes, packageInfoChangesCopyButton);
} }
async function copyLogs() { async function copyLogs() {
const logs = packageInfoLogsInput.text(); const logs = packageInfoLogsInput.textContent;
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);
@ -164,12 +163,13 @@
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 = _ => {
$.ajax({ makeRequest(
url: `/api/v1/packages/${packageBase}/patches/${variable.key}`, `/api/v1/packages/${packageBase}/patches/${variable.key}`,
type: "DELETE", {
dataType: "json", method: "DELETE",
success: _ => variableInput.remove(), },
}); _ => variableInput.remove(),
);
}; };
// bring them together // bring them together
@ -178,52 +178,57 @@
variableInput.appendChild(variableValueInput); variableInput.appendChild(variableValueInput);
variableInput.appendChild(variableButtonRemove); variableInput.appendChild(variableButtonRemove);
packageInfoVariablesDiv.append(variableInput); packageInfoVariablesDiv.appendChild(variableInput);
} }
function loadChanges(packageBase, onFailure) { function loadChanges(packageBase, onFailure) {
$.ajax({ makeRequest(
url: `/api/v1/packages/${packageBase}/changes`, `/api/v1/packages/${packageBase}/changes`,
data: { {
architecture: repository.architecture, query: {
repository: repository.repository, architecture: repository.architecture,
repository: repository.repository,
},
convert: response => response.json(),
}, },
type: "GET", data => {
dataType: "json", const changes = data.changes;
success: response => { packageInfoChangesInput.textContent = changes ?? "";
const changes = response.changes; highlight(packageInfoChangesInput);
packageInfoChangesInput.text(changes || "");
packageInfoChangesInput.map((_, el) => highlight(el));
}, },
error: onFailure, onFailure,
}); );
} }
function loadEvents(packageBase, onFailure) { function loadEvents(packageBase, onFailure) {
packageInfoEventsTable.bootstrapTable("showLoading"); packageInfoEventsTable.bootstrapTable("showLoading");
clearChart(); clearChart();
$.ajax({ makeRequest(
url: `/api/v1/events`, "/api/v1/events",
data: { {
architecture: repository.architecture, query: {
repository: repository.repository, architecture: repository.architecture,
object_id: packageBase, repository: repository.repository,
limit: 30, object_id: packageBase,
limit: 30,
},
convert: response => response.json(),
}, },
type: "GET", data => {
dataType: "json", const events = data.map(event => {
success: response => {
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: [{
@ -235,32 +240,31 @@
}; };
packageInfoEventsUpdateChart.update(); packageInfoEventsUpdateChart.update();
} }
packageInfoEventsUpdateChartCanvas.hidden = !chart.length;
packageInfoEventsTable.bootstrapTable("load", events);
packageInfoEventsTable.bootstrapTable("hideLoading");
}, },
error: onFailure, onFailure,
}); );
} }
function loadLogs(packageBase, onFailure) { function loadLogs(packageBase, onFailure) {
$.ajax({ makeRequest(
url: `/api/v2/packages/${packageBase}/logs`, `/api/v2/packages/${packageBase}/logs`,
data: { {
architecture: repository.architecture, query: {
repository: repository.repository, architecture: repository.architecture,
repository: repository.repository,
},
convert: response => response.json(),
}, },
type: "GET", data => {
dataType: "json", const logs = data.map(log_record => {
success: response => {
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.text(logs.join("\n")); packageInfoLogsInput.textContent = logs.join("\n");
packageInfoLogsInput.map((_, el) => highlight(el)); highlight(packageInfoLogsInput);
}, },
error: onFailure, onFailure,
}); );
} }
function loadPackage(packageBase, onFailure) { function loadPackage(packageBase, onFailure) {
@ -272,16 +276,17 @@
return ["bg-secondary", "text-white"]; return ["bg-secondary", "text-white"];
}; };
$.ajax({ makeRequest(
url: `/api/v1/packages/${packageBase}`, `/api/v1/packages/${packageBase}`,
data: { {
architecture: repository.architecture, query: {
repository: repository.repository, architecture: repository.architecture,
repository: repository.repository,
},
convert: response => response.json(),
}, },
type: "GET", data => {
dataType: "json", const description = data.find(Boolean);
success: response => {
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(
@ -291,72 +296,71 @@
) )
).sort(); ).sort();
packageInfo.text(`${description.package.base} ${description.status.status} at ${new Date(1000 * description.status.timestamp).toISOStringShort()}`); packageInfo.textContent = `${description.package.base} ${description.status.status} at ${new Date(1000 * description.status.timestamp).toISOStringShort()}`;
packageInfoModalHeader.removeClass(); packageInfoModalHeader.classList.remove(...packageInfoModalHeader.classList);
packageInfoModalHeader.addClass("modal-header"); packageInfoModalHeader.classList.add("modal-header");
headerClass(description.status.status).forEach(clz => packageInfoModalHeader.addClass(clz)); headerClass(description.status.status).forEach(clz => packageInfoModalHeader.classList.add(clz));
packageInfoAurUrl.html(aurUrl ? safeLink(aurUrl, aurUrl, "AUR link").outerHTML : ""); packageInfoAurUrl.innerHTML = aurUrl ? safeLink(aurUrl, aurUrl, "AUR link").outerHTML : "";
packageInfoDepends.html(listToTable( packageInfoDepends.innerHTML = 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.html(listToTable(extractListProperties(description.package, "groups"))); packageInfoGroups.innerHTML = listToTable(extractListProperties(description.package, "groups"));
packageInfoLicenses.html(listToTable(extractListProperties(description.package, "licenses"))); packageInfoLicenses.innerHTML = listToTable(extractListProperties(description.package, "licenses"));
packageInfoPackager.text(description.package.packager); packageInfoPackager.textContent = description.package.packager;
packageInfoPackages.html(listToTable(packages)); packageInfoPackages.innerHTML = listToTable(packages);
packageInfoUpstreamUrl.html(upstreamUrls.map(url => safeLink(url, url, "upstream link").outerHTML).join("<br>")); packageInfoUpstreamUrl.innerHTML = upstreamUrls.map(url => safeLink(url, url, "upstream link").outerHTML).join("<br>");
packageInfoVersion.text(description.package.version); packageInfoVersion.textContent = description.package.version;
hideInfoControls(false);
}, },
error: (jqXHR, _, errorThrown) => { onFailure,
hideInfoControls(true); );
onFailure(jqXHR, null, errorThrown);
},
});
} }
function loadPatches(packageBase, onFailure) { function loadPatches(packageBase, onFailure) {
$.ajax({ makeRequest(
url: `/api/v1/packages/${packageBase}/patches`, `/api/v1/packages/${packageBase}/patches`,
type: "GET", {
dataType: "json", convert: response => response.json(),
success: response => {
packageInfoVariablesDiv.empty();
response.map(patch => insertVariable(packageBase, patch));
packageInfoVariablesBlock.attr("hidden", response.length === 0);
}, },
error: onFailure, data => {
}); packageInfoVariablesDiv.replaceChildren();
data.map(patch => insertVariable(packageBase, patch));
packageInfoVariablesBlock.hidden = !data.length;
},
onFailure,
);
} }
function packageInfoRemove() { function packageInfoRemove() {
const packageBase = packageInfoModal.data("package"); const packageBase = packageInfoModal.package;
if (packageBase) return packagesRemove([packageBase]); packagesRemove([packageBase]);
} }
function packageInfoUpdate() { function packageInfoUpdate() {
const packageBase = packageInfoModal.data("package"); const packageBase = packageInfoModal.package;
if (packageBase) return packagesAdd(packageBase, [], repository); packagesAdd(packageBase, [], repository);
} }
function showPackageInfo(packageBase) { function showPackageInfo(packageBase) {
const isPackageBaseSet = packageBase !== undefined; const isPackageBaseSet = packageBase !== undefined;
if (isPackageBaseSet) if (isPackageBaseSet) {
packageInfoModal.data("package", packageBase); // set package base as currently used // set package base as currently used
else packageInfoModal.package = packageBase;
packageBase = packageInfoModal.data("package"); // read package base from the current window attribute } else {
// read package base from the current window attribute
packageBase = packageInfoModal.package;
}
const onFailure = (jqXHR, _, errorThrown) => { const onFailure = error => {
if (isPackageBaseSet) { if (isPackageBaseSet) {
const message = error => `Could not load package ${packageBase} info: ${error}`; const message = details => `Could not load package ${packageBase} info: ${details}`;
showFailure("Load failure", message, jqXHR, errorThrown); showFailure("Load failure", message, error);
} }
}; };
@ -366,10 +370,12 @@
loadChanges(packageBase, onFailure); loadChanges(packageBase, onFailure);
loadEvents(packageBase, onFailure); loadEvents(packageBase, onFailure);
if (isPackageBaseSet) packageInfoModal.modal("show"); if (isPackageBaseSet) {
bootstrap.Modal.getOrCreateInstance(packageInfoModal).show();
}
} }
$(_ => { ready(_ => {
packageInfoEventsUpdateChart = new Chart(packageInfoEventsUpdateChartCanvas, { packageInfoEventsUpdateChart = new Chart(packageInfoEventsUpdateChartCanvas, {
type: "line", type: "line",
data: {}, data: {},
@ -378,27 +384,23 @@
}, },
}); });
packageInfoModal.on("hidden.bs.modal", _ => { packageInfoModal.addEventListener("hidden.bs.modal", _ => {
packageInfoAurUrl.empty(); packageInfoAurUrl.textContent = "";
packageInfoDepends.empty(); packageInfoDepends.textContent = "";
packageInfoGroups.empty(); packageInfoGroups.textContent = "";
packageInfoLicenses.empty(); packageInfoLicenses.textContent = "";
packageInfoPackager.empty(); packageInfoPackager.textContent = "";
packageInfoPackages.empty(); packageInfoPackages.textContent = "";
packageInfoUpstreamUrl.empty(); packageInfoUpstreamUrl.textContent = "";
packageInfoVersion.empty(); packageInfoVersion.textContent = "";
packageInfoVariablesBlock.attr("hidden", true); packageInfoVariablesBlock.hidden = true;
packageInfoVariablesDiv.empty(); packageInfoVariablesDiv.replaceChildren();
packageInfoLogsInput.empty(); packageInfoLogsInput.textContent = "";
packageInfoChangesInput.empty(); packageInfoChangesInput.textContent = "";
packageInfoEventsTable.bootstrapTable("load", []); packageInfoEventsTable.bootstrapTable("load", []);
clearChart(); clearChart();
packageInfoModal.trigger("reset");
hideInfoControls(true);
}); });
}); });
</script> </script>

View File

@ -33,28 +33,31 @@
</div> </div>
<script> <script>
const packageRebuildModal = $("#package-rebuild-modal"); const packageRebuildModal = document.getElementById("package-rebuild-modal");
const packageRebuildForm = $("#package-rebuild-form"); const packageRebuildForm = document.getElementById("package-rebuild-form");
const packageRebuildDependencyInput = $("#package-rebuild-dependency-input"); const packageRebuildDependencyInput = document.getElementById("package-rebuild-dependency-input");
const packageRebuildRepositoryInput = $("#package-rebuild-repository-input"); const packageRebuildRepositoryInput = document.getElementById("package-rebuild-repository-input");
function packagesRebuild() { function packagesRebuild() {
const packages = packageRebuildDependencyInput.val(); const packages = packageRebuildDependencyInput.value;
const repository = getRepositorySelector(packageRebuildRepositoryInput); const repository = getRepositorySelector(packageRebuildRepositoryInput);
if (packages) { if (packages) {
packageRebuildModal.modal("hide"); bootstrap.Modal.getOrCreateInstance(packageRebuildModal).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.on("shown.bs.modal", _ => { packageRebuildModal.addEventListener("shown.bs.modal", _ => {
$(`#package-rebuild-repository-input option[value="${repository.architecture}-${repository.repository}"]`).prop("selected", true); const option = packageRebuildRepositoryInput.querySelector(`option[value="${repository.architecture}-${repository.repository}"]`);
option.selected = "selected";
}); });
packageRebuildModal.on("hidden.bs.modal", _ => { packageRebuildForm.trigger("reset"); }); packageRebuildModal.addEventListener("hidden.bs.modal", _ => {
packageRebuildForm.reset();
});
}); });
</script> </script>

View File

@ -1,39 +1,34 @@
<script> <script>
const keyImportButton = $("#key-import-button"); const packageRemoveButton = document.getElementById("package-remove-button");
const packageAddButton = $("#package-add-button"); const packageUpdateButton = document.getElementById("package-update-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;
const table = $("#packages"); // so far bootstrap-table only operates with jquery elements
const table = $(document.getElementById("packages"));
const statusBadge = $("#badge-status"); const statusBadge = document.getElementById("badge-status");
const versionBadge = $("#badge-version"); const versionBadge = document.getElementById("badge-version");
function doPackageAction(uri, packages, repository, successText, failureText, data) { function doPackageAction(uri, packages, repository, successText, failureText, data) {
const queryParams = $.param({ makeRequest(
architecture: repository.architecture, uri,
repository: repository.repository, {
}); // it will never be empty btw method: "POST",
query: {
$.ajax({ architecture: repository.architecture,
url: `${uri}?${queryParams}`, repository: repository.repository,
data: JSON.stringify(Object.assign({}, {packages: packages}, data || {})), },
type: "POST", json: Object.assign({}, {packages: packages}, data || {}),
contentType: "application/json", },
success: _ => { _ => {
const message = successText(packages.join(", ")); const message = successText(packages.join(", "));
showSuccess("Success", message); showSuccess("Success", message);
}, },
error: (jqXHR, _, errorThrown) => { error => {
showFailure("Action failed", failureText, jqXHR, errorThrown); showFailure("Action failed", failureText, error);
}, },
}); );
} }
function filterListGroups() { function filterListGroups() {
@ -49,10 +44,10 @@
} }
function getRepositorySelector(selector) { function getRepositorySelector(selector) {
const selected = selector.find(":selected"); const selected = selector.options[selector.selectedIndex];
return { return {
architecture: selected.data("architecture"), architecture: selected.getAttribute("data-architecture"),
repository: selected.data("repository"), repository: selected.getAttribute("data-repository"),
}; };
} }
@ -60,14 +55,6 @@
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`;
@ -97,16 +84,17 @@
return "btn-outline-secondary"; return "btn-outline-secondary";
}; };
$.ajax({ makeRequest(
url: "/api/v1/packages", "/api/v1/packages",
data: { {
architecture: repository.architecture, query: {
repository: repository.repository, architecture: repository.architecture,
repository: repository.repository,
},
convert: response => response.json(),
}, },
type: "GET", data => {
dataType: "json", const payload = data.map(description => {
success: response => {
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 {
@ -125,10 +113,9 @@
table.bootstrapTable("load", payload); table.bootstrapTable("load", payload);
table.bootstrapTable("uncheckAll"); table.bootstrapTable("uncheckAll");
table.bootstrapTable("hideLoading"); table.bootstrapTable("hideLoading");
hideControls(false);
}, },
error: (jqXHR, _, errorThrown) => { error => {
if ((jqXHR.status === 401) || (jqXHR.status === 403)) { if ((error.status === 401) || (error.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();
@ -136,39 +123,39 @@
table.bootstrapTable("hideLoading"); table.bootstrapTable("hideLoading");
} else { } else {
// other errors // other errors
const message = error => `Could not load list of packages: ${error}`; const message = details => `Could not load list of packages: ${details}`;
showFailure("Load failure", message, jqXHR, errorThrown); showFailure("Load failure", message, error);
} }
hideControls(true);
}, },
}); );
$.ajax({ makeRequest(
url: "/api/v1/status", "/api/v1/status",
data: { {
architecture: repository.architecture, query: {
repository: repository.repository, architecture: repository.architecture,
repository: repository.repository,
},
convert: response => response.json(),
}, },
type: "GET", data => {
dataType: "json", versionBadge.innerHTML = `<i class="bi bi-github"></i> ahriman ${safe(data.version)}`;
success: response => {
versionBadge.html(`<i class="bi bi-github"></i> ahriman ${safe(response.version)}`);
statusBadge statusBadge.classList.remove(...statusBadge.classList);
.popover("dispose") statusBadge.classList.add("btn");
.attr("data-bs-content", `${response.status.status} at ${new Date(1000 * response.status.timestamp).toISOStringShort()}`) statusBadge.classList.add(badgeClass(data.status.status));
.popover();
statusBadge.removeClass(); const popover = bootstrap.Popover.getOrCreateInstance(statusBadge);
statusBadge.addClass("btn"); popover.dispose();
statusBadge.addClass(badgeClass(response.status.status)); statusBadge.dataset.bsContent = `${data.status.status} at ${new Date(1000 * data.status.timestamp).toISOStringShort()}`;
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 }}";
const element = $(`#${fragment}-link`); document.getElementById(`${fragment}-link`).click();
element.click();
} }
function statusFormat(value) { function statusFormat(value) {
@ -182,20 +169,25 @@
return {classes: cellClass(value)}; return {classes: cellClass(value)};
} }
$(_ => { ready(_ => {
$("#repositories a").on("click", event => { document.querySelectorAll("#repositories a").forEach(element => {
const element = event.target; element.onclick = _ => {
repository = { repository = {
architecture: element.dataset.architecture, architecture: element.dataset.architecture,
repository: element.dataset.repository, repository: element.dataset.repository,
};
if (packageUpdateButton) {
packageUpdateButton.innerHTML = `<i class="bi bi-play"></i> update<span class="d-none d-sm-inline"> ${safe(repository.repository)} (${safe(repository.architecture)})</span>`;
}
bootstrap.Tab.getOrCreateInstance(document.getElementById(element.id)).show();
reload();
}; };
packageUpdateButton.html(`<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");
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", _ => {
packageRemoveButton.prop("disabled", !table.bootstrapTable("getSelections").length); if (packageRemoveButton) {
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) {
@ -204,26 +196,38 @@
} else showPackageInfo(data.id); } else showPackageInfo(data.id);
}); });
table.on("created-controls.bs.table", _ => { table.on("created-controls.bs.table", _ => {
const pickerInput = $(".bootstrap-table-filter-control-timestamp"); new easepick.create({
pickerInput.daterangepicker({ element: document.querySelector(".bootstrap-table-filter-control-timestamp"),
autoUpdateInput: false, css: [
"https://cdn.jsdelivr.net/npm/@easepick/bundle@1.2.1/dist/index.css",
],
grid: 2,
calendars: 2,
autoApply: false,
locale: { locale: {
cancelLabel: "Clear", cancel: "Clear",
},
RangePlugin: {
tooltip: false,
},
plugins: [
"RangePlugin",
],
setup: picker => {
picker.on("select", _ => { table.bootstrapTable("triggerSearch"); });
// replace "Cancel" behaviour to "Clear"
picker.onClickCancelButton = element => {
if (picker.isCancelButton(element)) {
picker.clear();
picker.hide();
table.bootstrapTable("triggerSearch");
}
};
}, },
});
pickerInput.on("apply.daterangepicker", (event, picker) => {
pickerInput.val(`${picker.startDate.format("YYYY-MM-DD")} - ${picker.endDate.format("YYYY-MM-DD")}`);
table.bootstrapTable("triggerSearch");
});
pickerInput.on("cancel.daterangepicker", _ => {
pickerInput.val("");
table.bootstrapTable("triggerSearch");
}); });
}); });
statusBadge.popover(); bootstrap.Popover.getOrCreateInstance(statusBadge);
selectRepository(); selectRepository();
}); });
</script> </script>

View File

@ -105,13 +105,13 @@ SigLevel = Database{% if has_repo_signed %}Required{% else %}Never{% endif %} Pa
</div> </div>
<script> <script>
const table = $("#packages"); const table = $(document.getElementById("packages"));
const pacmanConf = $("#pacman-conf"); const pacmanConf = document.getElementById("pacman-conf");
const pacmanConfCopyButton = $("#copy-btn"); const pacmanConfCopyButton = document.getElementById("copy-btn");
async function copyPacmanConf() { async function copyPacmanConf() {
const conf = pacmanConf.text(); const conf = pacmanConf.textContent;
await copyToClipboard(conf, pacmanConfCopyButton); await copyToClipboard(conf, pacmanConfCopyButton);
} }
@ -127,24 +127,36 @@ 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", _ => {
const pickerInput = $(".bootstrap-table-filter-control-timestamp"); new easepick.create({
pickerInput.daterangepicker({ element: document.querySelector(".bootstrap-table-filter-control-timestamp"),
autoUpdateInput: false, css: [
"https://cdn.jsdelivr.net/npm/@easepick/bundle@1.2.1/dist/index.css",
],
grid: 2,
calendars: 2,
autoApply: false,
locale: { locale: {
cancelLabel: "Clear", cancel: "Clear",
},
RangePlugin: {
tooltip: false,
},
plugins: [
"RangePlugin",
],
setup: picker => {
picker.on("select", _ => { table.bootstrapTable("triggerSearch"); });
// replace "Cancel" behaviour to "Clear"
picker.onClickCancelButton = element => {
if (picker.isCancelButton(element)) {
picker.clear();
picker.hide();
table.bootstrapTable("triggerSearch");
}
};
}, },
});
pickerInput.on("apply.daterangepicker", (event, picker) => {
pickerInput.val(`${picker.startDate.format("YYYY-MM-DD")} - ${picker.endDate.format("YYYY-MM-DD")}`);
table.bootstrapTable("triggerSearch");
});
pickerInput.on("cancel.daterangepicker", _ => {
pickerInput.val("");
table.bootstrapTable("triggerSearch");
}); });
}); });
}); });

View File

@ -1,41 +1,30 @@
<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/jquery.md5@1.0.2/index.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/moment@2.29.4/moment.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/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.2/dist/js/bootstrap.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-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/bootstrap-table.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/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/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/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/filter-control/bootstrap-table-filter-control.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/gh/highlightjs/cdn-release@11.9.0/build/highlight.min.js" crossorigin="anonymous" type="application/javascript"></script> <script src="https://cdn.jsdelivr.net/npm/@easepick/bundle@1.2.1/dist/index.umd.min.js" crossorigin="anonymous" type="application/javascript"></script>
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.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) { await navigator.clipboard.writeText(text);
const input = document.createElement("textarea"); button.innerHTML = "<i class=\"bi bi-clipboard-check\"></i> copied";
input.innerHTML = text; setTimeout(_ => {
document.body.appendChild(input); button.innerHTML = "<i class=\"bi bi-clipboard\"></i> copy";
input.select();
document.execCommand("copy");
document.body.removeChild(input);
} else {
await navigator.clipboard.writeText(text);
}
button.html("<i class=\"bi bi-clipboard-check\"></i> copied");
setTimeout(()=> {
button.html("<i class=\"bi bi-clipboard\"></i> copy");
}, 2000); }, 2000);
} }
@ -76,6 +65,47 @@
.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, "&amp;") .replace(/&/g, "&amp;")
@ -89,7 +119,9 @@
const element = document.createElement("a"); const element = document.createElement("a");
element.href = url; element.href = url;
element.innerText = text; element.innerText = text;
if (title) element.title = title; if (title) {
element.title = title;
}
return element; return element;
} }

View File

@ -1,17 +1,15 @@
<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@5.3.3/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.22.1/dist/bootstrap-table.min.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/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.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/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/bootswatch@5.3.2/dist/cosmo/bootstrap.min.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/daterangepicker@3.1.0/daterangepicker.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/gh/highlightjs/cdn-release@11.9.0/build/styles/github.min.css" crossorigin="anonymous" type="text/css">
<style> <style>
.pre-scrollable { .pre-scrollable {