mirror of
https://github.com/bunkerity/bunkerweb
synced 2026-05-24 09:28:37 +00:00
Add readonly specifications to ui routes
This commit is contained in:
parent
1263484e52
commit
33eea02c6e
31 changed files with 852 additions and 574 deletions
|
|
@ -479,9 +479,10 @@ class UIDatabase(Database):
|
|||
|
||||
return ""
|
||||
|
||||
def get_ui_user_sessions(self, username: str, current_session_id: Optional[str] = None) -> List[UserSessions]:
|
||||
def get_ui_user_sessions(self, username: str, current_session_id: Optional[str] = None) -> List[dict]:
|
||||
"""Get ui user sessions."""
|
||||
with self._db_session() as session:
|
||||
sessions = []
|
||||
if current_session_id:
|
||||
current_session_query = session.query(UserSessions).filter_by(user_name=username, id=current_session_id)
|
||||
other_sessions_query = (
|
||||
|
|
@ -490,10 +491,22 @@ class UIDatabase(Database):
|
|||
.filter(UserSessions.id != current_session_id)
|
||||
.order_by(UserSessions.creation_date.desc())
|
||||
)
|
||||
combined_query = current_session_query.union_all(other_sessions_query)
|
||||
return combined_query.all()
|
||||
query = current_session_query.union_all(other_sessions_query)
|
||||
else:
|
||||
return session.query(UserSessions).filter_by(user_name=username).order_by(UserSessions.creation_date.desc()).all()
|
||||
query = session.query(UserSessions).filter_by(user_name=username).order_by(UserSessions.creation_date.desc())
|
||||
|
||||
for session_data in query:
|
||||
sessions.append(
|
||||
{
|
||||
"id": session_data.id,
|
||||
"ip": session_data.ip,
|
||||
"user_agent": session_data.user_agent,
|
||||
"creation_date": session_data.creation_date,
|
||||
"last_activity": session_data.last_activity,
|
||||
}
|
||||
)
|
||||
|
||||
return sessions
|
||||
|
||||
def delete_ui_user_old_sessions(self, username: str) -> str:
|
||||
"""Delete ui user old sessions."""
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from time import time
|
|||
from flask import Blueprint, flash as flask_flash, redirect, render_template, request, url_for
|
||||
from flask_login import login_required
|
||||
|
||||
from app.dependencies import BW_INSTANCES_UTILS
|
||||
from app.dependencies import BW_INSTANCES_UTILS, DB
|
||||
from app.utils import flash
|
||||
|
||||
from app.routes.utils import get_redis_client, get_remain, handle_error, verify_data_in_form
|
||||
|
|
@ -19,72 +19,6 @@ bans = Blueprint("bans", __name__)
|
|||
def bans_page():
|
||||
redis_client = get_redis_client()
|
||||
|
||||
def get_load_data():
|
||||
try:
|
||||
data = loads(request.form["data"])
|
||||
assert isinstance(data, list)
|
||||
return data
|
||||
except BaseException:
|
||||
return handle_error("Data must be a list of dict", "bans", False, "exception")
|
||||
|
||||
if request.method == "POST" and request.form["operation"] == "unban":
|
||||
data = get_load_data()
|
||||
|
||||
for unban in data:
|
||||
try:
|
||||
unban = loads(unban.replace('"', '"').replace("'", '"'))
|
||||
except BaseException:
|
||||
flask_flash(f"Invalid unban: {unban}, skipping it ...", "error")
|
||||
continue
|
||||
|
||||
if "ip" not in unban:
|
||||
flask_flash(f"Invalid unban: {unban}, skipping it ...", "error")
|
||||
continue
|
||||
|
||||
if redis_client:
|
||||
if not redis_client.delete(f"bans_ip_{unban['ip']}"):
|
||||
flash(f"Couldn't unban {unban['ip']} on redis", "error")
|
||||
|
||||
resp = BW_INSTANCES_UTILS.unban(unban["ip"])
|
||||
if resp:
|
||||
flash(f"Couldn't unban {unban['ip']} on the following instances: {', '.join(resp)}", "error")
|
||||
else:
|
||||
flash(f"Successfully unbanned {unban['ip']}")
|
||||
|
||||
return redirect(url_for("loading", next=url_for("bans.bans_page"), message="Update bans"))
|
||||
|
||||
if request.method == "POST" and request.form["operation"] == "ban":
|
||||
data = get_load_data()
|
||||
|
||||
for ban in data:
|
||||
if not isinstance(ban, dict) or "ip" not in ban:
|
||||
flask_flash(f"Invalid ban: {ban}, skipping it ...", "error")
|
||||
continue
|
||||
|
||||
reason = ban.get("reason", "ui")
|
||||
ban_end = 86400.0
|
||||
if "ban_end" in ban:
|
||||
try:
|
||||
ban_end = float(ban["ban_end"])
|
||||
except ValueError:
|
||||
continue
|
||||
current_time = datetime.now().astimezone()
|
||||
ban_end = (datetime.fromtimestamp(ban_end, tz=current_time.tzinfo) - current_time).total_seconds()
|
||||
|
||||
if redis_client:
|
||||
ok = redis_client.set(f"bans_ip_{ban['ip']}", dumps({"reason": reason, "date": time()}))
|
||||
if not ok:
|
||||
flash(f"Couldn't ban {ban['ip']} on redis", "error")
|
||||
redis_client.expire(f"bans_ip_{ban['ip']}", int(ban_end))
|
||||
|
||||
resp = BW_INSTANCES_UTILS.ban(ban["ip"], ban_end, reason)
|
||||
if resp:
|
||||
flash(f"Couldn't ban {ban['ip']} on the following instances: {', '.join(resp)}", "error")
|
||||
else:
|
||||
flash(f"Successfully banned {ban['ip']}")
|
||||
|
||||
return redirect(url_for("loading", next=url_for("bans.bans_page"), message="Update bans"))
|
||||
|
||||
bans = []
|
||||
if redis_client:
|
||||
for key in redis_client.scan_iter("bans_ip_*"):
|
||||
|
|
@ -118,6 +52,9 @@ def bans_page():
|
|||
@bans.route("/bans/ban", methods=["POST"])
|
||||
@login_required
|
||||
def bans_ban():
|
||||
if DB.readonly:
|
||||
return handle_error("Database is in read-only mode", "bans")
|
||||
|
||||
verify_data_in_form(
|
||||
data={"bans": None},
|
||||
err_message="Missing bans parameter on /bans/ban.",
|
||||
|
|
@ -165,6 +102,9 @@ def bans_ban():
|
|||
@bans.route("/bans/unban", methods=["POST"])
|
||||
@login_required
|
||||
def bans_unban():
|
||||
if DB.readonly:
|
||||
return handle_error("Database is in read-only mode", "bans")
|
||||
|
||||
verify_data_in_form(
|
||||
data={"ips": None},
|
||||
err_message="Missing bans parameter on /bans/unban.",
|
||||
|
|
|
|||
|
|
@ -44,6 +44,9 @@ def configs_page():
|
|||
@configs.route("/configs/delete", methods=["POST"])
|
||||
@login_required
|
||||
def configs_delete():
|
||||
if DB.readonly:
|
||||
return handle_error("Database is in read-only mode", "configs")
|
||||
|
||||
verify_data_in_form(
|
||||
data={"configs": None},
|
||||
err_message="Missing configs parameter on /configs/delete.",
|
||||
|
|
@ -119,6 +122,9 @@ def configs_delete():
|
|||
@login_required
|
||||
def configs_new():
|
||||
if request.method == "POST":
|
||||
if DB.readonly:
|
||||
return handle_error("Database is in read-only mode", "configs")
|
||||
|
||||
verify_data_in_form(
|
||||
data={"service": None},
|
||||
err_message="Missing service parameter on /configs/new.",
|
||||
|
|
@ -239,6 +245,9 @@ def configs_edit(service: str, config_type: str, name: str):
|
|||
return handle_error(f"Config {config_type}/{name}{' for service ' + service if service else ''} does not exist.", "configs", True)
|
||||
|
||||
if request.method == "POST":
|
||||
if DB.readonly:
|
||||
return handle_error("Database is in read-only mode", "configs")
|
||||
|
||||
if not db_config["template"] and db_config["method"] != "ui":
|
||||
return handle_error(
|
||||
f"Config {config_type}/{name}{' for service ' + service if service else ''} is not a UI custom config and cannot be edited.", "configs", True
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@ def instances_page():
|
|||
@instances.route("/instances/new", methods=["POST"])
|
||||
@login_required
|
||||
def instances_new():
|
||||
if DB.readonly:
|
||||
return handle_error("Database is in read-only mode", "instances")
|
||||
verify_data_in_form(
|
||||
data={"hostname": None},
|
||||
err_message="Missing instance hostname parameter on /instances/new.",
|
||||
|
|
@ -102,6 +104,9 @@ def instances_action(action: Literal["ping", "reload", "stop", "delete"]): # TO
|
|||
|
||||
return jsonify({"succeed": succeed, "failed": failed}), 200
|
||||
elif action == "delete":
|
||||
if DB.readonly:
|
||||
return handle_error("Database is in read-only mode", "instances")
|
||||
|
||||
delete_instances = set()
|
||||
non_ui_instances = set()
|
||||
for instance in DB.get_instances():
|
||||
|
|
|
|||
|
|
@ -19,6 +19,9 @@ def jobs_page():
|
|||
@jobs.route("/jobs/run", methods=["POST"])
|
||||
@login_required
|
||||
def jobs_run():
|
||||
if DB.readonly:
|
||||
return handle_error("Database is in read-only mode", "jobs")
|
||||
|
||||
verify_data_in_form(
|
||||
data={"jobs": None},
|
||||
err_message="Missing jobs parameter on /jobs/run.",
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
from datetime import datetime
|
||||
from typing import Dict, Generator, Tuple, Union
|
||||
from flask import Blueprint, Response, jsonify, redirect, render_template, request, stream_with_context, url_for, session
|
||||
from flask_login import current_user, login_required, logout_user
|
||||
|
|
@ -35,6 +36,8 @@ DEVICES = {
|
|||
def get_last_sessions(page: int, per_page: int) -> Tuple[Generator[Dict[str, Union[str, bool]], None, None], int]:
|
||||
db_sessions = DB.get_ui_user_sessions(current_user.username, session.get("session_id"))
|
||||
total_sessions = len(db_sessions)
|
||||
if "session_id" not in session:
|
||||
total_sessions += 1
|
||||
|
||||
if total_sessions <= per_page:
|
||||
per_page = total_sessions
|
||||
|
|
@ -42,17 +45,26 @@ def get_last_sessions(page: int, per_page: int) -> Tuple[Generator[Dict[str, Uni
|
|||
elif total_sessions <= (page - 1) * per_page:
|
||||
page = total_sessions // per_page
|
||||
|
||||
def session_generator():
|
||||
for db_session in db_sessions[(page - 1) * per_page : page * per_page]: # noqa: E203
|
||||
ua_data = parse(db_session.user_agent)
|
||||
def session_generator(page: int, per_page: int):
|
||||
additional_sessions = []
|
||||
if page == 1 and "session_id" not in session and per_page > 1:
|
||||
per_page -= 1
|
||||
additional_sessions.append(session)
|
||||
|
||||
for db_session in additional_sessions + db_sessions[(page - 1) * per_page : page * per_page]: # noqa: E203
|
||||
ua_data = parse(db_session["user_agent"])
|
||||
last_session = {
|
||||
"current": db_session.id == session.get("session_id"),
|
||||
"current": db_session["id"] == session.get("session_id") if "session_id" in session else "id" not in db_session,
|
||||
"browser": ua_data.get_browser(),
|
||||
"os": ua_data.get_os(),
|
||||
"device": ua_data.get_device(),
|
||||
"ip": db_session.ip,
|
||||
"creation_date": db_session.creation_date.astimezone().strftime("%Y-%m-%d %H:%M:%S %Z"),
|
||||
"last_activity": db_session.last_activity.astimezone().strftime("%Y-%m-%d %H:%M:%S %Z"),
|
||||
"ip": db_session["ip"],
|
||||
"creation_date": db_session["creation_date"].astimezone().strftime("%Y-%m-%d %H:%M:%S %Z"),
|
||||
"last_activity": (
|
||||
db_session["last_activity"].astimezone().strftime("%Y-%m-%d %H:%M:%S %Z")
|
||||
if "id" in db_session
|
||||
else datetime.now().astimezone().strftime("%Y-%m-%d %H:%M:%S %Z")
|
||||
),
|
||||
}
|
||||
|
||||
for browser, icon in BROWSERS.items():
|
||||
|
|
@ -69,7 +81,7 @@ def get_last_sessions(page: int, per_page: int) -> Tuple[Generator[Dict[str, Uni
|
|||
|
||||
yield last_session
|
||||
|
||||
return session_generator(), total_sessions
|
||||
return session_generator(page, per_page), total_sessions
|
||||
|
||||
|
||||
@profile.route("/profile", methods=["GET"])
|
||||
|
|
@ -295,7 +307,9 @@ def wipe_old_sessions():
|
|||
if not current_user.check_password(request.form["password"]):
|
||||
return handle_error("The current password is incorrect.", "profile")
|
||||
|
||||
DATA["REVOKED_SESSIONS"] = [db_session.id for db_session in DB.get_ui_user_sessions(current_user.username) if db_session.id != session.get("session_id")]
|
||||
DATA["REVOKED_SESSIONS"] = [
|
||||
db_session["id"] for db_session in DB.get_ui_user_sessions(current_user.username) if db_session["id"] != session.get("session_id")
|
||||
]
|
||||
|
||||
ret = DB.delete_ui_user_old_sessions(current_user.username)
|
||||
if ret:
|
||||
|
|
|
|||
|
|
@ -21,6 +21,9 @@ def services_page():
|
|||
@services.route("/services/convert", methods=["POST"])
|
||||
@login_required
|
||||
def services_convert():
|
||||
if DB.readonly:
|
||||
return handle_error("Database is in read-only mode", "services")
|
||||
|
||||
verify_data_in_form(
|
||||
data={"services": None},
|
||||
err_message="Missing services parameter on /services/convert.",
|
||||
|
|
@ -101,6 +104,9 @@ def services_convert():
|
|||
@services.route("/services/delete", methods=["POST"])
|
||||
@login_required
|
||||
def services_delete():
|
||||
if DB.readonly:
|
||||
return handle_error("Database is in read-only mode", "services")
|
||||
|
||||
verify_data_in_form(
|
||||
data={"services": None},
|
||||
err_message="Missing services parameter on /services/delete.",
|
||||
|
|
@ -178,6 +184,7 @@ def services_service_page(service: str):
|
|||
if request.method == "POST":
|
||||
if DB.readonly:
|
||||
return handle_error("Database is in read-only mode", "services")
|
||||
|
||||
DATA.load_from_file()
|
||||
|
||||
# Check variables
|
||||
|
|
|
|||
|
|
@ -362,11 +362,14 @@ td.highlight {
|
|||
}
|
||||
|
||||
.btn-text-bw-green,
|
||||
.btn-text-bw-green.disabled,
|
||||
.btn-text-bw-green:hover {
|
||||
color: var(--bs-bw-green) !important;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-text-secondary,
|
||||
.btn-text-secondary.disabled,
|
||||
.btn-text-secondary:hover {
|
||||
color: var(--bs-secondary) !important;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ $(document).ready(function () {
|
|||
var actionLock = false;
|
||||
let addBanNumber = 1;
|
||||
const banNumber = parseInt($("#bans_number").val());
|
||||
const isReadOnly = $("#is-read-only").val().trim() === "True";
|
||||
|
||||
// Utility functions
|
||||
function addDays(date, days) {
|
||||
|
|
@ -253,8 +254,14 @@ $(document).ready(function () {
|
|||
|
||||
$.fn.dataTable.ext.buttons.add_ban = {
|
||||
text: '<span class="tf-icons bx bx-plus-circle bx-18px me-2"></span>Add<span class="d-none d-md-inline"> ban(s)</span>',
|
||||
className: "btn btn-sm btn-outline-bw-green",
|
||||
className: `btn btn-sm btn-outline-bw-green${
|
||||
isReadOnly ? " disabled" : ""
|
||||
}`,
|
||||
action: function (e, dt, node, config) {
|
||||
if (isReadOnly) {
|
||||
alert("This action is not allowed in read-only mode.");
|
||||
return;
|
||||
}
|
||||
const ban_modal = $("#modal-ban-ips");
|
||||
const modal = new bootstrap.Modal(ban_modal);
|
||||
modal.show();
|
||||
|
|
@ -264,6 +271,10 @@ $(document).ready(function () {
|
|||
$.fn.dataTable.ext.buttons.unban_ips = {
|
||||
text: '<span class="tf-icons bx bxs-buoy bx-18px me-2"></span>Unban',
|
||||
action: function (e, dt, node, config) {
|
||||
if (isReadOnly) {
|
||||
alert("This action is not allowed in read-only mode.");
|
||||
return;
|
||||
}
|
||||
if (actionLock) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -346,6 +357,14 @@ $(document).ready(function () {
|
|||
initComplete: function (settings, json) {
|
||||
$("#bans_wrapper .btn-secondary").removeClass("btn-secondary");
|
||||
$("#bans_wrapper th").addClass("text-center");
|
||||
if (isReadOnly)
|
||||
$("#bans_wrapper .dt-buttons")
|
||||
.attr(
|
||||
"data-bs-original-title",
|
||||
"The database is in readonly, therefore you cannot add bans.",
|
||||
)
|
||||
.attr("data-bs-placement", "right")
|
||||
.tooltip();
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -388,6 +407,10 @@ $(document).ready(function () {
|
|||
});
|
||||
|
||||
$(".unban-ip").on("click", function () {
|
||||
if (isReadOnly) {
|
||||
alert("This action is not allowed in read-only mode.");
|
||||
return;
|
||||
}
|
||||
$this = $(this);
|
||||
setupUnbanModal([
|
||||
{ ip: $this.data("ip"), time_remaining: $this.data("time-left") },
|
||||
|
|
@ -440,6 +463,10 @@ $(document).ready(function () {
|
|||
});
|
||||
|
||||
$(document).on("click", ".delete-ban", function () {
|
||||
if (isReadOnly) {
|
||||
alert("This action is not allowed in read-only mode.");
|
||||
return;
|
||||
}
|
||||
const banContainer = $(this).closest("li");
|
||||
if (banContainer.attr("id") === "ban-1") return;
|
||||
banContainer.remove();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
$(document).ready(function () {
|
||||
const isReadOnly = $("#is-read-only").val().trim() === "True";
|
||||
let selectedService = $("#selected-service").val().trim();
|
||||
const originalService = selectedService;
|
||||
let selectedType = $("#selected-type").val().trim();
|
||||
|
|
@ -9,6 +10,9 @@ $(document).ready(function () {
|
|||
const editor = ace.edit(editorElement[0]);
|
||||
editor.setTheme("ace/theme/cloud9_day"); // cloud9_night when dark mode is supported
|
||||
|
||||
if (isReadOnly && window.location.pathname.endsWith("/new"))
|
||||
window.location.href = window.location.href.split("/new")[0];
|
||||
|
||||
const language = editorElement.data("language"); // TODO: Support ModSecurity
|
||||
if (language === "NGINX") {
|
||||
editor.session.setMode("ace/mode/nginx");
|
||||
|
|
@ -138,6 +142,10 @@ $(document).ready(function () {
|
|||
});
|
||||
|
||||
$(".save-config").on("click", function () {
|
||||
if (isReadOnly) {
|
||||
alert("This action is not allowed in read-only mode.");
|
||||
return;
|
||||
}
|
||||
const value = editor.getValue().trim();
|
||||
if (
|
||||
value &&
|
||||
|
|
@ -226,6 +234,8 @@ $(document).ready(function () {
|
|||
changeTypesVisibility();
|
||||
|
||||
$(window).on("beforeunload", function (e) {
|
||||
if (isReadOnly) return;
|
||||
|
||||
const value = editor.getValue().trim();
|
||||
if (
|
||||
value &&
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
$(document).ready(function () {
|
||||
var actionLock = false;
|
||||
const configNumber = parseInt($("#configs_number").val());
|
||||
const isReadOnly = $("#is-read-only").val().trim() === "True";
|
||||
|
||||
const setupDeletionModal = (configs) => {
|
||||
const delete_modal = $("#modal-delete-configs");
|
||||
|
|
@ -201,8 +202,14 @@ $(document).ready(function () {
|
|||
|
||||
$.fn.dataTable.ext.buttons.create_config = {
|
||||
text: '<span class="tf-icons bx bx-plus-circle bx-18px me-2"></span>Create<span class="d-none d-md-inline"> new custom config</span>',
|
||||
className: "btn btn-sm btn-outline-bw-green",
|
||||
className: `btn btn-sm btn-outline-bw-green${
|
||||
isReadOnly ? " disabled" : ""
|
||||
}`,
|
||||
action: function (e, dt, node, config) {
|
||||
if (isReadOnly) {
|
||||
alert("This action is not allowed in read-only mode.");
|
||||
return;
|
||||
}
|
||||
window.location.href = `${window.location.href}/new`;
|
||||
},
|
||||
};
|
||||
|
|
@ -210,6 +217,10 @@ $(document).ready(function () {
|
|||
$.fn.dataTable.ext.buttons.delete_configs = {
|
||||
text: '<span class="tf-icons bx bx-trash bx-18px me-2"></span>Delete',
|
||||
action: function (e, dt, node, config) {
|
||||
if (isReadOnly) {
|
||||
alert("This action is not allowed in read-only mode.");
|
||||
return;
|
||||
}
|
||||
if (actionLock) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -276,6 +287,13 @@ $(document).ready(function () {
|
|||
initComplete: function (settings, json) {
|
||||
$("#configs_wrapper .btn-secondary").removeClass("btn-secondary");
|
||||
$("#configs_wrapper th").addClass("text-center");
|
||||
$("#configs_wrapper .dt-buttons")
|
||||
.attr(
|
||||
"data-bs-original-title",
|
||||
"The database is in readonly, therefore you cannot create new custom configurations.",
|
||||
)
|
||||
.attr("data-bs-placement", "right")
|
||||
.tooltip();
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -318,6 +336,10 @@ $(document).ready(function () {
|
|||
});
|
||||
|
||||
$(".delete-config").on("click", function () {
|
||||
if (isReadOnly) {
|
||||
alert("This action is not allowed in read-only mode.");
|
||||
return;
|
||||
}
|
||||
const config = {
|
||||
name: $(this).data("config-name"),
|
||||
type: $(this).data("config-type"),
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ $(document).ready(function () {
|
|||
var toastNum = 0;
|
||||
var actionLock = false;
|
||||
const instanceNumber = parseInt($("#instances_number").val());
|
||||
const isReadOnly = $("#is-read-only").val().trim() === "True";
|
||||
|
||||
const pingInstances = (instances) => {
|
||||
setTimeout(() => {
|
||||
|
|
@ -61,6 +62,54 @@ $(document).ready(function () {
|
|||
});
|
||||
};
|
||||
|
||||
const setupDeletionModal = (instances) => {
|
||||
$("#selected-instances-input").val(instances.join(","));
|
||||
|
||||
const list = $(
|
||||
`<ul class="list-group list-group-horizontal d-flex w-100">
|
||||
<li class="list-group-item align-items-center text-center bg-secondary text-white" style="flex: 1 0;">
|
||||
<div class="ms-2 me-auto">
|
||||
<div class="fw-bold">Hostname</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="list-group-item align-items-center text-center bg-secondary text-white" style="flex: 1 0;">
|
||||
<div class="fw-bold">Health</div>
|
||||
</li>
|
||||
</ul>`,
|
||||
);
|
||||
$("#selected-instances").append(list);
|
||||
|
||||
const delete_modal = $("#modal-delete-instances");
|
||||
instances.forEach((instance) => {
|
||||
const list = $(
|
||||
`<ul class="list-group list-group-horizontal d-flex w-100"></ul>`,
|
||||
);
|
||||
|
||||
// Create the list item using template literals
|
||||
const listItem =
|
||||
$(`<li class="list-group-item align-items-center" style="flex: 1 0;">
|
||||
<div class="ms-2 me-auto">
|
||||
<div class="fw-bold">${instance}</div>
|
||||
</div>
|
||||
</li>`);
|
||||
list.append(listItem);
|
||||
|
||||
// Clone the status element and append it to the list item
|
||||
const statusClone = $("#status-" + instance).clone();
|
||||
const statusListItem = $(
|
||||
`<li class="list-group-item d-flex align-items-center justify-content-center" style="flex: 1 0;"></li>`,
|
||||
);
|
||||
statusListItem.append(statusClone.removeClass("highlight"));
|
||||
list.append(statusListItem);
|
||||
|
||||
// Append the list item to the list
|
||||
$("#selected-instances").append(list);
|
||||
});
|
||||
|
||||
const modal = new bootstrap.Modal(delete_modal);
|
||||
modal.show();
|
||||
};
|
||||
|
||||
const execForm = (instances, action) => {
|
||||
// Create a form element using jQuery and set its attributes
|
||||
const form = $("<form>", {
|
||||
|
|
@ -214,8 +263,14 @@ $(document).ready(function () {
|
|||
|
||||
$.fn.dataTable.ext.buttons.create_instance = {
|
||||
text: '<span class="tf-icons bx bx-plus-circle bx-18px me-2"></span>Create<span class="d-none d-md-inline"> new instance</span>',
|
||||
className: "btn btn-sm btn-outline-bw-green",
|
||||
className: `btn btn-sm btn-outline-bw-green${
|
||||
isReadOnly ? " disabled" : ""
|
||||
}`,
|
||||
action: function (e, dt, node, config) {
|
||||
if (isReadOnly) {
|
||||
alert("This action is not allowed in read-only mode.");
|
||||
return;
|
||||
}
|
||||
const modal = new bootstrap.Modal($("#modal-create-instance"));
|
||||
modal.show();
|
||||
|
||||
|
|
@ -273,6 +328,10 @@ $(document).ready(function () {
|
|||
$.fn.dataTable.ext.buttons.delete_instances = {
|
||||
text: '<span class="tf-icons bx bx-trash bx-18px me-2"></span>Delete',
|
||||
action: function (e, dt, node, config) {
|
||||
if (isReadOnly) {
|
||||
alert("This action is not allowed in read-only mode.");
|
||||
return;
|
||||
}
|
||||
if (actionLock) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -285,51 +344,7 @@ $(document).ready(function () {
|
|||
return;
|
||||
}
|
||||
|
||||
$("#selected-instances-input").val(instances.join(","));
|
||||
|
||||
const list = $(
|
||||
`<ul class="list-group list-group-horizontal d-flex w-100">
|
||||
<li class="list-group-item align-items-center text-center bg-secondary text-white" style="flex: 1 0;">
|
||||
<div class="ms-2 me-auto">
|
||||
<div class="fw-bold">Hostname</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="list-group-item align-items-center text-center bg-secondary text-white" style="flex: 1 0;">
|
||||
<div class="fw-bold">Health</div>
|
||||
</li>
|
||||
</ul>`,
|
||||
);
|
||||
$("#selected-instances").append(list);
|
||||
|
||||
const delete_modal = $("#modal-delete-instances");
|
||||
instances.forEach((instance) => {
|
||||
const list = $(
|
||||
`<ul class="list-group list-group-horizontal d-flex w-100"></ul>`,
|
||||
);
|
||||
|
||||
// Create the list item using template literals
|
||||
const listItem =
|
||||
$(`<li class="list-group-item align-items-center" style="flex: 1 0;">
|
||||
<div class="ms-2 me-auto">
|
||||
<div class="fw-bold">${instance}</div>
|
||||
</div>
|
||||
</li>`);
|
||||
list.append(listItem);
|
||||
|
||||
// Clone the status element and append it to the list item
|
||||
const statusClone = $("#status-" + instance).clone();
|
||||
const statusListItem = $(
|
||||
`<li class="list-group-item d-flex align-items-center justify-content-center" style="flex: 1 0;"></li>`,
|
||||
);
|
||||
statusListItem.append(statusClone.removeClass("highlight"));
|
||||
list.append(statusListItem);
|
||||
|
||||
// Append the list item to the list
|
||||
$("#selected-instances").append(list);
|
||||
});
|
||||
|
||||
const modal = new bootstrap.Modal(delete_modal);
|
||||
modal.show();
|
||||
setupDeletionModal(instances);
|
||||
|
||||
actionLock = false;
|
||||
},
|
||||
|
|
@ -403,6 +418,14 @@ $(document).ready(function () {
|
|||
initComplete: function (settings, json) {
|
||||
$("#instances_wrapper .btn-secondary").removeClass("btn-secondary");
|
||||
$("#instances_wrapper th").addClass("text-center");
|
||||
if (isReadOnly)
|
||||
$("#instances_wrapper .dt-buttons")
|
||||
.attr(
|
||||
"data-bs-original-title",
|
||||
"The database is in readonly, therefore you cannot create new instances.",
|
||||
)
|
||||
.attr("data-bs-placement", "right")
|
||||
.tooltip();
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -459,4 +482,13 @@ $(document).ready(function () {
|
|||
const action = $(this).hasClass("reload-instance") ? "reload" : "stop";
|
||||
execForm([instance], action);
|
||||
});
|
||||
|
||||
$(".delete-instance").on("click", function () {
|
||||
if (isReadOnly) {
|
||||
alert("This action is not allowed in read-only mode.");
|
||||
return;
|
||||
}
|
||||
const instance = $(this).data("instance");
|
||||
setupDeletionModal([instance]);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
$(document).ready(function () {
|
||||
let actionLock = false;
|
||||
const jobNumber = parseInt($("#job_number").val());
|
||||
const isReadOnly = $("#is-read-only").val().trim() === "True";
|
||||
|
||||
const layout = {
|
||||
topStart: {},
|
||||
|
|
@ -130,6 +131,10 @@ $(document).ready(function () {
|
|||
$.fn.dataTable.ext.buttons.run_jobs = {
|
||||
text: '<span class="tf-icons bx bx-play bx-18px me-2"></span>Run selected jobs',
|
||||
action: function (e, dt, node, config) {
|
||||
if (isReadOnly) {
|
||||
alert("This action is not allowed in read-only mode.");
|
||||
return;
|
||||
}
|
||||
if (actionLock) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -275,6 +280,10 @@ $(document).ready(function () {
|
|||
});
|
||||
|
||||
$(".run-job").on("click", function () {
|
||||
if (isReadOnly) {
|
||||
alert("This action is not allowed in read-only mode.");
|
||||
return;
|
||||
}
|
||||
const job = {
|
||||
name: $(this).data("job"),
|
||||
plugin: $(this).data("plugin"),
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ $(document).ready(function () {
|
|||
var toastNum = 0;
|
||||
var actionLock = false;
|
||||
const serviceNumber = parseInt($("#services_number").val());
|
||||
const isReadOnly = $("#is-read-only").val().trim() === "True";
|
||||
|
||||
const setupModal = (services, modal) => {
|
||||
const list = $(
|
||||
|
|
@ -210,14 +211,24 @@ $(document).ready(function () {
|
|||
|
||||
$.fn.dataTable.ext.buttons.create_service = {
|
||||
text: '<span class="tf-icons bx bx-plus-circle bx-18px me-2"></span>Create<span class="d-none d-md-inline"> new service</span>',
|
||||
className: "btn btn-sm btn-outline-bw-green",
|
||||
className: `btn btn-sm btn-outline-bw-green${
|
||||
isReadOnly ? " disabled" : ""
|
||||
}`,
|
||||
action: function (e, dt, node, config) {
|
||||
if (isReadOnly) {
|
||||
alert("This action is not allowed in read-only mode.");
|
||||
return;
|
||||
}
|
||||
window.location.href = `${window.location.href}/new`;
|
||||
},
|
||||
};
|
||||
|
||||
$.fn.dataTable.ext.buttons.convert_services = {
|
||||
action: function (e, dt, node, config) {
|
||||
if (isReadOnly) {
|
||||
alert("This action is not allowed in read-only mode.");
|
||||
return;
|
||||
}
|
||||
if (actionLock) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -259,6 +270,10 @@ $(document).ready(function () {
|
|||
$.fn.dataTable.ext.buttons.delete_services = {
|
||||
text: '<span class="tf-icons bx bx-trash bx-18px me-2"></span>Delete',
|
||||
action: function (e, dt, node, config) {
|
||||
if (isReadOnly) {
|
||||
alert("This action is not allowed in read-only mode.");
|
||||
return;
|
||||
}
|
||||
if (actionLock) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -341,6 +356,14 @@ $(document).ready(function () {
|
|||
initComplete: function (settings, json) {
|
||||
$("#services_wrapper .btn-secondary").removeClass("btn-secondary");
|
||||
$("#services_wrapper th").addClass("text-center");
|
||||
if (isReadOnly)
|
||||
$("#services_wrapper .dt-buttons")
|
||||
.attr(
|
||||
"data-bs-original-title",
|
||||
"The database is in readonly, therefore you cannot create new services.",
|
||||
)
|
||||
.attr("data-bs-placement", "right")
|
||||
.tooltip();
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -383,12 +406,20 @@ $(document).ready(function () {
|
|||
});
|
||||
|
||||
$(".convert-service").on("click", function () {
|
||||
if (isReadOnly) {
|
||||
alert("This action is not allowed in read-only mode.");
|
||||
return;
|
||||
}
|
||||
const service = $(this).data("service-id");
|
||||
const convertionType = $(this).data("value");
|
||||
setupConversionModal([service], convertionType);
|
||||
});
|
||||
|
||||
$(".delete-service").on("click", function () {
|
||||
if (isReadOnly) {
|
||||
alert("This action is not allowed in read-only mode.");
|
||||
return;
|
||||
}
|
||||
const service = $(this).data("service-id");
|
||||
setupDeletionModal([service]);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,6 +2,10 @@ $(document).ready(() => {
|
|||
var toastNum = 0;
|
||||
let currentPlugin = "general";
|
||||
let currentStep = 1;
|
||||
const isReadOnly = $("#is-read-only").val().trim() === "True";
|
||||
|
||||
if (isReadOnly && window.location.pathname.endsWith("/new"))
|
||||
window.location.href = window.location.href.split("/new")[0];
|
||||
|
||||
const $templateInput = $("#used-template");
|
||||
let usedTemplate = "advanced";
|
||||
|
|
@ -761,6 +765,11 @@ $(document).ready(() => {
|
|||
});
|
||||
|
||||
$(".save-settings").on("click", function () {
|
||||
if (isReadOnly) {
|
||||
alert("This action is not allowed in read-only mode.");
|
||||
return;
|
||||
}
|
||||
|
||||
const form = getFormFromSettings($(this));
|
||||
if (currentMode === "easy") {
|
||||
const currentStepId = `navs-steps-${currentTemplate}-${currentStep}`;
|
||||
|
|
@ -1064,6 +1073,11 @@ $(document).ready(() => {
|
|||
editor.session.setMode("ace/mode/text"); // Default mode if language is unrecognized
|
||||
}
|
||||
|
||||
const method = $(this).data("method");
|
||||
if (method !== "ui") {
|
||||
editor.setReadOnly(true);
|
||||
}
|
||||
|
||||
// Set the editor's initial content
|
||||
editor.setValue(initialContent, -1); // The second parameter moves the cursor to the start
|
||||
|
||||
|
|
@ -1079,6 +1093,8 @@ $(document).ready(() => {
|
|||
});
|
||||
|
||||
$(window).on("beforeunload", function (e) {
|
||||
if (isReadOnly) return;
|
||||
|
||||
const form = getFormFromSettings($(this));
|
||||
if (currentMode !== "easy") {
|
||||
let minSettings = 4;
|
||||
|
|
|
|||
|
|
@ -50,11 +50,11 @@
|
|||
<div class="d-flex justify-content-center">
|
||||
<div data-bs-toggle="tooltip"
|
||||
data-bs-placement="bottom"
|
||||
data-bs-original-title="Unban {{ ban['ip'] }}">
|
||||
data-bs-original-title="{% if is_readonly %}Disabled by readonly{% else %}Unban {{ ban['ip'] }}{% endif %}">
|
||||
<button type="button"
|
||||
data-ip="{{ ban['ip'] }}"
|
||||
data-time-left="{{ ban['remain'] }}"
|
||||
class="btn btn-outline-danger btn-sm me-1 unban-ip">
|
||||
class="btn btn-outline-danger btn-sm me-1 unban-ip{% if is_readonly %} disabled{% endif %}">
|
||||
<i class="bx bxs-buoy bx-xs"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -68,118 +68,120 @@
|
|||
</span>
|
||||
</table>
|
||||
</div>
|
||||
<div class="modal modal-xl fade"
|
||||
id="modal-ban-ips"
|
||||
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">Add Ban(s)</h5>
|
||||
<button id="add-ban"
|
||||
type="button"
|
||||
class="btn btn-text-bw-green rounded-pill p-0 ms-4 me-2">
|
||||
<i class="bx bx-plus-circle"></i> INSERT
|
||||
</button>
|
||||
<button id="clear-bans"
|
||||
type="button"
|
||||
class="btn btn-text-danger rounded-pill p-0 ms-4">
|
||||
<i class="bx bx-trash"></i> CLEAR
|
||||
</button>
|
||||
<button type="button"
|
||||
class="btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"></button>
|
||||
</div>
|
||||
<form id="bans-form" action="{{ url_for("bans") }}/ban" method="POST">
|
||||
<div class="modal-body justify-content-center">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||
<ul id="bans-container" class="list-group rounded-top w-100">
|
||||
<li id="bans-header" class="list-group-item bg-secondary text-white">
|
||||
<div class="row">
|
||||
<div class="col-3 text-center fw-bold">IP Address</div>
|
||||
<div class="col-5 border-start text-center fw-bold">End Date</div>
|
||||
<div class="col-3 border-start text-center fw-bold">Reason</div>
|
||||
<div class="col-1 border-start text-center fw-bold">Delete</div>
|
||||
</div>
|
||||
</li>
|
||||
<li id="ban-1" class="list-group-item rounded-0">
|
||||
<div class="row align-items-center d-flex">
|
||||
<div class="col-3">
|
||||
<input type="text"
|
||||
name="ip"
|
||||
class="form-control"
|
||||
placeholder="127.0.0.1"
|
||||
required />
|
||||
</div>
|
||||
<div class="col-5 border-start">
|
||||
<input type="flapickr-datetime"
|
||||
name="datetime"
|
||||
class="form-control"
|
||||
required />
|
||||
</div>
|
||||
<div class="col-3 border-start">
|
||||
<input type="text" name="reason" class="form-control" value="ui" required />
|
||||
</div>
|
||||
<div class="col-1 border-start align-items-center d-flex"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="right"
|
||||
data-bs-original-title="Can't delete the original Ban">
|
||||
<button type="button"
|
||||
class="btn btn-outline-danger btn-sm me-1 delete-ban disabled">
|
||||
<i class="bx bx-trash bx-xs"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
{% if not is_readonly %}
|
||||
<div class="modal modal-xl fade"
|
||||
id="modal-ban-ips"
|
||||
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">Add Ban(s)</h5>
|
||||
<button id="add-ban"
|
||||
type="button"
|
||||
class="btn btn-text-bw-green rounded-pill p-0 ms-4 me-2">
|
||||
<i class="bx bx-plus-circle"></i> INSERT
|
||||
</button>
|
||||
<button id="clear-bans"
|
||||
type="button"
|
||||
class="btn btn-text-danger rounded-pill p-0 ms-4">
|
||||
<i class="bx bx-trash"></i> CLEAR
|
||||
</button>
|
||||
<button type="button"
|
||||
class="btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-footer justify-content-center">
|
||||
<button type="submit" class="btn btn-outline-danger me-2">Ban</button>
|
||||
<button type="reset"
|
||||
class="btn btn-outline-secondary"
|
||||
data-bs-dismiss="modal">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal modal-lg fade"
|
||||
id="modal-unban-ips"
|
||||
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">Confirm Unban</h5>
|
||||
<button type="button"
|
||||
class="btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"></button>
|
||||
</div>
|
||||
<form action="{{ url_for("bans") }}/unban" method="POST">
|
||||
<div class="modal-body">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||
<input type="hidden" id="selected-ips-input-unban" name="ips" value="" />
|
||||
<div class="alert alert-danger text-center" role="alert">
|
||||
Are you sure you want to unban the selected IP addresses?
|
||||
<form id="bans-form" action="{{ url_for("bans") }}/ban" method="POST">
|
||||
<div class="modal-body justify-content-center">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||
<ul id="bans-container" class="list-group rounded-top w-100">
|
||||
<li id="bans-header" class="list-group-item bg-secondary text-white">
|
||||
<div class="row">
|
||||
<div class="col-3 text-center fw-bold">IP Address</div>
|
||||
<div class="col-5 border-start text-center fw-bold">End Date</div>
|
||||
<div class="col-3 border-start text-center fw-bold">Reason</div>
|
||||
<div class="col-1 border-start text-center fw-bold">Delete</div>
|
||||
</div>
|
||||
</li>
|
||||
<li id="ban-1" class="list-group-item rounded-0">
|
||||
<div class="row align-items-center d-flex">
|
||||
<div class="col-3">
|
||||
<input type="text"
|
||||
name="ip"
|
||||
class="form-control"
|
||||
placeholder="127.0.0.1"
|
||||
required />
|
||||
</div>
|
||||
<div class="col-5 border-start">
|
||||
<input type="flapickr-datetime"
|
||||
name="datetime"
|
||||
class="form-control"
|
||||
required />
|
||||
</div>
|
||||
<div class="col-3 border-start">
|
||||
<input type="text" name="reason" class="form-control" value="ui" required />
|
||||
</div>
|
||||
<div class="col-1 border-start align-items-center d-flex"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="right"
|
||||
data-bs-original-title="Can't delete the original Ban">
|
||||
<button type="button"
|
||||
class="btn btn-outline-danger btn-sm me-1 delete-ban disabled">
|
||||
<i class="bx bx-trash bx-xs"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<ul id="selected-ips-unban" class="list-group w-100 mb-3">
|
||||
</ul>
|
||||
</div>
|
||||
<div class="modal-footer justify-content-center">
|
||||
<button type="submit" class="btn btn-outline-danger me-2">Unban</button>
|
||||
<button type="reset"
|
||||
class="btn btn-outline-secondary"
|
||||
data-bs-dismiss="modal">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
<div class="modal-footer justify-content-center">
|
||||
<button type="submit" class="btn btn-outline-danger me-2">Ban</button>
|
||||
<button type="reset"
|
||||
class="btn btn-outline-secondary"
|
||||
data-bs-dismiss="modal">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal modal-lg fade"
|
||||
id="modal-unban-ips"
|
||||
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">Confirm Unban</h5>
|
||||
<button type="button"
|
||||
class="btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"></button>
|
||||
</div>
|
||||
<form action="{{ url_for("bans") }}/unban" method="POST">
|
||||
<div class="modal-body">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||
<input type="hidden" id="selected-ips-input-unban" name="ips" value="" />
|
||||
<div class="alert alert-danger text-center" role="alert">
|
||||
Are you sure you want to unban the selected IP addresses?
|
||||
</div>
|
||||
<ul id="selected-ips-unban" class="list-group w-100 mb-3">
|
||||
</ul>
|
||||
</div>
|
||||
<div class="modal-footer justify-content-center">
|
||||
<button type="submit" class="btn btn-outline-danger me-2">Unban</button>
|
||||
<button type="reset"
|
||||
class="btn btn-outline-secondary"
|
||||
data-bs-dismiss="modal">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<!-- / Content -->
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -100,6 +100,7 @@
|
|||
nonce="{{ script_nonce }}"></script>
|
||||
</head>
|
||||
<body>
|
||||
<input type="hidden" id="is-read-only" value="{{ is_readonly }}" />
|
||||
<!-- prettier-ignore -->
|
||||
{% if current_endpoint != "loading" %}
|
||||
{% include "flash.html" %}
|
||||
|
|
|
|||
|
|
@ -17,9 +17,12 @@
|
|||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<p id="cache-waiting" class="text-center relative w-full p-2 text-primary rounded-lg fw-bold">Loading cache file...</p>
|
||||
<p id="cache-waiting"
|
||||
class="text-center relative w-full p-2 text-primary rounded-lg fw-bold">Loading cache file...</p>
|
||||
<div id="cache-value"
|
||||
class="visually-hidden ace-editor border rounded position-absolute top-0 start-0 end-0 bottom-0">{{ cache_file }}</div>
|
||||
class="visually-hidden ace-editor border rounded position-absolute top-0 start-0 end-0 bottom-0">
|
||||
{{ cache_file }}
|
||||
</div>
|
||||
</div>
|
||||
<!-- / Content -->
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
<div class="card p-1 mb-4 sticky-card">
|
||||
<div class="d-flex flex-wrap justify-content-around align-items-center">
|
||||
<div class="d-flex">
|
||||
{% if not config_template and config_method and config_method != "ui" %}
|
||||
{% if not config_template and config_method and config_method != "ui" or is_readonly %}
|
||||
<button type="button" class="btn btn-sm btn-secondary ms-2 disabled">
|
||||
<i class="bx bx-xs bx-cube"></i>
|
||||
Service: {{ config_service or "no service" }}
|
||||
|
|
@ -115,15 +115,21 @@
|
|||
value="{{ name }}"
|
||||
pattern="^[a-zA-Z0-9_\-]{1,64}$"
|
||||
required
|
||||
{% if not config_template and config_method and config_method != "ui" %}disabled{% endif %}>
|
||||
{% if not config_template and config_method and config_method != "ui" or is_readonly %}disabled{% endif %}>
|
||||
<label for="config-name">Configuration Name</label>
|
||||
<span class="input-group-text border-0 border-primary border-bottom mt-2 pb-0 shadow-none"
|
||||
id="config-name-suffix">.conf</span>
|
||||
</div>
|
||||
</div>
|
||||
<div {% if not config_template and config_method and config_method != "ui" %}data-bs-toggle="tooltip" data-bs-placement="top" data-bs-original-title="The custom config was created using the {{ config_method }} method, therefore it is locked"{% endif %}>
|
||||
<div {% if not config_template and config_method and config_method != "ui" or is_readonly %}data-bs-toggle="tooltip" data-bs-placement="top" data-bs-original-title="{% if is_readonly %}The database is in readonly{% else %}The custom config was created using the {{ config_method }} method{% endif %}
|
||||
therefore
|
||||
it
|
||||
is
|
||||
locked
|
||||
"
|
||||
{% endif %}>
|
||||
<button type="button"
|
||||
class="btn btn-sm btn-outline-bw-green save-config{% if not config_template and config_method and config_method != "ui" %} disabled{% endif %}">
|
||||
class="btn btn-sm btn-outline-bw-green save-config{% if not config_template and config_method and config_method != "ui" or is_readonly %} disabled{% endif %}">
|
||||
<i class="bx bx-save bx-sm"></i>
|
||||
<span class="d-none d-md-inline"> Save</span>
|
||||
</button>
|
||||
|
|
@ -131,13 +137,18 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="card position-relative p-4 min-vh-70"
|
||||
{% if not config_template and config_method and config_method != "ui" %}data-bs-toggle="tooltip" data-bs-placement="top" data-bs-original-title="Disabled by {{ config_method }}"{% endif %}>
|
||||
<p id="config-waiting" class="text-center relative w-full p-2 text-primary rounded-lg fw-bold">Loading custom configuration...</p>
|
||||
{% if not config_template and config_method and config_method != "ui" or is_readonly %}data-bs-toggle="tooltip" data-bs-placement="top" data-bs-original-title="Disabled by {% if is_readonly %}readonly{% else %}{{ config_method }}{% endif %}
|
||||
"
|
||||
{% endif %}>
|
||||
<p id="config-waiting"
|
||||
class="text-center relative w-full p-2 text-primary rounded-lg fw-bold">Loading custom configuration...</p>
|
||||
<div id="config-value"
|
||||
data-language="{% if type and type.startswith(('CRS', 'MODSEC')) %}ModSecurity{% else %}NGINX{% endif %}"
|
||||
data-method="{{ config_method or 'ui' }}"
|
||||
data-template="{{ config_template }}"
|
||||
class="visually-hidden ace-editor border rounded position-absolute top-0 start-0 end-0 bottom-0">{{ config_value }}</div>
|
||||
class="visually-hidden ace-editor border rounded position-absolute top-0 start-0 end-0 bottom-0">
|
||||
{{ config_value }}
|
||||
</div>
|
||||
</div>
|
||||
<!-- / Content -->
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@
|
|||
<a href="{{ url_for("configs") }}/{{ service_id }}/{{ config['type'] }}/{{ config['name'] }}"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="bottom"
|
||||
data-bs-original-title="Edit custom config {{ config['name'] }}"><i class="bx bx-edit bx-xs"></i> {{ config["name"] }}</a>
|
||||
data-bs-original-title="{% if config['method'] != 'ui' or is_readonly %}View{% else %}Edit{% endif %} custom config {{ config['name'] }}"><i class="bx bx-{% if config['method'] != 'ui' or is_readonly %}show{% else %}edit{% endif %} bx-xs"></i> {{ config["name"] }}</a>
|
||||
</td>
|
||||
<td id="type-{{ config['type'] }}-{{ service_id.replace('.', '_') }}-{{ config['name'] }}">
|
||||
<i class="bx bx-{{ config_icon }}"></i>
|
||||
|
|
@ -101,25 +101,27 @@
|
|||
href="{{ url_for("configs") }}/{{ service_id }}/{{ config['type'] }}/{{ config['name'] }}"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="bottom"
|
||||
data-bs-original-title="Edit custom config {{ config['name'] }}">
|
||||
<i class="bx bx-edit bx-xs"></i>
|
||||
</a>
|
||||
<a role="button"
|
||||
class="btn btn-outline-secondary btn-sm me-1"
|
||||
href="{{ url_for("configs") }}/new?clone={{ service_id }}/{{ config['type'] }}/{{ config['name'] }}"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="bottom"
|
||||
data-bs-original-title="Clone custom config {{ config['name'] }}">
|
||||
<i class="bx bx-copy-alt bx-xs"></i>
|
||||
data-bs-original-title="{% if is_readonly %}View{% else %}Edit{% endif %} custom config {{ config['name'] }}">
|
||||
<i class="bx bx-{% if is_readonly %}show{% else %}edit{% endif %} bx-xs"></i>
|
||||
</a>
|
||||
<div {% if is_readonly %}data-bs-toggle="tooltip" data-bs-placement="bottom" data-bs-original-title="Disabled by readonly"{% endif %}>
|
||||
<a role="button"
|
||||
class="btn btn-outline-secondary btn-sm me-1{% if is_readonly %} disabled{% endif %}"
|
||||
href="{{ url_for("configs") }}/new?clone={{ service_id }}/{{ config['type'] }}/{{ config['name'] }}"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="bottom"
|
||||
data-bs-original-title="Clone custom config {{ config['name'] }}">
|
||||
<i class="bx bx-copy-alt bx-xs"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div data-bs-toggle="tooltip"
|
||||
data-bs-placement="bottom"
|
||||
data-bs-original-title="{% if config['method'] != 'ui' %}Disabled by {% if config['template'] %}template: {{ config['template'] }}{% else %}{{ config['method'] }}{% endif %}{% else %}Delete custom config {{ config['name'] }}{% endif %}">
|
||||
data-bs-original-title="{% if config['method'] != 'ui' or is_readonly %}Disabled by {% if is_readonly %}readonly{% else %}{{ config['method'] }}{% endif %}{% else %}Delete custom config {{ config['name'] }}{% endif %}">
|
||||
<button type="button"
|
||||
data-config-name="{{ config['name'] }}"
|
||||
data-config-type="{{ config['type'] }}"
|
||||
data-config-service="{{ config['service_id'] or 'global' }}"
|
||||
class="btn btn-outline-danger btn-sm me-1 delete-config{% if config['method'] != 'ui' %} disabled{% endif %}">
|
||||
class="btn btn-outline-danger btn-sm me-1 delete-config{% if config['method'] != 'ui' or is_readonly %} disabled{% endif %}">
|
||||
<i class="bx bx-trash bx-xs"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -130,40 +132,42 @@
|
|||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="modal modal-lg fade"
|
||||
id="modal-delete-configs"
|
||||
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">Confirm deletion</h5>
|
||||
<button type="button"
|
||||
class="btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"></button>
|
||||
{% if not is_readonly %}
|
||||
<div class="modal modal-lg fade"
|
||||
id="modal-delete-configs"
|
||||
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">Confirm deletion</h5>
|
||||
<button type="button"
|
||||
class="btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"></button>
|
||||
</div>
|
||||
<form action="{{ url_for("configs") }}/delete" method="POST">
|
||||
<div class="modal-body">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||
<input type="hidden"
|
||||
id="selected-configs-input-delete"
|
||||
name="configs"
|
||||
value="" />
|
||||
<div class="alert alert-danger text-center" role="alert">Are you sure you want to delete the selected configs?</div>
|
||||
<div id="selected-configs-delete" class="mb-3"></div>
|
||||
</div>
|
||||
<div class="modal-footer justify-content-center">
|
||||
<button type="submit" class="btn btn-outline-danger me-2">Delete</button>
|
||||
<button type="reset"
|
||||
class="btn btn-outline-secondary"
|
||||
data-bs-dismiss="modal">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<form action="{{ url_for("configs") }}/delete" method="POST">
|
||||
<div class="modal-body">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||
<input type="hidden"
|
||||
id="selected-configs-input-delete"
|
||||
name="configs"
|
||||
value="" />
|
||||
<div class="alert alert-danger text-center" role="alert">Are you sure you want to delete the selected configs?</div>
|
||||
<div id="selected-configs-delete" class="mb-3"></div>
|
||||
</div>
|
||||
<div class="modal-footer justify-content-center">
|
||||
<button type="submit" class="btn btn-outline-danger me-2">Delete</button>
|
||||
<button type="reset"
|
||||
class="btn btn-outline-secondary"
|
||||
data-bs-dismiss="modal">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<!-- / Content -->
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -141,10 +141,10 @@
|
|||
<span class="d-none d-md-inline">Notifications</span>
|
||||
</button>
|
||||
{% if flash_messages %}
|
||||
<span class="badge-dot-text position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger">
|
||||
{{ flash_messages|length }}
|
||||
<span class="visually-hidden">unread notifications</span>
|
||||
</span>
|
||||
<span class="badge-dot-text position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger">
|
||||
{{ flash_messages|length }}
|
||||
<span class="visually-hidden">unread notifications</span>
|
||||
</span>
|
||||
{% endif %}
|
||||
</li>
|
||||
<li class="position-relative">
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@
|
|||
<td>
|
||||
{% if instance.status == "up" %}
|
||||
<span id="status-{{ instance.hostname }}"
|
||||
class="badge rounded-pill bg-label-success">Up</span>
|
||||
class="badge rounded-pill bg-label-primary">Up</span>
|
||||
{% elif instance.status == "down" %}
|
||||
<span id="status-{{ instance.hostname }}"
|
||||
class="badge rounded-pill bg-label-danger">Down</span>
|
||||
|
|
@ -119,10 +119,10 @@
|
|||
</div>
|
||||
<div data-bs-toggle="tooltip"
|
||||
data-bs-placement="bottom"
|
||||
data-bs-original-title="{% if instance['method'] != 'ui' %}Disabled by {{ instance['method'] }}{% else %}Delete instance {{ instance['hostname'] }}{% endif %}">
|
||||
data-bs-original-title="{% if instance['method'] != 'ui' or is_readonly %}Disabled by {% if is_readonly %}readonly{% else %}{{ instance['method'] }}{% endif %}{% else %}Delete instance {{ instance['hostname'] }}{% endif %}">
|
||||
<button type="button"
|
||||
data-instance="{{ instance['hostname'] }}"
|
||||
class="btn btn-outline-danger btn-sm me-1 delete-instance{% if instance['method'] != 'ui' %} disabled{% endif %}">
|
||||
class="btn btn-outline-danger btn-sm me-1 delete-instance{% if instance['method'] != 'ui' or is_readonly %} disabled{% endif %}">
|
||||
<i class="bx bx-trash bx-xs"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -153,89 +153,91 @@
|
|||
</div>
|
||||
<div class="toast-body">If you read this, it means that you're curious 👀</div>
|
||||
</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>
|
||||
{% if not is_readonly %}
|
||||
<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 class="alert alert-primary text-center" role="alert">
|
||||
You don't need to provide the port or the server_name as the values of both <code>API_HTTP_PORT</code> and <code>API_SERVER_NAME</code> will be used for the instance configuration.
|
||||
</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-outline-secondary"
|
||||
data-bs-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</form>
|
||||
</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 class="alert alert-primary text-center" role="alert">
|
||||
You don't need to provide the port or the server_name as the values of both <code>API_HTTP_PORT</code> and <code>API_SERVER_NAME</code> will be used for the instance configuration.
|
||||
</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-outline-secondary"
|
||||
data-bs-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal fade"
|
||||
id="modal-delete-instances"
|
||||
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">Delete instances</h5>
|
||||
<button type="button"
|
||||
class="btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"></button>
|
||||
<div class="modal fade"
|
||||
id="modal-delete-instances"
|
||||
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">Delete instances</h5>
|
||||
<button type="button"
|
||||
class="btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"></button>
|
||||
</div>
|
||||
<form action="{{ url_for("instances") }}/delete" method="POST">
|
||||
<div class="modal-body">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||
<input type="hidden" id="selected-instances-input" name="instances" value="" />
|
||||
<div class="alert alert-danger text-center" role="alert">Are you sure you want to delete the selected instances?</div>
|
||||
<div id="selected-instances" class="mb-3"></div>
|
||||
</div>
|
||||
<div class="modal-footer justify-content-center">
|
||||
<button type="submit" class="btn btn-outline-danger me-2">Delete instances</button>
|
||||
<button type="reset"
|
||||
class="btn btn-outline-secondary"
|
||||
data-bs-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<form action="{{ url_for("instances") }}/delete" method="POST">
|
||||
<div class="modal-body">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||
<input type="hidden" id="selected-instances-input" name="instances" value="" />
|
||||
<div class="alert alert-danger text-center" role="alert">Are you sure you want to delete the selected instances?</div>
|
||||
<div id="selected-instances" class="mb-3"></div>
|
||||
</div>
|
||||
<div class="modal-footer justify-content-center">
|
||||
<button type="submit" class="btn btn-outline-danger me-2">Delete instances</button>
|
||||
<button type="reset"
|
||||
class="btn btn-outline-secondary"
|
||||
data-bs-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<!-- / Content -->
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@
|
|||
<td>{{ job }}</td>
|
||||
<td>{{ job_data["plugin_id"] }}</td>
|
||||
<td>
|
||||
<i class="bx {% if job_data['every'] == 'once' %}bx-check-square{% elif job_data['every'] == 'day' %}bx-calendar-event{% elif job_data['every'] == 'week' %}bx-calendar-week{% else %}bxs-hourglass{% endif %}"></i> {{ job_data["every"] }}
|
||||
<i class="bx {% if job_data['every'] == 'once' %}bx-revision{% elif job_data['every'] == 'day' %}bx-calendar-event{% elif job_data['every'] == 'week' %}bx-calendar-week{% else %}bxs-hourglass{% endif %}"></i> {{ job_data["every"] }}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<i class="bx bx-sm bx-{% if job_data['reload'] %}check-circle text-success{% else %}x-circle text-danger{% endif %}"></i>
|
||||
|
|
@ -60,11 +60,11 @@
|
|||
<div class="d-flex justify-content-center">
|
||||
<div data-bs-toggle="tooltip"
|
||||
data-bs-placement="bottom"
|
||||
data-bs-original-title="Run the Job">
|
||||
data-bs-original-title="{% if is_readonly %}Disabled by readonly{% else %}Run the Job{% endif %}">
|
||||
<button type="button"
|
||||
data-job="{{ job }}"
|
||||
data-plugin="{{ job_data['plugin_id'] }}"
|
||||
class="btn btn-primary btn-sm me-1 run-job">
|
||||
class="btn btn-primary btn-sm me-1 run-job{% if is_readonly %} disabled{% endif %}">
|
||||
<i class="bx bx-play bx-xs"></i> Run
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -65,9 +65,12 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="card p-4 min-vh-70">
|
||||
<p id="logs-waiting" class="text-center relative w-full p-2 text-primary rounded-lg fw-bold">Loading logs...</p>
|
||||
<p id="logs-waiting"
|
||||
class="text-center relative w-full p-2 text-primary rounded-lg fw-bold">Loading logs...</p>
|
||||
<div id="raw-logs"
|
||||
class="visually-hidden ace-editor border rounded position-absolute top-0 start-0 end-0 bottom-0">{{ logs }}</div>
|
||||
class="visually-hidden ace-editor border rounded position-absolute top-0 start-0 end-0 bottom-0">
|
||||
{{ logs }}
|
||||
</div>
|
||||
</div>
|
||||
<!-- / Content -->
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@
|
|||
</div>
|
||||
<div class="d-flex justify-content-center">
|
||||
{% if current_endpoint != "global-config" %}
|
||||
<div {% if current_endpoint != 'new' and service_method != 'ui' %}data-bs-toggle="tooltip" data-bs-placement="top" title="The draft mode can only be toggled on UI created services"{% endif %}>
|
||||
<div {% if current_endpoint != 'new' and service_method != 'ui' %}data-bs-toggle="tooltip" data-bs-placement="top" data-bs-original-title="The draft mode can only be toggled on UI created services"{% endif %}>
|
||||
<button type="button"
|
||||
class="btn btn-sm btn-outline-secondary toggle-draft me-3 {% if current_endpoint != 'new' and service_method != 'ui' %}disabled{% endif %}">
|
||||
<i class="bx bx-sm bx-{% if is_draft == 'yes' %}file-blank{% else %}globe{% endif %}"></i>
|
||||
|
|
@ -79,9 +79,16 @@
|
|||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div {% if service_method == "autoconf" %}data-bs-toggle="tooltip" data-bs-placement="top" title="The service was created using the autoconf method, therefore the configuration is locked"{% endif %}>
|
||||
<div {% if service_method == "autoconf" or is_readonly %}data-bs-toggle="tooltip" data-bs-placement="top" data-bs-original-title="{% if is_readonly %}The database is in readonly{% else %}The service was created using the autoconf method{% endif %}
|
||||
therefore
|
||||
the
|
||||
configuration
|
||||
is
|
||||
locked
|
||||
"
|
||||
{% endif %}>
|
||||
<button type="button"
|
||||
class="btn btn-sm btn-outline-bw-green save-settings {% if service_method == "autoconf" %}disabled{% endif %}">
|
||||
class="btn btn-sm btn-outline-bw-green save-settings {% if service_method == "autoconf" or is_readonly %}disabled{% endif %}">
|
||||
<i class="bx bx-save bx-sm"></i>
|
||||
<span class="d-none d-md-inline"> Save</span>
|
||||
</button>
|
||||
|
|
@ -115,7 +122,7 @@
|
|||
rel="noopener"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="top"
|
||||
title="{% if plugin_data['stream'] != 'no' %}Supports{% else %}Doesn't support{% endif %} STREAM mode{% if plugin_data['stream'] == 'partial' %} partially{% endif %}">
|
||||
data-bs-original-title="{% if plugin_data['stream'] != 'no' %}Supports{% else %}Doesn't support{% endif %} STREAM mode{% if plugin_data['stream'] == 'partial' %} partially{% endif %}">
|
||||
<i class="bx bx-{% if plugin_data['stream'] == 'yes' %}badge-check{% elif plugin_data['stream'] == 'partial' %}message-square-detail{% else %}no-entry{% endif %}"></i> STREAM
|
||||
</a>
|
||||
<a href="{% if plugin_data['type'] == 'core' %}https://docs.bunkerweb.io/latest/settings/?utm_campaign=self&utm_source=ui#{% if plugin == 'general' %}global-settings{% else %}{{ plugin }}{% endif %}{% else %}https://docs.bunkerweb.io/latest/plugins/?utm_campaign=self&utm_source=ui{% endif %}"
|
||||
|
|
@ -151,8 +158,12 @@
|
|||
{% set setting_method = "autoconf" %}
|
||||
{% set disabled = true %}
|
||||
{% endif %}
|
||||
{% if is_readonly %}
|
||||
{% set disabled = true %}
|
||||
{% set setting_method = "readonly" %}
|
||||
{% endif %}
|
||||
<div class="col-12 col-sm-6 col-lg-4 pb-3"
|
||||
{% if disabled %}data-bs-toggle="tooltip" data-bs-placement="top" title="Disabled by {{ setting_method }}"{% endif %}>
|
||||
{% if disabled %}data-bs-toggle="tooltip" data-bs-placement="top" data-bs-original-title="Disabled by {{ setting_method }}"{% endif %}>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<label id="label-setting-{{ plugin }}-{{ setting_data['id'] }}"
|
||||
for="setting-{{ plugin }}-{{ setting_data['id'] }}"
|
||||
|
|
@ -164,7 +175,7 @@
|
|||
class="badge badge-center rounded-pill bg-secondary d-flex align-items-center justify-content-center p-1 me-1"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="top"
|
||||
title="Multisite setting"
|
||||
data-bs-original-title="Multisite setting"
|
||||
target="_blank"
|
||||
rel="noopener">
|
||||
<span class="bx bx-server bx-xs"></span>
|
||||
|
|
@ -174,7 +185,7 @@
|
|||
<span class="badge badge-center rounded-pill bg-secondary d-flex align-items-center justify-content-center p-1 me-1"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="top"
|
||||
title="From template: {{ setting_template }}">
|
||||
data-bs-original-title="From template: {{ setting_template }}">
|
||||
<span class="bx bx-spreadsheet bx-xs"></span>
|
||||
</span>
|
||||
{% endif %}
|
||||
|
|
@ -182,14 +193,14 @@
|
|||
<span class="badge badge-center rounded-pill bg-primary-subtle text-dark d-flex align-items-center justify-content-center p-1 me-1"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="top"
|
||||
title="From global configuration">
|
||||
data-bs-original-title="From global configuration">
|
||||
<span class="bx bx-globe bx-xs"></span>
|
||||
</span>
|
||||
{% endif %}
|
||||
<span class="badge rounded-pill bg-secondary-subtle text-dark d-flex align-items-center justify-content-center p-1"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="top"
|
||||
title="{{ setting_data['help'] }}">
|
||||
data-bs-original-title="{{ setting_data['help'] }}">
|
||||
<span class="bx bx-question-mark bx-xs"></span>
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -235,19 +246,27 @@
|
|||
</h6>
|
||||
<div class="d-flex align-items-center">
|
||||
{% if setting_suffix == "0" %}
|
||||
<button id="add-multiple-{{ plugin }}-{{ multiple }}"
|
||||
type="button"
|
||||
class="btn btn-xs btn-text-bw-green rounded-pill add-multiple p-0 pe-2">
|
||||
<i class="bx bx-plus-circle bx-sm"></i> ADD
|
||||
</button>
|
||||
{% else %}
|
||||
<div>
|
||||
<button id="remove-multiple-{{ plugin }}-{{ multiple }}-{{ setting_suffix }}"
|
||||
<div class="me-1"
|
||||
{% if service_method == "autoconf" or is_readonly %}data-bs-toggle="tooltip" data-bs-placement="top" data-bs-original-title="{% if is_readonly %}The database is in readonly{% else %}The service was created using the autoconf method{% endif %}
|
||||
therefore
|
||||
multiple
|
||||
settings
|
||||
are
|
||||
locked
|
||||
"
|
||||
{% endif %}>
|
||||
<button id="add-multiple-{{ plugin }}-{{ multiple }}"
|
||||
type="button"
|
||||
class="btn btn-xs btn-text-danger rounded-pill remove-multiple p-0 pe-2">
|
||||
<i class="bx bx-trash bx-sm"></i> REMOVE
|
||||
class="btn btn-xs btn-text-bw-green rounded-pill add-multiple p-0 pe-2{% if service_method == "autoconf" or is_readonly %} disabled{% endif %}">
|
||||
<i class="bx bx-plus-circle bx-sm"></i> ADD
|
||||
</button>
|
||||
</div>
|
||||
{% else %}
|
||||
<button id="remove-multiple-{{ plugin }}-{{ multiple }}-{{ setting_suffix }}"
|
||||
type="button"
|
||||
class="btn btn-xs btn-text-danger rounded-pill remove-multiple p-0 pe-2">
|
||||
<i class="bx bx-trash bx-sm"></i> REMOVE
|
||||
</button>
|
||||
{% endif %}
|
||||
<button id="show-multiple-{{ plugin }}-{{ multiple }}-{{ setting_suffix }}"
|
||||
type="button"
|
||||
|
|
@ -277,8 +296,12 @@
|
|||
{% set setting_method = "autoconf" %}
|
||||
{% set disabled = true %}
|
||||
{% endif %}
|
||||
{% if is_readonly %}
|
||||
{% set disabled = true %}
|
||||
{% set setting_method = "readonly" %}
|
||||
{% endif %}
|
||||
<div class="col-12{% if multiple_settings %} col-md-6{% endif %}{% if settings|length > 2 and not multiple_multiples %} col-lg-4{% endif %} pb-2"
|
||||
{% if disabled %}data-bs-toggle="tooltip" data-bs-placement="top" title="Disabled by {{ setting_method }}"{% endif %}>
|
||||
{% if disabled %}data-bs-toggle="tooltip" data-bs-placement="top" data-bs-original-title="Disabled by {{ setting_method }}"{% endif %}>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<label id="label-multiple-setting-{{ plugin }}-{{ setting_data['id'] }}-{{ setting_suffix }}"
|
||||
for="multiple-setting-{{ plugin }}-{{ setting_data['id'] }}-{{ setting_suffix }}"
|
||||
|
|
@ -292,7 +315,7 @@
|
|||
class="badge badge-center rounded-pill bg-secondary d-flex align-items-center justify-content-center p-1 me-1"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="top"
|
||||
title="Multisite setting"
|
||||
data-bs-original-title="Multisite setting"
|
||||
target="_blank"
|
||||
rel="noopener">
|
||||
<span class="bx bx-server bx-xs"></span>
|
||||
|
|
@ -302,7 +325,7 @@
|
|||
<span class="badge badge-center rounded-pill bg-secondary d-flex align-items-center justify-content-center p-1 me-1"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="top"
|
||||
title="From template: {{ setting_template }}">
|
||||
data-bs-original-title="From template: {{ setting_template }}">
|
||||
<span class="bx bx-spreadsheet bx-xs"></span>
|
||||
</span>
|
||||
{% endif %}
|
||||
|
|
@ -310,14 +333,14 @@
|
|||
<span class="badge badge-center rounded-pill bg-primary-subtle text-dark d-flex align-items-center justify-content-center p-1 me-1"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="top"
|
||||
title="From global configuration">
|
||||
data-bs-original-title="From global configuration">
|
||||
<span class="bx bx-globe bx-xs"></span>
|
||||
</span>
|
||||
{% endif %}
|
||||
<span class="badge rounded-pill bg-secondary-subtle text-dark d-flex align-items-center justify-content-center p-1"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="top"
|
||||
title="{{ setting_data['help'] }}">
|
||||
data-bs-original-title="{{ setting_data['help'] }}">
|
||||
<span class="bx bx-question-mark bx-xs"></span>
|
||||
</span>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@
|
|||
</div>
|
||||
<div class="d-flex justify-content-center">
|
||||
{% if current_endpoint != "global-config" %}
|
||||
<div {% if current_endpoint != 'new' and service_method != 'ui' %}data-bs-toggle="tooltip" data-bs-placement="top" title="The draft mode can only be toggled on UI created services"{% endif %}>
|
||||
<div {% if current_endpoint != 'new' and service_method != 'ui' %}data-bs-toggle="tooltip" data-bs-placement="top" data-bs-original-title="The draft mode can only be toggled on UI created services"{% endif %}>
|
||||
<button type="button"
|
||||
class="btn btn-sm btn-outline-secondary toggle-draft me-3 {% if current_endpoint != 'new' and service_method != 'ui' %}disabled{% endif %}">
|
||||
<i class="bx bx-sm bx-{% if is_draft == 'yes' %}file-blank{% else %}globe{% endif %}"></i>
|
||||
|
|
@ -58,7 +58,7 @@
|
|||
class="btn btn-sm btn-outline-danger me-3"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="top"
|
||||
title="Reset the current template settings">
|
||||
data-bs-original-title="Reset the current template settings">
|
||||
<i class="bx bx-sm bx-reset"></i>
|
||||
<span class="d-none d-md-inline">
|
||||
Reset
|
||||
|
|
@ -141,8 +141,12 @@
|
|||
{% set setting_method = "autoconf" %}
|
||||
{% set disabled = true %}
|
||||
{% endif %}
|
||||
{% if is_readonly %}
|
||||
{% set disabled = true %}
|
||||
{% set setting_method = "readonly" %}
|
||||
{% endif %}
|
||||
<div class="col-12 col-sm-6 col-lg-4 pb-3"
|
||||
{% if disabled %}data-bs-toggle="tooltip" data-bs-placement="top" title="Disabled by {{ setting_method }}"{% endif %}>
|
||||
{% if disabled %}data-bs-toggle="tooltip" data-bs-placement="top" data-bs-original-title="Disabled by {{ setting_method }}"{% endif %}>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<label id="label-{{ template }}-setting-{{ template_data['plugin_id'] }}-{{ setting_data['id'] }}"
|
||||
for="{{ template }}-setting-{{ template_data['plugin_id'] }}-{{ setting_data['id'] }}"
|
||||
|
|
@ -156,7 +160,7 @@
|
|||
class="badge badge-center rounded-pill bg-secondary d-flex align-items-center justify-content-center p-1 me-1"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="top"
|
||||
title="Multisite setting"
|
||||
data-bs-original-title="Multisite setting"
|
||||
target="_blank"
|
||||
rel="noopener">
|
||||
<span class="bx bx-server bx-xs"></span>
|
||||
|
|
@ -166,14 +170,14 @@
|
|||
<span class="badge badge-center rounded-pill bg-primary-subtle text-dark d-flex align-items-center justify-content-center p-1 me-1"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="top"
|
||||
title="From global configuration">
|
||||
data-bs-original-title="From global configuration">
|
||||
<span class="bx bx-globe bx-xs"></span>
|
||||
</span>
|
||||
{% endif %}
|
||||
<span class="badge rounded-pill bg-secondary-subtle text-dark d-flex align-items-center justify-content-center p-1"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="top"
|
||||
title="{{ setting_data['help'] }}">
|
||||
data-bs-original-title="{{ setting_data['help'] }}">
|
||||
<span class="bx bx-question-mark bx-xs"></span>
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -202,15 +206,25 @@
|
|||
{% for step_config in step_configs %}
|
||||
{% set step_config_id = template + "-config-" + template_data['plugin_id'] + "-" + step_config.replace("/", "-").replace(".conf", "") %}
|
||||
{% set step_config_value = configs.get(current_endpoint + "_" + step_config.replace(".conf", "").replace("/", "_")) %}
|
||||
{% set step_config_method = "ui" %}
|
||||
{% if step_config_value is none %}
|
||||
{% set step_config_value = template_data["configs"].get(step_config, "") %}
|
||||
{% else %}
|
||||
{% set step_config_value = step_config_value["data"].decode("utf-8") %}
|
||||
{% set step_config_method = step_config_value["method"] %}
|
||||
{% endif %}
|
||||
<div class="mb-3 pb-6 position-relative h-vh-40">
|
||||
<div class="mb-3 pb-6 position-relative h-vh-40"
|
||||
{% if service_method == "autoconf" or is_readonly %}data-bs-toggle="tooltip" data-bs-placement="top" data-bs-original-title="{% if is_readonly %}The database is in readonly{% else %}The service was created using the autoconf method{% endif %}
|
||||
therefore
|
||||
the
|
||||
configuration
|
||||
is
|
||||
locked
|
||||
"
|
||||
{% endif %}>
|
||||
<label for="{{ step_config_id }}" class="form-label fw-semibold fs-6">{{ step_config }}</label>
|
||||
<textarea id="{{ step_config_id }}-default" class="visually-hidden">{{ template_data["configs"].get(step_config, "") }}</textarea>
|
||||
<div id="{{ step_config_id }}" aria-labelledby="label-{{ step_config_id }}" data-language="{% if step_config.startswith(('crs', 'modsec')) %}ModSecurity{% else %}NGINX{% endif %}" data-name="CUSTOM_CONF_{{ step_config.split("/")[0] |upper }}_{{ step_config.split("/")[1] .replace(".conf", "") }}" class="ace-editor border rounded position-absolute top-0 start-0 end-0 bottom-0 mt-6">
|
||||
<div id="{{ step_config_id }}" aria-labelledby="label-{{ step_config_id }}" data-language="{% if step_config.startswith(('crs', 'modsec')) %}ModSecurity{% else %}NGINX{% endif %}" data-name="CUSTOM_CONF_{{ step_config.split("/")[0] |upper }}_{{ step_config.split("/")[1] .replace(".conf", "") }}" data-method="{% if is_readonly %}readonly{% else %}{{ step_config_method }}{% endif %}" class="ace-editor border rounded position-absolute top-0 start-0 end-0 bottom-0 mt-6">
|
||||
{{ step_config_value }}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -227,9 +241,16 @@
|
|||
<div></div>
|
||||
{% endif %}
|
||||
{% if loop.index == template_data["steps"]|length %}
|
||||
<div {% if service_method == "autoconf" %}data-bs-toggle="tooltip" data-bs-placement="top" title="The service was created using the autoconf method, therefore the configuration is locked"{% endif %}>
|
||||
<div {% if service_method == "autoconf" or is_readonly %}data-bs-toggle="tooltip" data-bs-placement="top" data-bs-original-title="{% if is_readonly %}The database is in readonly{% else %}The service was created using the autoconf method{% endif %}
|
||||
therefore
|
||||
the
|
||||
configuration
|
||||
is
|
||||
locked
|
||||
"
|
||||
{% endif %}>
|
||||
<button type="button"
|
||||
class="btn btn-outline-bw-green save-settings{% if service_method == "autoconf" %} disabled{% endif %}"
|
||||
class="btn btn-outline-bw-green save-settings{% if service_method == "autoconf" or is_readonly %} disabled{% endif %}"
|
||||
data-template="{{ template }}">
|
||||
<i class="bx bx-save bx-sm ms-sm-n2"></i>
|
||||
<span class="align-middle d-sm-inline-block d-none ms-sm-1">Save</span>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
<div class="position-absolute top-0 end-0 m-3" style="z-index: 1000">
|
||||
<div class="d-flex flex-wrap justify-content-center align-items-center">
|
||||
<div class="card p-1 me-2"
|
||||
{% if not request.is_secure %}data-bs-toggle="tooltip" data-bs-placement="top" title="The copy feature is only available over HTTPS"{% endif %}>
|
||||
{% if not request.is_secure %}data-bs-toggle="tooltip" data-bs-placement="top" data-bs-original-title="The copy feature is only available over HTTPS"{% endif %}>
|
||||
<button type="button"
|
||||
class="btn btn-sm btn-outline-secondary copy-settings{% if not request.is_secure %} disabled{% endif %}">
|
||||
<i class="bx bx-copy-alt bx-xs"></i>
|
||||
|
|
@ -13,10 +13,17 @@
|
|||
</button>
|
||||
</div>
|
||||
<div class="card p-1"
|
||||
{% if service_method == "autoconf" %} data-bs-toggle="tooltip" data-bs-placement="top" title="The service was created using the autoconf method, therefore the configuration is locked"{% endif %}>
|
||||
{% if service_method == "autoconf" or is_readonly %}data-bs-toggle="tooltip" data-bs-placement="top" data-bs-original-title="{% if is_readonly %}The database is in readonly{% else %}The service was created using the autoconf method{% endif %}
|
||||
therefore
|
||||
the
|
||||
configuration
|
||||
is
|
||||
locked
|
||||
"
|
||||
{% endif %}>
|
||||
<!-- Save button container -->
|
||||
<button type="button"
|
||||
class="btn btn-sm btn-outline-bw-green save-settings {% if service_method == "autoconf" %}disabled{% endif %}">
|
||||
class="btn btn-sm btn-outline-bw-green save-settings {% if service_method == "autoconf" or is_readonly %}disabled{% endif %}">
|
||||
<i class="bx bx-save bx-xs"></i>
|
||||
<span class="d-none d-md-inline"> Save</span>
|
||||
</button>
|
||||
|
|
@ -24,7 +31,9 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="card bg-primary p-1 d-flex flex-column"
|
||||
{% if service_method == "autoconf" %}data-bs-toggle="tooltip" data-bs-placement="top" title="Disabled by {{ service_method }}"{% endif %}>
|
||||
{% if service_method == "autoconf" or is_readonly %}data-bs-toggle="tooltip" data-bs-placement="bottom" data-bs-original-title="Disabled by {% if is_readonly %}readonly{% else %}{{ service_method }}{% endif %}
|
||||
"
|
||||
{% endif %}>
|
||||
{% set config_lines = ["IS_DRAFT=" + config.get('IS_DRAFT', {}).get('value', 'no')] %}
|
||||
{% set default_settings = ["IS_DRAFT=no"] %}
|
||||
{% for plugin_data in plugins.values() %}
|
||||
|
|
@ -56,7 +65,7 @@
|
|||
rows="35"
|
||||
id="raw-config"
|
||||
aria-label="Raw configuration"
|
||||
{% if service_method == "autoconf" %}disabled{% endif %}>{{ raw_config|safe }}</textarea>
|
||||
{% if service_method == "autoconf" or is_readonly %}readonly{% endif %}>{{ raw_config|safe }}</textarea>
|
||||
<label for="raw-config" class="text-white">Raw configuration</label>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -117,7 +117,8 @@
|
|||
<form method="POST" action="{{ profile_url }}/edit">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<div class="col-md-6"
|
||||
{% if is_readonly %} data-bs-toggle="tooltip" data-bs-placement="top" data-bs-original-title="Disabled by readonly"{% endif %}>
|
||||
<label for="username" class="form-label">Username</label>
|
||||
<input class="form-control"
|
||||
type="text"
|
||||
|
|
@ -126,9 +127,11 @@
|
|||
value="{{ current_user.get_id() }}"
|
||||
placeholder="john.doe@example.com"
|
||||
aria-label="Username"
|
||||
required />
|
||||
required
|
||||
{% if is_readonly %}disabled{% endif %} />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="col-md-6"
|
||||
{% if is_readonly %} data-bs-toggle="tooltip" data-bs-placement="top" data-bs-original-title="Disabled by readonly"{% endif %}>
|
||||
<label for="email" class="form-label">E-mail</label>
|
||||
<input class="form-control"
|
||||
type="email"
|
||||
|
|
@ -136,24 +139,32 @@
|
|||
name="email"
|
||||
value="{{ current_user.email or '' }}"
|
||||
placeholder="john.doe@example.com"
|
||||
aria-label="E-mail" />
|
||||
aria-label="E-mail"
|
||||
{% if is_readonly %}disabled{% endif %} />
|
||||
</div>
|
||||
<div class="col-md-12 form-password-toggle">
|
||||
<label class="form-label" for="password">Current password</label>
|
||||
<div class="input-group input-group-merge">
|
||||
<div class="input-group input-group-merge"
|
||||
{% if is_readonly %} data-bs-toggle="tooltip" data-bs-placement="top" data-bs-original-title="Disabled by readonly"{% endif %}>
|
||||
<input type="password"
|
||||
id="password"
|
||||
class="form-control"
|
||||
name="password"
|
||||
placeholder="············"
|
||||
aria-describedby="Current password"
|
||||
required />
|
||||
required
|
||||
{% if is_readonly %}disabled{% endif %} />
|
||||
<span class="input-group-text cursor-pointer"><i class="bx bx-hide"></i></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 justify-content-center d-flex">
|
||||
<button type="submit" class="btn btn-primary me-2">Save Changes</button>
|
||||
<div {% if is_readonly %}data-bs-toggle="tooltip" data-bs-placement="top" data-bs-original-title="The database is in readonly, therefore the configuration is locked"{% endif %}>
|
||||
<button type="submit"
|
||||
class="btn btn-primary me-2{% if is_readonly %} disabled{% endif %}">
|
||||
Save Changes
|
||||
</button>
|
||||
</div>
|
||||
<button type="reset" class="btn btn-outline-secondary">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
|
|
@ -165,19 +176,16 @@
|
|||
<!-- Theme -->
|
||||
<div class="card">
|
||||
<h5 class="card-header">Change Theme</h5>
|
||||
<div class="card-body pb-4">
|
||||
<div class="card-body pb-4"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="top"
|
||||
data-bs-html="true"
|
||||
data-bs-original-title="<i class='bx bx-rocket bx-xs'></i><span>Coming soon</span>">
|
||||
<!-- <form method="POST" action="{{ profile_url }}/edit"> -->
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||
<div class="row g-3">
|
||||
<div class="col-md-12 form-floating">
|
||||
<select class="form-select"
|
||||
id="theme"
|
||||
name="theme"
|
||||
disabled
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="top"
|
||||
data-bs-html="true"
|
||||
data-bs-original-title="<i class='bx bx-rocket bx-xs'></i><span>Coming soon</span>">
|
||||
<select class="form-select" id="theme" name="theme" disabled>
|
||||
<option value="light"
|
||||
{% if current_user.theme == "light" %}selected{% endif %}>
|
||||
Light
|
||||
|
|
@ -188,7 +196,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="mt-4 justify-content-center d-flex">
|
||||
<button type="submit" class="btn btn-primary me-2">Save Changes</button>
|
||||
<button type="submit" class="btn btn-primary me-2 disabled">Save Changes</button>
|
||||
<button type="reset" class="btn btn-outline-secondary">Cancel</button>
|
||||
</div>
|
||||
<!-- </form> -->
|
||||
|
|
@ -210,16 +218,11 @@
|
|||
<div class="row g-3">
|
||||
<div class="col-md-12 form-password-toggle">
|
||||
<label for="new_password" class="form-label">New Password</label>
|
||||
<div class="input-group input-group-merge">
|
||||
<input class="form-control"
|
||||
type="password"
|
||||
id="new_password"
|
||||
name="new_password"
|
||||
placeholder="············"
|
||||
aria-label="New Password"
|
||||
autocomplete="off"
|
||||
required
|
||||
pattern="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[ !"#$%&'()*+,./:;<=>?@[\\\]^_`{|}~-]).{8,}$" />
|
||||
<div class="input-group input-group-merge"
|
||||
{% if is_readonly %} data-bs-toggle="tooltip" data-bs-placement="top" data-bs-original-title="Disabled by readonly"{% endif %}>
|
||||
<input class="form-control" type="password" id="new_password" name="new_password" placeholder="············" aria-label="New Password" autocomplete="off" required pattern="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[ !"#$%&'()*+,./:;<=>?@[\\\]^_`{|}~-]).{8,}$"
|
||||
{% if is_readonly %}disabled{% endif %}
|
||||
/>
|
||||
<span class="input-group-text cursor-pointer"><i class="bx bx-hide"></i></span>
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
|
|
@ -245,35 +248,37 @@
|
|||
</div>
|
||||
<div class="col-md-12 form-password-toggle">
|
||||
<label for="new_password_confirm" class="form-label">Confirm Password</label>
|
||||
<div class="input-group input-group-merge">
|
||||
<input class="form-control"
|
||||
type="password"
|
||||
id="new_password_confirm"
|
||||
name="new_password_confirm"
|
||||
placeholder="············"
|
||||
aria-label="Confirm Password"
|
||||
autocomplete="off"
|
||||
required
|
||||
pattern="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[ !"#$%&'()*+,./:;<=>?@[\\\]^_`{|}~-]).{8,}$" />
|
||||
<div class="input-group input-group-merge"
|
||||
{% if is_readonly %} data-bs-toggle="tooltip" data-bs-placement="top" data-bs-original-title="Disabled by readonly"{% endif %}>
|
||||
<input class="form-control" type="password" id="new_password_confirm" name="new_password_confirm" placeholder="············" aria-label="Confirm Password" autocomplete="off" required pattern="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[ !"#$%&'()*+,./:;<=>?@[\\\]^_`{|}~-]).{8,}$"
|
||||
{% if is_readonly %}disabled{% endif %}
|
||||
/>
|
||||
<span class="input-group-text cursor-pointer"><i class="bx bx-hide"></i></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12 form-password-toggle">
|
||||
<label class="form-label" for="password">Current password</label>
|
||||
<div class="input-group input-group-merge">
|
||||
<div class="input-group input-group-merge"
|
||||
{% if is_readonly %} data-bs-toggle="tooltip" data-bs-placement="top" data-bs-original-title="Disabled by readonly"{% endif %}>
|
||||
<input type="password"
|
||||
id="password"
|
||||
class="form-control"
|
||||
name="password"
|
||||
placeholder="············"
|
||||
aria-describedby="Current password"
|
||||
required />
|
||||
required
|
||||
{% if is_readonly %}disabled{% endif %} />
|
||||
<span class="input-group-text cursor-pointer"><i class="bx bx-hide"></i></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 justify-content-center d-flex">
|
||||
<button type="submit" class="btn btn-primary me-2">Save Changes</button>
|
||||
<div {% if is_readonly %}data-bs-toggle="tooltip" data-bs-placement="top" data-bs-original-title="The database is in readonly, therefore the configuration is locked"{% endif %}>
|
||||
<button type="submit"
|
||||
class="btn btn-primary me-2{% if is_readonly %} disabled{% endif %}">
|
||||
Save Changes
|
||||
</button>
|
||||
</div>
|
||||
<button type="reset" class="btn btn-outline-secondary">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
|
|
@ -320,7 +325,8 @@
|
|||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<div class="col-md-12"
|
||||
{% if is_readonly %} data-bs-toggle="tooltip" data-bs-placement="top" data-bs-original-title="Disabled by readonly"{% endif %}>
|
||||
<label for="2fa_code" class="form-label text-start d-block">2FA Code</label>
|
||||
<div class="input-group">
|
||||
<input class="form-control"
|
||||
|
|
@ -329,25 +335,33 @@
|
|||
name="totp_token"
|
||||
placeholder="Enter code"
|
||||
aria-label="2FA Code"
|
||||
required />
|
||||
required
|
||||
{% if is_readonly %}disabled{% endif %} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12 form-password-toggle">
|
||||
<label class="form-label" for="password">Current password</label>
|
||||
<div class="input-group input-group-merge">
|
||||
<div class="input-group input-group-merge"
|
||||
{% if is_readonly %} data-bs-toggle="tooltip" data-bs-placement="top" data-bs-original-title="Disabled by readonly"{% endif %}>
|
||||
<input type="password"
|
||||
id="password"
|
||||
class="form-control"
|
||||
name="password"
|
||||
placeholder="············"
|
||||
aria-describedby="Current password"
|
||||
required />
|
||||
required
|
||||
{% if is_readonly %}disabled{% endif %} />
|
||||
<span class="input-group-text cursor-pointer"><i class="bx bx-hide"></i></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 justify-content-center d-flex">
|
||||
<button type="submit" class="btn btn-primary">Enable TOTP</button>
|
||||
<div {% if is_readonly %}data-bs-toggle="tooltip" data-bs-placement="top" data-bs-original-title="The database is in readonly, therefore the configuration is locked"{% endif %}>
|
||||
<button type="submit"
|
||||
class="btn btn-primary{% if is_readonly %} disabled{% endif %}">
|
||||
Enable TOTP
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<!-- /Enable 2FA -->
|
||||
|
|
@ -361,32 +375,41 @@
|
|||
</div>
|
||||
<div class="col-md-12">
|
||||
<label for="totp_token" class="form-label text-start d-block">2FA Code or Recovery Code</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group"
|
||||
{% if is_readonly %} data-bs-toggle="tooltip" data-bs-placement="top" data-bs-original-title="Disabled by readonly"{% endif %}>
|
||||
<input class="form-control"
|
||||
type="text"
|
||||
id="totp_token"
|
||||
name="totp_token"
|
||||
placeholder="Enter code"
|
||||
aria-label="2FA Code or Recovery Code"
|
||||
required />
|
||||
required
|
||||
{% if is_readonly %}disabled{% endif %} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12 form-password-toggle">
|
||||
<label class="form-label" for="password">Current password</label>
|
||||
<div class="input-group input-group-merge">
|
||||
<div class="input-group input-group-merge"
|
||||
{% if is_readonly %} data-bs-toggle="tooltip" data-bs-placement="top" data-bs-original-title="Disabled by readonly"{% endif %}>
|
||||
<input type="password"
|
||||
id="password"
|
||||
class="form-control"
|
||||
name="password"
|
||||
placeholder="············"
|
||||
aria-describedby="Current password"
|
||||
required />
|
||||
required
|
||||
{% if is_readonly %}disabled{% endif %} />
|
||||
<span class="input-group-text cursor-pointer"><i class="bx bx-hide"></i></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 justify-content-center d-flex">
|
||||
<button type="submit" class="btn btn-primary">Disable TOTP</button>
|
||||
<div {% if is_readonly %}data-bs-toggle="tooltip" data-bs-placement="top" data-bs-original-title="The database is in readonly, therefore the configuration is locked"{% endif %}>
|
||||
<button type="submit"
|
||||
class="btn btn-primary{% if is_readonly %} disabled{% endif %}">
|
||||
Disable TOTP
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<!-- /Disable 2FA -->
|
||||
|
|
@ -408,7 +431,8 @@
|
|||
autocomplete="off">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||
<div class="row g-3">
|
||||
<div class="col-md-12 form-password-toggle">
|
||||
<div class="col-md-12 form-password-toggle"
|
||||
{% if is_readonly %} data-bs-toggle="tooltip" data-bs-placement="top" data-bs-original-title="Disabled by readonly"{% endif %}>
|
||||
<label class="form-label" for="password">Current password</label>
|
||||
<div class="input-group input-group-merge">
|
||||
<input type="password"
|
||||
|
|
@ -417,13 +441,19 @@
|
|||
name="password"
|
||||
placeholder="············"
|
||||
aria-describedby="Current password"
|
||||
required />
|
||||
required
|
||||
{% if is_readonly %}disabled{% endif %} />
|
||||
<span class="input-group-text cursor-pointer"><i class="bx bx-hide"></i></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 justify-content-center d-flex">
|
||||
<button type="submit" class="btn btn-primary">Refresh Recovery Codes</button>
|
||||
<div {% if is_readonly %}data-bs-toggle="tooltip" data-bs-placement="top" data-bs-original-title="The database is in readonly, therefore the configuration is locked"{% endif %}>
|
||||
<button type="submit"
|
||||
class="btn btn-primary{% if is_readonly %} disabled{% endif %}">
|
||||
Refresh Recovery Codes
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
|
@ -444,20 +474,27 @@
|
|||
<div class="row g-3">
|
||||
<div class="col-md-12 form-password-toggle">
|
||||
<label class="form-label" for="password">Current password</label>
|
||||
<div class="input-group input-group-merge">
|
||||
<div class="input-group input-group-merge"
|
||||
{% if is_readonly %} data-bs-toggle="tooltip" data-bs-placement="top" data-bs-original-title="Disabled by readonly"{% endif %}>
|
||||
<input type="password"
|
||||
id="password"
|
||||
class="form-control"
|
||||
name="password"
|
||||
placeholder="············"
|
||||
aria-describedby="Current password"
|
||||
required />
|
||||
required
|
||||
{% if is_readonly %}disabled{% endif %} />
|
||||
<span class="input-group-text cursor-pointer"><i class="bx bx-hide"></i></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 d-flex justify-content-center">
|
||||
<button type="submit" class="btn btn-danger">Wipe other sessions</button>
|
||||
<div {% if is_readonly %}data-bs-toggle="tooltip" data-bs-placement="top" data-bs-original-title="The database is in readonly, therefore the configuration is locked"{% endif %}>
|
||||
<button type="submit"
|
||||
class="btn btn-danger{% if is_readonly %} disabled{% endif %}">
|
||||
Wipe other sessions
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@
|
|||
<a href="{{ url_for("services") }}/{{ service['id'] }}"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="bottom"
|
||||
data-bs-original-title="Edit service {{ service['id'] }}"><i class="bx bx-edit bx-xs"></i> {{ service["id"] }}</a>
|
||||
data-bs-original-title="{% if service['method'] == 'autoconf' or is_readonly %}View{% else %}Edit{% endif %} service {{ service['id'] }}"><i class="bx bx-{% if service['method'] == 'autoconf' or is_readonly %}show{% else %}edit{% endif %} bx-xs"></i> {{ service["id"] }}</a>
|
||||
</td>
|
||||
<td>
|
||||
{% if service['is_draft'] %}
|
||||
|
|
@ -78,29 +78,33 @@
|
|||
href="{{ url_for("services") }}/{{ service['id'] }}"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="bottom"
|
||||
data-bs-original-title="Edit service {{ service['id'] }}">
|
||||
<i class="bx bx-edit bx-xs"></i>
|
||||
data-bs-original-title="{% if service['method'] == 'autoconf' or is_readonly %}View{% else %}Edit{% endif %} service {{ service['id'] }}">
|
||||
<i class="bx bx-{% if service['method'] == 'autoconf' or is_readonly %}show{% else %}edit{% endif %} bx-xs"></i>
|
||||
</a>
|
||||
<a role="button"
|
||||
class="btn btn-outline-secondary btn-sm me-1"
|
||||
href="{{ url_for("services") }}/new?clone={{ service['id'] }}"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="bottom"
|
||||
data-bs-original-title="Clone service {{ service['id'] }}">
|
||||
<i class="bx bx-copy-alt bx-xs"></i>
|
||||
</a>
|
||||
<button type="button"
|
||||
class="btn btn-outline-secondary btn-sm me-1 convert-service"
|
||||
data-service-id="{{ service['id'] }}"
|
||||
data-value="{{ 'online' if service['is_draft'] else 'draft' }}"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="bottom"
|
||||
data-bs-original-title="Convert service {{ service['id'] }} to {{ 'online' if service['is_draft'] else 'draft' }}">
|
||||
<i class="bx bx-transfer bx-xs"></i>
|
||||
</button>
|
||||
<div {% if is_readonly %}data-bs-toggle="tooltip" data-bs-placement="bottom" data-bs-original-title="Disabled by readonly"{% endif %}>
|
||||
<a role="button"
|
||||
class="btn btn-outline-secondary btn-sm me-1{% if is_readonly %} disabled{% endif %}"
|
||||
href="{{ url_for("services") }}/new?clone={{ service['id'] }}"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="bottom"
|
||||
data-bs-original-title="Clone service {{ service['id'] }}">
|
||||
<i class="bx bx-copy-alt bx-xs"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div {% if is_readonly %}data-bs-toggle="tooltip" data-bs-placement="bottom" data-bs-original-title="Disabled by readonly"{% endif %}>
|
||||
<button type="button"
|
||||
class="btn btn-outline-secondary btn-sm me-1 convert-service{% if is_readonly %} disabled{% endif %}"
|
||||
data-service-id="{{ service['id'] }}"
|
||||
data-value="{{ 'online' if service['is_draft'] else 'draft' }}"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="bottom"
|
||||
data-bs-original-title="Convert service {{ service['id'] }} to {{ 'online' if service['is_draft'] else 'draft' }}">
|
||||
<i class="bx bx-transfer bx-xs"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div data-bs-toggle="tooltip"
|
||||
data-bs-placement="bottom"
|
||||
data-bs-original-title="{% if service['method'] != 'ui' %}Disabled by {{ service['method'] }}{% else %}Delete service {{ service['id'] }}{% endif %}">
|
||||
data-bs-original-title="{% if service['method'] != 'ui' or is_readonly %}Disabled by {% if is_readonly %}readonly{% else %}{{ service['method'] }}{% endif %}{% else %}Delete service {{ service['id'] }}{% endif %}">
|
||||
<button type="button"
|
||||
data-service-id="{{ service['id'] }}"
|
||||
class="btn btn-outline-danger btn-sm me-1 delete-service{% if service['method'] != 'ui' %} disabled{% endif %}">
|
||||
|
|
@ -134,76 +138,78 @@
|
|||
</div>
|
||||
<div class="toast-body">If you read this, it means that you're curious 👀</div>
|
||||
</div>
|
||||
<div class="modal fade"
|
||||
id="modal-convert-services"
|
||||
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">Confirm Conversion</h5>
|
||||
<button type="button"
|
||||
class="btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"></button>
|
||||
{% if not is_readonly %}
|
||||
<div class="modal fade"
|
||||
id="modal-convert-services"
|
||||
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">Confirm Conversion</h5>
|
||||
<button type="button"
|
||||
class="btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"></button>
|
||||
</div>
|
||||
<form action="{{ url_for("services") }}/convert" method="POST">
|
||||
<div class="modal-body">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||
<input type="hidden" id="convertion-type" name="convert_to" value="draft" />
|
||||
<input type="hidden"
|
||||
id="selected-services-input-convert"
|
||||
name="services"
|
||||
value="" />
|
||||
<div class="alert alert-danger text-center" role="alert">Are you sure you want to convert the selected services?</div>
|
||||
<div id="selected-services-convert" class="mb-3"></div>
|
||||
</div>
|
||||
<div class="modal-footer justify-content-center">
|
||||
<button type="submit" class="btn btn-outline-success me-2">Convert services</button>
|
||||
<button type="reset"
|
||||
class="btn btn-outline-secondary"
|
||||
data-bs-dismiss="modal">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<form action="{{ url_for("services") }}/convert" method="POST">
|
||||
<div class="modal-body">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||
<input type="hidden" id="convertion-type" name="convert_to" value="draft" />
|
||||
<input type="hidden"
|
||||
id="selected-services-input-convert"
|
||||
name="services"
|
||||
value="" />
|
||||
<div class="alert alert-danger text-center" role="alert">Are you sure you want to convert the selected services?</div>
|
||||
<div id="selected-services-convert" class="mb-3"></div>
|
||||
</div>
|
||||
<div class="modal-footer justify-content-center">
|
||||
<button type="submit" class="btn btn-outline-success me-2">Convert services</button>
|
||||
<button type="reset"
|
||||
class="btn btn-outline-secondary"
|
||||
data-bs-dismiss="modal">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal fade"
|
||||
id="modal-delete-services"
|
||||
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">Confirm deletion</h5>
|
||||
<button type="button"
|
||||
class="btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"></button>
|
||||
<div class="modal fade"
|
||||
id="modal-delete-services"
|
||||
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">Confirm deletion</h5>
|
||||
<button type="button"
|
||||
class="btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"></button>
|
||||
</div>
|
||||
<form action="{{ url_for("services") }}/delete" method="POST">
|
||||
<div class="modal-body">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||
<input type="hidden"
|
||||
id="selected-services-input-delete"
|
||||
name="services"
|
||||
value="" />
|
||||
<div class="alert alert-danger text-center" role="alert">Are you sure you want to delete the selected services?</div>
|
||||
<div id="selected-services-delete" class="mb-3"></div>
|
||||
</div>
|
||||
<div class="modal-footer justify-content-center">
|
||||
<button type="submit" class="btn btn-outline-danger me-2">Delete</button>
|
||||
<button type="reset"
|
||||
class="btn btn-outline-secondary"
|
||||
data-bs-dismiss="modal">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<form action="{{ url_for("services") }}/delete" method="POST">
|
||||
<div class="modal-body">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||
<input type="hidden"
|
||||
id="selected-services-input-delete"
|
||||
name="services"
|
||||
value="" />
|
||||
<div class="alert alert-danger text-center" role="alert">Are you sure you want to delete the selected services?</div>
|
||||
<div id="selected-services-delete" class="mb-3"></div>
|
||||
</div>
|
||||
<div class="modal-footer justify-content-center">
|
||||
<button type="submit" class="btn btn-outline-danger me-2">Delete</button>
|
||||
<button type="reset"
|
||||
class="btn btn-outline-secondary"
|
||||
data-bs-dismiss="modal">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<!-- / Content -->
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ def on_starting(server):
|
|||
x += 1
|
||||
TOTP_SECRETS = tmp_secrets.copy()
|
||||
del tmp_secrets
|
||||
invalid_totp_secrets = True
|
||||
invalid_totp_secrets = x == 1
|
||||
|
||||
if not TOTP_SECRETS:
|
||||
LOGGER.warning("The TOTP_SECRETS environment variable is missing, generating a random one ...")
|
||||
|
|
@ -114,14 +114,18 @@ def on_starting(server):
|
|||
ret, err = DB.init_ui_tables(BW_VERSION)
|
||||
|
||||
if not ret and err:
|
||||
LOGGER.error(f"Exception while checking database tables : {err}")
|
||||
exit(1)
|
||||
if err.startswith("The database is read-only"):
|
||||
LOGGER.warning(err)
|
||||
else:
|
||||
LOGGER.error(f"Exception while checking database tables : {err}")
|
||||
exit(1)
|
||||
elif not ret:
|
||||
LOGGER.info("Database ui tables didn't change, skipping update ...")
|
||||
else:
|
||||
LOGGER.info("Database ui tables successfully updated")
|
||||
|
||||
if not DB.get_ui_roles(as_dict=True):
|
||||
|
||||
ret = DB.create_ui_role("admin", "Admins can create new users, edit and read the data.", ["manage", "write", "read"])
|
||||
if ret:
|
||||
LOGGER.error(f"Couldn't create the admin role in the database: {ret}")
|
||||
|
|
@ -210,7 +214,15 @@ def on_starting(server):
|
|||
latest_version = latest_release["tag_name"].removeprefix("v")
|
||||
|
||||
TMP_DIR.joinpath("ui_data.json").write_text(
|
||||
dumps({"LATEST_VERSION": latest_version, "LATEST_VERSION_LAST_CHECK": datetime.now().astimezone().isoformat(), "TO_FLASH": []}), encoding="utf-8"
|
||||
dumps(
|
||||
{
|
||||
"LATEST_VERSION": latest_version,
|
||||
"LATEST_VERSION_LAST_CHECK": datetime.now().astimezone().isoformat(),
|
||||
"TO_FLASH": [],
|
||||
"READONLY_MODE": DB.readonly,
|
||||
}
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
LOGGER.info("UI is ready")
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ with app.app_context():
|
|||
|
||||
@app.context_processor
|
||||
def inject_variables():
|
||||
if request.path.startswith(("/setup", "/loading", "/login", "/totp")):
|
||||
if request.path.startswith(("/check_reloading", "/setup", "/loading", "/login", "/totp")):
|
||||
return dict(script_nonce=app.config["SCRIPT_NONCE"])
|
||||
|
||||
DATA.load_from_file()
|
||||
|
|
@ -256,7 +256,7 @@ def check_database_state():
|
|||
"LAST_DATABASE_RETRY": DB.last_connection_retry.isoformat() if DB.last_connection_retry else datetime.now().astimezone().isoformat(),
|
||||
}
|
||||
)
|
||||
elif not DATA.get("READONLY_MODE", False) and request.method == "POST" and not ("/totp" in request.path or "/login" in request.path):
|
||||
elif DB.database_uri and not DATA.get("READONLY_MODE", False) and request.method == "POST" and not ("/totp" in request.path or "/login" in request.path):
|
||||
try:
|
||||
DB.test_write()
|
||||
DATA["READONLY_MODE"] = False
|
||||
|
|
@ -293,8 +293,8 @@ def before_request():
|
|||
|
||||
DB.readonly = DATA.get("READONLY_MODE", False)
|
||||
|
||||
if DB.readonly:
|
||||
flask_flash("Database connection is in read-only mode : no modification possible.", "error")
|
||||
if not request.path.startswith(("/check_reloading", "/loading", "/login", "/totp")) and DB.readonly:
|
||||
flask_flash("Database connection is in read-only mode : no modifications possible.", "error")
|
||||
|
||||
if current_user.is_authenticated:
|
||||
passed = True
|
||||
|
|
@ -310,7 +310,7 @@ def before_request():
|
|||
elif session["user_agent"] != request.headers.get("User-Agent"):
|
||||
LOGGER.warning(f"User {current_user.get_id()} tried to access his session with a different User-Agent.")
|
||||
passed = False
|
||||
elif session["session_id"] in DATA.get("REVOKED_SESSIONS", []):
|
||||
elif "session_id" in session and session["session_id"] in DATA.get("REVOKED_SESSIONS", []):
|
||||
LOGGER.warning(f"User {current_user.get_id()} tried to access a revoked session.")
|
||||
passed = False
|
||||
|
||||
|
|
@ -319,6 +319,9 @@ def before_request():
|
|||
|
||||
|
||||
def mark_user_access(session_id):
|
||||
if DB.readonly:
|
||||
return
|
||||
|
||||
ret = DB.mark_ui_user_access(session_id, datetime.now().astimezone())
|
||||
if ret:
|
||||
LOGGER.error(f"Couldn't mark the user access: {ret}")
|
||||
|
|
|
|||
Loading…
Reference in a new issue