Made advancements in instances page - web UI

This commit is contained in:
Théophile Diot 2024-08-30 10:31:09 +02:00
parent 8b40406612
commit 1b76c2f0a9
No known key found for this signature in database
GPG key ID: FA995104A0BA376A
14 changed files with 277 additions and 88 deletions

View file

@ -18,17 +18,17 @@ def instances_page():
return render_template("instances.html", instances=BW_INSTANCES_UTILS.get_instances())
@instances.route("/instances/new", methods=["PUT"])
@instances.route("/instances/new", methods=["POST"])
@login_required
def instances_new():
verify_data_in_form(
data={"instance_hostname": None},
data={"hostname": None},
err_message="Missing instance hostname parameter on /instances/new.",
redirect_url="instances",
next=True,
)
verify_data_in_form(
data={"instance_name": None},
data={"name": None},
err_message="Missing instance name parameter on /instances/new.",
redirect_url="instances",
next=True,
@ -37,10 +37,10 @@ def instances_new():
db_config = BW_CONFIG.get_config(global_only=True, methods=False, filtered_settings=("API_HTTP_PORT", "API_SERVER_NAME"))
instance = {
"hostname": request.form["instance_hostname"].replace("http://", "").replace("https://", ""),
"name": request.form["instance_name"],
"port": db_config["API_HTTP_PORT"],
"server_name": db_config["API_SERVER_NAME"],
"hostname": request.form["hostname"].replace("http://", "").replace("https://", "").split(":")[0],
"name": request.form["name"],
"port": db_config.get("API_HTTP_PORT", "5000"),
"server_name": db_config.get("API_SERVER_NAME", "bwapi"),
"method": "ui",
}
@ -87,9 +87,8 @@ def instances_action(instance_hostname: str, action: Literal["ping", "reload", "
"loading",
next=url_for("instances.instances_page"),
message=(
f"{action.title()}ing"
if action not in ("delete", "stop")
else ("Deleting" if action == "delete" else "Stopping") + f" instance {instance_hostname}"
(f"{action.title()}ing" if action not in ("delete", "stop") else ("Deleting" if action == "delete" else "Stopping"))
+ f" instance {instance_hostname}"
),
)
)

View file

@ -0,0 +1,3 @@
td.highlight {
background-color: rgba(var(--bs-primary-rgb), 0.1) !important;
}

View file

@ -10,7 +10,7 @@
margin-top: 2rem;
}
.layout-main-info h2 {
.layout-main-info h3 {
color: #fff;
}

View file

@ -1,23 +1,108 @@
$(document).ready(function () {
new DataTable("#instances", {
$.fn.dataTable.ext.buttons.create_instance = {
text: "Create new instance",
className: "btn btn-sm btn-outline-primary",
action: function (e, dt, node, config) {
var modal = new bootstrap.Modal($("#modal-create-instance"));
modal.show();
},
};
const instances_table = new DataTable("#instances", {
columnDefs: [{ orderable: false, targets: 7 }],
order: [[6, "desc"]],
autoFill: false,
colReorder: true,
responsive: true,
layout: {
topStart: {
pageLength: {
menu: [10, 25, 50, 100, { label: "All", value: -1 }],
},
buttons: [
{
extend: "colvis",
columns: "th:not(:first-child):not(:last-child)",
text: "Columns",
className: "btn btn-sm btn-outline-primary",
columnText: function (dt, idx, title) {
return idx + 1 + ". " + title;
},
},
{
extend: "colvisRestore",
text: "Reset",
className: "btn btn-sm btn-outline-primary",
},
{
extend: "collection",
text: "Export",
className: "btn btn-sm btn-outline-primary",
buttons: [
{
extend: "copy",
text: "Copy current page",
exportOptions: {
modifier: {
page: "current",
},
},
},
{
extend: "csv",
bom: true,
filename: "bw_instances",
exportOptions: {
modifier: {
search: "none",
},
},
},
{
extend: "excel",
filename: "bw_instances",
exportOptions: {
modifier: {
search: "none",
},
},
},
],
},
{
extend: "create_instance",
},
],
},
},
});
$("#instance-form").on("submit", function (event) {
event.preventDefault(); // Prevent the default form submission
instances_table.on("mouseenter", "td", function () {
const colIdx = instances_table.cell(this).index().column;
const form = $(this);
const clickedButton = form.find('button[type="submit"]:focus'); // Find the button that triggered the submit
const action = clickedButton.data("action"); // Get the action from the button
instances_table
.cells()
.nodes()
.each((el) => el.classList.remove("highlight"));
instances_table
.column(colIdx)
.nodes()
.each((el) => el.classList.add("highlight"));
});
$(document).on("click", "button[data-action]", function () {
const form = $(this).closest("form");
const action = $(this).data("action"); // Get the action from the button
const actionSplit = form.attr("action").split("/");
const instanceHostname = actionSplit[actionSplit.length - 1];
if (
action === "delete" &&
$(`#method-${instanceHostname}`).val() !== "ui"
$(`#method-${instanceHostname}`).text() !== "ui"
) {
return;
} else if ($(`#status-${instanceHostname}`).val() !== "Up") {
} else if ($(`#status-${instanceHostname}`).text() !== "Up") {
return;
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -31,12 +31,19 @@
<link rel="stylesheet"
href="libs/perfect-scrollbar/perfect-scrollbar.css"
nonce="{{ style_nonce }}" />
<link rel="stylesheet"
href="libs/apexcharts/apexcharts.css"
nonce="{{ style_nonce }}" />
<link rel="stylesheet"
href="libs/datatables/datatables.min.css"
nonce="{{ style_nonce }}" />
{% if current_endpoint == "home" %}
<link rel="stylesheet"
href="libs/apexcharts/apexcharts.css"
nonce="{{ style_nonce }}" />
{% endif %}
{% if current_endpoint == "instances" %}
<link rel="stylesheet"
href="libs/datatables/datatables.min.css"
nonce="{{ style_nonce }}" />
<link rel="stylesheet"
href="css/pages/instances.css"
nonce="{{ style_nonce }}" />
{% endif %}
<!-- Page CSS -->
<!-- Page -->
{% if current_endpoint == "login" or current_endpoint == "totp" %}
@ -68,19 +75,21 @@
nonce="{{ script_nonce }}"></script>
<script src="libs/perfect-scrollbar/perfect-scrollbar.min.js"
nonce="{{ script_nonce }}"></script>
<script src="libs/hammer/hammer.min.js" nonce="{{ script_nonce }}"></script>
<script src="libs/masonry/masonry.pkgd.min.js" nonce="{{ script_nonce }}"></script>
<script src="libs/purify/purify.min.js" nonce="{{ script_nonce }}"></script>
<script src="libs/datatables/datatables.min.js" nonce="{{ script_nonce }}"></script>
{% if current_endpoint == "instances" %}
<script src="libs/datatables/datatables.min.js" nonce="{{ script_nonce }}"></script>
{% endif %}
<!-- Core JS -->
<script src="js/menu.js" nonce="{{ script_nonce }}"></script>
<script src="libs/apexcharts/apexcharts.min.js" nonce="{{ script_nonce }}"></script>
{% if current_endpoint == "home" %}
<script src="libs/apexcharts/apexcharts.min.js" nonce="{{ script_nonce }}"></script>
<script src="js/dashboards-analytics.js" nonce="{{ script_nonce }}"></script>
{% endif %}
<!-- Main JS -->
<script src="js/main.js" nonce="{{ script_nonce }}"></script>
{% if current_endpoint != "login" and current_endpoint != "totp" %}
<script async defer src="js/sidebar.js" nonce="{{ script_nonce }}"></script>
{% endif %}
<script src="js/dashboards-analytics.js" nonce="{{ script_nonce }}"></script>
<!-- Page JS -->
{% if current_endpoint == "profile" %}
<script src="js/pages/profile.js" nonce="{{ script_nonce }}"></script>

View file

@ -11,7 +11,7 @@
rel="noopener"
class="footer-link">Bunkerity</a>
</div>
<div class="d-none d-lg-inline-block">
<div class="d-none d-md-inline-block">
<a href="https://docs.bunkerweb.io/latest/?utm_campaign=self&utm_source=ui"
target="_blank"
rel="noopener"

View file

@ -1,7 +1,7 @@
{% extends "dashboard.html" %}
{% block content %}
<!-- Content -->
<div class="card table-responsive text-nowrap p-4 mh-100">
<div class="card table-responsive text-nowrap p-4 h-70">
<table id="instances" class="table w-100">
<thead>
<tr>
@ -34,13 +34,13 @@
{% endif %}
</td>
<td>{{ instance.type }}</td>
<td>{{ instance.creation_date.astimezone().strftime("%Y-%m-%d at %H:%M:%S %Z") }}</td>
<td>{{ instance.last_seen.astimezone().strftime("%Y-%m-%d at %H:%M:%S %Z") }}</td>
<td>{{ instance.creation_date.astimezone().strftime("%Y-%m-%d %H:%M:%S %Z") }}</td>
<td>{{ instance.last_seen.astimezone().strftime("%Y-%m-%d %H:%M:%S %Z") }}</td>
<td>
<form id="instance-form" action="{{ url_for('instances') }}/{{ instance.hostname }}" method="POST">
<input type="hidden"
name="csrf_token"
value="{{ csrf_token() }}" />
<form id="instance-form"
action="{{ url_for("instances") }}/{{ instance.hostname }}"
method="POST">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
{% if instance.status != "up" %}
{% set disabled = "disabled" %}
{% else %}
@ -51,18 +51,40 @@
{% else %}
{% set can_delete = "disabled" %}
{% endif %}
<button type="submit"
data-action="ping"
class="btn btn-sm btn-outline-primary {{ disabled }}">Ping</button>
<button type="submit"
data-action="reload"
class="btn btn-sm btn-outline-warning {{ disabled }}">Reload</button>
<button type="submit"
data-action="stop"
class="btn btn-sm btn-outline-dark {{ disabled }}">Stop</button>
<button type="submit"
data-action="delete"
class="btn btn-sm btn-outline-danger {{ can_delete }}">Delete</button>
<div class="btn-group" role="group" aria-label="Action group">
<button type="button"
data-action="ping"
data-bs-toggle="tooltip"
data-bs-placement="top"
data-bs-original-title="Ping"
class="btn btn-sm btn-outline-primary {{ disabled }}">
<i class="tf-icons bx bx-xs bx-bell"></i>
</button>
<button type="button"
data-action="stop"
data-bs-toggle="tooltip"
data-bs-placement="top"
data-bs-original-title="Stop"
class="btn btn-sm btn-outline-primary {{ disabled }}">
<i class="tf-icons bx bx-xs bx-stop"></i>
</button>
<button type="button"
data-action="reload"
data-bs-toggle="tooltip"
data-bs-placement="top"
data-bs-original-title="Reload"
class="btn btn-sm btn-outline-primary {{ disabled }}">
<i class="tf-icons bx bx-xs bx-refresh"></i>
</button>
<button type="button"
data-action="delete"
data-bs-toggle="tooltip"
data-bs-placement="top"
data-bs-original-title="Delete"
class="btn btn-sm btn-outline-danger {{ can_delete }}">
<i class="tf-icons bx bx-xs bx-trash"></i>
</button>
</div>
</form>
</td>
</tr>
@ -70,5 +92,52 @@
</tbody>
</table>
</div>
<div class="modal fade"
id="modal-create-instance"
data-bs-backdrop="static"
tabindex="-1"
aria-hidden="true"
role="dialog">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Create new instance</h5>
<button type="button"
class="btn-close"
data-bs-dismiss="modal"
aria-label="Close"></button>
</div>
<form action="{{ url_for("instances") }}/new" method="POST">
<div class="modal-body">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
<div class="mb-3">
<label for="hostname" class="form-label">Hostname</label>
<input type="text"
class="form-control"
id="hostname"
name="hostname"
placeholder="http://bunkerweb"
maxlength="256"
required />
</div>
<div class="mb-3">
<label for="name" class="form-label">Name</label>
<input type="text"
class="form-control"
id="name"
name="name"
placeholder="My Bunker"
maxlength="256"
required />
</div>
</div>
<div class="modal-footer justify-content-center">
<button type="submit" class="btn btn-outline-primary me-2">Create Instance</button>
<button type="reset" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</form>
</div>
</div>
</div>
<!-- / Content -->
{% endblock %}

View file

@ -10,7 +10,7 @@
</div>
{% if message %}
<div class="layout-main-info">
<h2>{{ message }}</h2>
<h3>{{ message }}</h3>
</div>
{% endif %}
</div>

View file

@ -477,7 +477,7 @@
tabindex="-1"
aria-hidden="true"
role="dialog">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-dialog modal-lg modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Recovery Codes</h5>

View file

@ -131,14 +131,15 @@ def inject_variables():
if not changes_ongoing and DATA.get("PRO_LOADING"):
DATA["PRO_LOADING"] = False
if not changes_ongoing and metadata["failover"]:
flash(
"The last changes could not be applied because it creates a configuration error on NGINX, please check the logs for more information. The configured fell back to the last working one.",
"error",
)
elif not changes_ongoing and not metadata["failover"] and DATA.get("CONFIG_CHANGED", False):
flash("The last changes have been applied successfully.", "success")
DATA["CONFIG_CHANGED"] = False
if not request.path.startswith("/loading"):
if not changes_ongoing and metadata["failover"]:
flash(
"The last changes could not be applied because it creates a configuration error on NGINX, please check the logs for more information. The configured fell back to the last working one.",
"error",
)
elif not changes_ongoing and not metadata["failover"] and DATA.get("CONFIG_CHANGED", False):
flash("The last changes have been applied successfully.", "success")
DATA["CONFIG_CHANGED"] = False
services = BW_CONFIG.get_config(global_only=True, methods=False, filtered_settings=("SERVER_NAME"))["SERVER_NAME"].split(" ")