This commit is contained in:
Théophile Diot 2024-08-01 13:17:14 +01:00
commit d721bde76e
No known key found for this signature in database
GPG key ID: FA995104A0BA376A
23 changed files with 14509 additions and 105 deletions

View file

@ -11,20 +11,11 @@ for deps_path in [join(sep, "usr", "share", "bunkerweb", *paths) for paths in ((
from builder.utils.form import get_forms
def global_config_builder(plugins: list, settings: dict) -> str:
def global_config_builder(templates: list[dict], plugins: list, settings: dict) -> str:
"""Render forms with global config data.
ATM we don't need templates but we need to pass at least one to the function (it will simply not override anything).
"""
templates = [
{
"name": "default",
"steps": [],
"configs": {},
"settings": {},
}
]
builder = [
{
"type": "card",

View file

@ -0,0 +1,44 @@
import base64
import json
from os.path import join, sep
from sys import path as sys_path
for deps_path in [join(sep, "usr", "share", "bunkerweb", *paths) for paths in (("deps", "python"), ("utils",), ("api",), ("db",))]:
if deps_path not in sys_path:
sys_path.append(deps_path)
from builder.utils.form import get_forms, get_service_settings
def raw_mode_builder(templates: list[dict], plugins: list, global_config: dict, total_config: dict, service_name: str) -> str:
"""Render forms with global config data.
ATM we don't need templates but we need to pass at least one to the function (it will simply not override anything).
"""
# We need
settings = get_service_settings(service_name, global_config, total_config)
print("settings", settings)
builder = [
{
"type": "card",
"containerColumns": {"pc": 12, "tablet": 12, "mobile": 12},
"widgets": [
{
"type": "Title",
"data": {"title": "services_mode_title", "type": "container"},
},
{
"type": "Subtitle",
"data": {"subtitle": "services_mode_subtitle", "type": "container"},
},
{
"type": "Templates",
"data": {
"templates": get_forms(templates, plugins, settings, ("raw",)),
},
},
],
}
]
return base64.b64encode(bytes(json.dumps(builder), "utf-8")).decode("ascii")

View file

@ -2,6 +2,28 @@ import copy
from typing import Union
def get_service_settings(service_name: str, global_config: dict, total_config: dict) -> dict:
"""
total_config is a dict that contains global settings and services settings (format SERVICE_NAME_SETTING - www.example.com_USE_ANTIBOT for example -).
We will only keep settings that are related to the service_name (with prefix SERVICE_NAME_).
Then we will loop on global key and override value from global config by service config if exists.
"""
# Get service settings
service_settings = {}
for key, value in total_config.items():
if not key.startswith(f"{service_name}_"):
continue
service_settings[key.replace(f"{service_name}_", "")] = value
# Loop on global settings to override by service settings
for key, value in service_settings.items():
global_config[key] = value
return global_config
def get_forms(templates: list = [], plugins: list = [], settings: dict = {}, render_forms: tuple = ("advanced", "easy", "raw")) -> dict:
"""
Will generate every needed form using templates, plugins and settings.
@ -83,22 +105,24 @@ def set_raw(template: list, plugins_base: list, settings: dict) -> dict:
# Update settings with global config data
for plugin in plugins:
for setting, value in plugin.get("settings").items():
# avoid some methods from services_settings
if setting in settings and settings[setting].get("method", "ui") not in ("ui", "default", "manual"):
continue
raw_value = None
# Start by setting template value if exists
if setting in template_settings:
# Update value or set default as value
raw_value = template_settings.get(setting, value.get("default"))
raw_value = template_settings.get(setting, None)
# Then override by service settings
if setting in settings:
# Check if the service setting is not default value to add it
default_val = value.get("default")
val = settings[setting].get("value", value.get("value", value.get("default")))
if val != default_val:
raw_value = val

View file

@ -106,7 +106,7 @@ def move_template(folder: Path, target_folder: Path):
for file in folder.rglob("index.html"):
file_html = base_html
if "global-config" in file.parts or "jobs" in file.parts or "services" in file.parts:
if "global-config" in file.parts or "jobs" in file.parts or "services" in file.parts or "raw" in file.parts:
file_html = base_html.replace("data_server_builder[1:-1]", "data_server_builder")
content = file.read_text()

View file

@ -293,5 +293,7 @@
"services_delete_subtitle": "Are you sure you want to delete the service below ?",
"services_settings_table_title": "Get the activate setting state of main plugins for this service.",
"services_settings_table_name": "Plugin",
"services_settings_table_status": "Status"
"services_settings_table_status": "Status",
"services_mode_title": "Service mode",
"services_mode_subtitle": "Manage your service settings."
}

View file

@ -4,20 +4,11 @@ import json
from .utils.form import get_forms
def global_config_builder(plugins: list, settings: dict) -> str:
def global_config_builder(templates: list[dict], plugins: list, settings: dict) -> str:
"""Render forms with global config data.
ATM we don't need templates but we need to pass at least one to the function (it will simply not override anything).
"""
templates = [
{
"name": "default",
"steps": [],
"configs": {},
"settings": {},
}
]
builder = [
{
"type": "card",
@ -40,4 +31,4 @@ def global_config_builder(plugins: list, settings: dict) -> str:
],
}
]
return base64.b64encode(bytes(json.dumps(builder), "utf-8")).decode("ascii")
return builder

View file

@ -0,0 +1,37 @@
import base64
import json
from .utils.form import get_forms, get_service_settings
def raw_mode_builder(templates: list[dict], plugins: list, global_config: dict, total_config: dict, service_name: str) -> str:
"""Render forms with global config data.
ATM we don't need templates but we need to pass at least one to the function (it will simply not override anything).
"""
# We need
settings = get_service_settings(service_name, global_config, total_config)
print("settings", settings)
builder = [
{
"type": "card",
"containerColumns": {"pc": 12, "tablet": 12, "mobile": 12},
"widgets": [
{
"type": "Title",
"data": {"title": "services_mode_title", "type": "container"},
},
{
"type": "Subtitle",
"data": {"subtitle": "services_mode_subtitle", "type": "container"},
},
{
"type": "Templates",
"data": {
"templates": get_forms(templates, plugins, settings, ("raw",)),
},
},
],
}
]
return builder

View file

@ -2,6 +2,28 @@ import copy
from typing import Union
def get_service_settings(service_name: str, global_config: dict, total_config: dict) -> dict:
"""
total_config is a dict that contains global settings and services settings (format SERVICE_NAME_SETTING - www.example.com_USE_ANTIBOT for example -).
We will only keep settings that are related to the service_name (with prefix SERVICE_NAME_).
Then we will loop on global key and override value from global config by service config if exists.
"""
# Get service settings
service_settings = {}
for key, value in total_config.items():
if not key.startswith(f"{service_name}_"):
continue
service_settings[key.replace(f"{service_name}_", "")] = value
# Loop on global settings to override by service settings
for key, value in service_settings.items():
global_config[key] = value
return global_config
def get_forms(templates: list = [], plugins: list = [], settings: dict = {}, render_forms: tuple = ("advanced", "easy", "raw")) -> dict:
"""
Will generate every needed form using templates, plugins and settings.
@ -83,22 +105,24 @@ def set_raw(template: list, plugins_base: list, settings: dict) -> dict:
# Update settings with global config data
for plugin in plugins:
for setting, value in plugin.get("settings").items():
# avoid some methods from services_settings
if setting in settings and settings[setting].get("method", "ui") not in ("ui", "default", "manual"):
continue
raw_value = None
# Start by setting template value if exists
if setting in template_settings:
# Update value or set default as value
raw_value = template_settings.get(setting, value.get("default"))
raw_value = template_settings.get(setting, None)
# Then override by service settings
if setting in settings:
# Check if the service setting is not default value to add it
default_val = value.get("default")
val = settings[setting].get("value", value.get("value", value.get("default")))
if val != default_val:
raw_value = val

File diff suppressed because one or more lines are too long

View file

@ -3324,8 +3324,16 @@ global_config = {
"BUNKERWEB_INSTANCES": {"value": "bunkerweb", "global": True, "method": "scheduler"},
}
templates = [
{
"name": "default",
"steps": [],
"configs": {},
"settings": {},
}
]
output = global_config_builder(plugins, global_config)
output = global_config_builder(templates, plugins, global_config)
with open("globalconfig.json", "w") as f:
json.dump(output, f, indent=4)

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,47 @@
[
{
"type": "card",
"containerColumns": {
"pc": 12,
"tablet": 12,
"mobile": 12
},
"widgets": [
{
"type": "Title",
"data": {
"title": "services_mode_title",
"type": "container"
}
},
{
"type": "Subtitle",
"data": {
"subtitle": "services_mode_subtitle",
"type": "container"
}
},
{
"type": "Templates",
"data": {
"templates": {
"raw": {
"default": {
"SERVER_NAME": "app1.example.com",
"CORS_ALLOW_ORIGIN": "self",
"CROSS_ORIGIN_OPENER_POLICY": "same-origin",
"CROSS_ORIGIN_EMBEDDER_POLICY": "require-corp",
"CROSS_ORIGIN_RESOURCE_POLICY": "same-site",
"REMOVE_HEADERS": "Server Expect-CT X-Powered-By X-AspNet-Version X-AspNetMvc-Version Public-Key-Pins",
"KEEP_UPSTREAM_HEADERS": "Content-Security-Policy Permissions-Policy X-Frame-Options",
"STRICT_TRANSPORT_SECURITY": "max-age=31536000; includeSubDomains; preload",
"PERMISSIONS_POLICY": "accelerometer=(), ambient-light-sensor=(), attribution-reporting=(), autoplay=(), battery=(), bluetooth=(), browsing-topics=(), camera=(), compute-pressure=(), display-capture=(), document-domain=(), encrypted-media=(), execution-while-not-rendered=(), execution-while-out-of-viewport=(), fullscreen=(), gamepad=(), geolocation=(), gyroscope=(), hid=(), identity-credentials-get=(), idle-detection=(), local-fonts=(), magnetometer=(), microphone=(), midi=(), otp-credentials=(), payment=(), picture-in-picture=(), publickey-credentials-create=(), publickey-credentials-get=(), screen-wake-lock=(), serial=(), speaker-selection=(), storage-access=(), usb=(), web-share=(), window-management=(), xr-spatial-tracking=()",
"MODSECURITY_CRS_VERSION": "4"
}
}
}
}
}
]
}
]

4119
src/ui/client/tests/raw.py Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1 @@
W3sidHlwZSI6ICJjYXJkIiwgImNvbnRhaW5lckNvbHVtbnMiOiB7InBjIjogMTIsICJ0YWJsZXQiOiAxMiwgIm1vYmlsZSI6IDEyfSwgIndpZGdldHMiOiBbeyJ0eXBlIjogIlRpdGxlIiwgImRhdGEiOiB7InRpdGxlIjogImdsb2JhbF9jb25maWdfdGl0bGUiLCAidHlwZSI6ICJjb250YWluZXIifX0sIHsidHlwZSI6ICJTdWJ0aXRsZSIsICJkYXRhIjogeyJzdWJ0aXRsZSI6ICJnbG9iYWxfY29uZmlnX3N1YnRpdGxlIiwgInR5cGUiOiAiY29udGFpbmVyIn19LCB7InR5cGUiOiAiVGVtcGxhdGVzIiwgImRhdGEiOiB7InRlbXBsYXRlcyI6IHsicmF3IjogeyJkZWZhdWx0IjogeyJTRVJWRVJfTkFNRSI6ICJhcHAxLmV4YW1wbGUuY29tIiwgIkNPUlNfQUxMT1dfT1JJR0lOIjogInNlbGYiLCAiQ1JPU1NfT1JJR0lOX09QRU5FUl9QT0xJQ1kiOiAic2FtZS1vcmlnaW4iLCAiQ1JPU1NfT1JJR0lOX0VNQkVEREVSX1BPTElDWSI6ICJyZXF1aXJlLWNvcnAiLCAiQ1JPU1NfT1JJR0lOX1JFU09VUkNFX1BPTElDWSI6ICJzYW1lLXNpdGUiLCAiUkVNT1ZFX0hFQURFUlMiOiAiU2VydmVyIEV4cGVjdC1DVCBYLVBvd2VyZWQtQnkgWC1Bc3BOZXQtVmVyc2lvbiBYLUFzcE5ldE12Yy1WZXJzaW9uIFB1YmxpYy1LZXktUGlucyIsICJLRUVQX1VQU1RSRUFNX0hFQURFUlMiOiAiQ29udGVudC1TZWN1cml0eS1Qb2xpY3kgUGVybWlzc2lvbnMtUG9saWN5IFgtRnJhbWUtT3B0aW9ucyIsICJTVFJJQ1RfVFJBTlNQT1JUX1NFQ1VSSVRZIjogIm1heC1hZ2U9MzE1MzYwMDA7IGluY2x1ZGVTdWJEb21haW5zOyBwcmVsb2FkIiwgIlBFUk1JU1NJT05TX1BPTElDWSI6ICJhY2NlbGVyb21ldGVyPSgpLCBhbWJpZW50LWxpZ2h0LXNlbnNvcj0oKSwgYXR0cmlidXRpb24tcmVwb3J0aW5nPSgpLCBhdXRvcGxheT0oKSwgYmF0dGVyeT0oKSwgYmx1ZXRvb3RoPSgpLCBicm93c2luZy10b3BpY3M9KCksIGNhbWVyYT0oKSwgY29tcHV0ZS1wcmVzc3VyZT0oKSwgZGlzcGxheS1jYXB0dXJlPSgpLCBkb2N1bWVudC1kb21haW49KCksIGVuY3J5cHRlZC1tZWRpYT0oKSwgZXhlY3V0aW9uLXdoaWxlLW5vdC1yZW5kZXJlZD0oKSwgZXhlY3V0aW9uLXdoaWxlLW91dC1vZi12aWV3cG9ydD0oKSwgZnVsbHNjcmVlbj0oKSwgZ2FtZXBhZD0oKSwgZ2VvbG9jYXRpb249KCksIGd5cm9zY29wZT0oKSwgaGlkPSgpLCBpZGVudGl0eS1jcmVkZW50aWFscy1nZXQ9KCksIGlkbGUtZGV0ZWN0aW9uPSgpLCBsb2NhbC1mb250cz0oKSwgbWFnbmV0b21ldGVyPSgpLCBtaWNyb3Bob25lPSgpLCBtaWRpPSgpLCBvdHAtY3JlZGVudGlhbHM9KCksIHBheW1lbnQ9KCksIHBpY3R1cmUtaW4tcGljdHVyZT0oKSwgcHVibGlja2V5LWNyZWRlbnRpYWxzLWNyZWF0ZT0oKSwgcHVibGlja2V5LWNyZWRlbnRpYWxzLWdldD0oKSwgc2NyZWVuLXdha2UtbG9jaz0oKSwgc2VyaWFsPSgpLCBzcGVha2VyLXNlbGVjdGlvbj0oKSwgc3RvcmFnZS1hY2Nlc3M9KCksIHVzYj0oKSwgd2ViLXNoYXJlPSgpLCB3aW5kb3ctbWFuYWdlbWVudD0oKSwgeHItc3BhdGlhbC10cmFja2luZz0oKSIsICJNT0RTRUNVUklUWV9DUlNfVkVSU0lPTiI6ICI0In19fX19XX1d

View file

@ -0,0 +1 @@
W3sidHlwZSI6ICJjYXJkIiwgImNvbnRhaW5lckNvbHVtbnMiOiB7InBjIjogMTIsICJ0YWJsZXQiOiAxMiwgIm1vYmlsZSI6IDEyfSwgIndpZGdldHMiOiBbeyJ0eXBlIjogIlRpdGxlIiwgImRhdGEiOiB7InRpdGxlIjogImdsb2JhbF9jb25maWdfdGl0bGUiLCAidHlwZSI6ICJjb250YWluZXIifX0sIHsidHlwZSI6ICJTdWJ0aXRsZSIsICJkYXRhIjogeyJzdWJ0aXRsZSI6ICJnbG9iYWxfY29uZmlnX3N1YnRpdGxlIiwgInR5cGUiOiAiY29udGFpbmVyIn19LCB7InR5cGUiOiAiVGVtcGxhdGVzIiwgImRhdGEiOiB7InRlbXBsYXRlcyI6IHsiciI6IHt9LCAiYSI6IHt9LCAidyI6IHt9fX19XX1d

View file

@ -51,6 +51,7 @@ export default defineConfig({
),
jobs: resolve(__dirname, "./dashboard/pages/jobs/index.html"),
services: resolve(__dirname, "./dashboard/pages/services/index.html"),
raw: resolve(__dirname, "./dashboard/pages/raw/index.html"),
},
},
},

View file

@ -57,6 +57,7 @@ from builder.instances import instances_builder
from builder.global_config import global_config_builder
from builder.jobs import jobs_builder
from builder.services import services_builder
from builder.raw_mode import raw_mode_builder
from common_utils import get_version # type: ignore
from logger import setup_logger # type: ignore
@ -98,6 +99,17 @@ signal(SIGTERM, handle_stop)
sbin_nginx_path = Path(sep, "usr", "sbin", "nginx")
TEMPLATE_PLACEHOLDER = [
{
"name": "default",
"steps": [],
"configs": {},
"settings": {},
}
]
# Flask app
app = Flask(__name__, static_url_path="/", static_folder="static", template_folder="templates")
@ -1191,55 +1203,41 @@ def get_service_data():
return config, variables, format_configs, server_name, old_server_name, operation, is_draft, was_draft, is_draft_unchanged
@app.route("/services", methods=["GET", "POST"])
@login_required
def services():
if request.method == "POST":
if DB.readonly:
return handle_error("Database is in read-only mode", "services")
def update_service(config, variables, format_configs, server_name, old_server_name, operation, is_draft, was_draft, is_draft_unchanged, redirect_name):
if request.form["operation"] == "edit":
if is_draft_unchanged and len(variables) == 1 and "SERVER_NAME" in variables and server_name == old_server_name:
return handle_error("The service was not edited because no values were changed.", "services", True)
verify_data_in_form(
data={"operation": ("edit", "new", "delete")},
err_message="Invalid operation parameter on /services.",
redirect_url="services",
if request.form["operation"] == "new" and not variables:
return handle_error("The service was not created because all values had the default value.", "services", True)
# Delete
if request.form["operation"] == "delete":
is_service = app.bw_config.check_variables({"SERVER_NAME": request.form["SERVER_NAME"]}, config)
if not is_service:
error_message(f"Error while deleting the service {request.form['SERVER_NAME']}")
if config.get(f"{request.form['SERVER_NAME'].split(' ')[0]}_SERVER_NAME", {"method": "scheduler"})["method"] != "ui":
return handle_error("The service cannot be deleted because it has not been created with the UI.", "services", True)
db_metadata = DB.get_metadata()
def update_services(threaded: bool = False):
wait_applying()
manage_bunkerweb(
"services",
variables,
old_server_name,
variables.get("SERVER_NAME", ""),
operation=operation,
is_draft=is_draft,
was_draft=was_draft,
threaded=threaded,
)
config, variables, format_configs, server_name, old_server_name, operation, is_draft, was_draft, is_draft_unchanged = get_service_data()
if request.form["operation"] == "edit":
if is_draft_unchanged and len(variables) == 1 and "SERVER_NAME" in variables and server_name == old_server_name:
return handle_error("The service was not edited because no values were changed.", "services", True)
if request.form["operation"] == "new" and not variables:
return handle_error("The service was not created because all values had the default value.", "services", True)
# Delete
if request.form["operation"] == "delete":
is_service = app.bw_config.check_variables({"SERVER_NAME": request.form["SERVER_NAME"]}, config)
if not is_service:
error_message(f"Error while deleting the service {request.form['SERVER_NAME']}")
if config.get(f"{request.form['SERVER_NAME'].split(' ')[0]}_SERVER_NAME", {"method": "scheduler"})["method"] != "ui":
return handle_error("The service cannot be deleted because it has not been created with the UI.", "services", True)
db_metadata = DB.get_metadata()
def update_services(threaded: bool = False):
wait_applying()
manage_bunkerweb(
"services",
variables,
old_server_name,
variables.get("SERVER_NAME", ""),
operation=operation,
is_draft=is_draft,
was_draft=was_draft,
threaded=threaded,
)
if any(
v
for k, v in db_metadata.items()
@ -1253,16 +1251,69 @@ def services():
app.data["CONFIG_CHANGED"] = True
message = ""
message = ""
if request.form["operation"] == "new":
message = f"Creating {'draft ' if is_draft else ''}service {variables.get('SERVER_NAME', '').split(' ')[0]}"
elif request.form["operation"] == "edit":
message = f"Saving configuration for {'draft ' if is_draft else ''}service {old_server_name.split(' ')[0]}"
elif request.form["operation"] == "delete":
message = f"Deleting {'draft ' if was_draft and is_draft else ''}service {request.form.get('SERVER_NAME', '').split(' ')[0]}"
if request.form["operation"] == "new":
message = f"Creating {'draft ' if is_draft else ''}service {variables.get('SERVER_NAME', '').split(' ')[0]}"
elif request.form["operation"] == "edit":
message = f"Saving configuration for {'draft ' if is_draft else ''}service {old_server_name.split(' ')[0]}"
elif request.form["operation"] == "delete":
message = f"Deleting {'draft ' if was_draft and is_draft else ''}service {request.form.get('SERVER_NAME', '').split(' ')[0]}"
return redirect(url_for("loading", next=url_for("services"), message=message))
return redirect(url_for("loading", next=url_for(redirect_name, service_name=[server_name]), message=message))
@app.route("/raw-mode", methods=["GET", "POST"])
@login_required
def services_raw():
if request.method == "POST":
if DB.readonly:
return handle_error("Database is in read-only mode", "services")
verify_data_in_form(
data={"operation": ("edit", "new", "delete")},
err_message="Invalid operation parameter on /services.",
redirect_url="services",
)
config, variables, format_configs, server_name, old_server_name, operation, is_draft, was_draft, is_draft_unchanged = get_service_data()
update_service(config, variables, format_configs, server_name, old_server_name, operation, is_draft, was_draft, is_draft_unchanged, "raw-mode")
if not request.args.get("service_name"):
return handle_error("Service name missing to access raw mode.", "services")
service_name = request.args.get("service_name")
total_config = DB.get_config(methods=True, with_drafts=True)
service_names = total_config["SERVER_NAME"]["value"].split(" ")
# Case new service
service_names.append("new")
if service_name not in service_names:
return handle_error("Service name not found to access raw mode.", "services")
global_config = app.bw_config.get_config(global_only=True, methods=True)
plugins = app.bw_config.get_plugins()
data_server_builder = raw_mode_builder(TEMPLATE_PLACEHOLDER, plugins, global_config, total_config, service_name)
return render_template("raw.html", data_server_builder=data_server_builder)
@app.route("/services", methods=["GET", "POST"])
@login_required
def services():
if request.method == "POST":
if DB.readonly:
return handle_error("Database is in read-only mode", "services")
verify_data_in_form(
data={"operation": ("edit", "new", "delete")},
err_message="Invalid operation parameter on /services.",
redirect_url="services",
)
config, variables, format_configs, server_name, old_server_name, operation, is_draft, was_draft, is_draft_unchanged = get_service_data()
update_service(config, variables, format_configs, server_name, old_server_name, operation, is_draft, was_draft, is_draft_unchanged, "services")
# Display services
services = []
@ -1304,7 +1355,7 @@ def services():
@app.route("/services/raw/{service_name}", methods=["GET", "POST"])
@login_required
def services_raw(service_name: str):
def services_raw(service_name: str): # noqa: F811
if request.method == "POST":
if DB.readonly:
return handle_error("Database is in read-only mode", "services")
@ -1490,9 +1541,9 @@ def global_config():
)
)
global_config = DB.get_config(global_only=True, methods=True)
global_config = app.bw_config.get_config(global_only=True, methods=True)
plugins = app.bw_config.get_plugins()
data_server_builder = global_config_builder(plugins, global_config)
data_server_builder = global_config_builder(TEMPLATE_PLACEHOLDER, plugins, global_config)
return render_template("global-config.html", data_server_builder=data_server_builder)

View file

@ -7,10 +7,13 @@
<link rel="stylesheet" href="css/flag-icons.min.css" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>BunkerWeb | Global config</title>
<script type="module" crossorigin nonce="{{ script_nonce }}" src="assets/global_config-_p4bx2iA.js"></script>
<link rel="modulepreload" crossorigin nonce="{{ script_nonce }}" href="assets/Text-BvrU_MzZ.js">
<link rel="modulepreload" crossorigin nonce="{{ script_nonce }}" href="assets/ButtonGroup-Bmy1qIwo.js">
<link rel="stylesheet" crossorigin href="assets/ButtonGroup-D2kv0NCW.css">
<script type="module" crossorigin nonce="{{ script_nonce }}" src="assets/global_config-CYnbkMMn.js"></script>
<link rel="modulepreload" crossorigin nonce="{{ script_nonce }}" href="assets/Layout-RaTf2L6T.js">
<link rel="modulepreload" crossorigin nonce="{{ script_nonce }}" href="assets/Text-xpxpyVZa.js">
<link rel="modulepreload" crossorigin nonce="{{ script_nonce }}" href="assets/theme-dawn-Bo1pPESL.js">
<link rel="modulepreload" crossorigin nonce="{{ script_nonce }}" href="assets/ButtonGroup-CClxClGv.js">
<link rel="modulepreload" crossorigin nonce="{{ script_nonce }}" href="assets/form-j3jOuvnO.js">
<link rel="stylesheet" crossorigin href="assets/theme-dawn-D2kv0NCW.css">
</head>
<body>

View file

@ -7,8 +7,9 @@
<link rel="stylesheet" href="css/flag-icons.min.css" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>BunkerWeb | Home</title>
<script type="module" crossorigin nonce="{{ script_nonce }}" src="assets/home-C5vSVPv_.js"></script>
<link rel="modulepreload" crossorigin nonce="{{ script_nonce }}" href="assets/Text-BvrU_MzZ.js">
<script type="module" crossorigin nonce="{{ script_nonce }}" src="assets/home-C2BYy_dW.js"></script>
<link rel="modulepreload" crossorigin nonce="{{ script_nonce }}" href="assets/Layout-RaTf2L6T.js">
<link rel="modulepreload" crossorigin nonce="{{ script_nonce }}" href="assets/Text-xpxpyVZa.js">
</head>
<body>

View file

@ -7,10 +7,12 @@
<link rel="stylesheet" href="css/flag-icons.min.css" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>BunkerWeb | Instances</title>
<script type="module" crossorigin nonce="{{ script_nonce }}" src="assets/instances-DlYdXYfk.js"></script>
<link rel="modulepreload" crossorigin nonce="{{ script_nonce }}" href="assets/Text-BvrU_MzZ.js">
<link rel="modulepreload" crossorigin nonce="{{ script_nonce }}" href="assets/ButtonGroup-Bmy1qIwo.js">
<link rel="stylesheet" crossorigin href="assets/ButtonGroup-D2kv0NCW.css">
<script type="module" crossorigin nonce="{{ script_nonce }}" src="assets/instances-W4YarpdG.js"></script>
<link rel="modulepreload" crossorigin nonce="{{ script_nonce }}" href="assets/Layout-RaTf2L6T.js">
<link rel="modulepreload" crossorigin nonce="{{ script_nonce }}" href="assets/Text-xpxpyVZa.js">
<link rel="modulepreload" crossorigin nonce="{{ script_nonce }}" href="assets/theme-dawn-Bo1pPESL.js">
<link rel="modulepreload" crossorigin nonce="{{ script_nonce }}" href="assets/ButtonGroup-CClxClGv.js">
<link rel="stylesheet" crossorigin href="assets/theme-dawn-D2kv0NCW.css">
</head>
<body>

View file

@ -7,10 +7,12 @@
<link rel="stylesheet" href="css/flag-icons.min.css" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>BunkerWeb | Jobs</title>
<script type="module" crossorigin nonce="{{ script_nonce }}" src="assets/jobs-DsETM5wn.js"></script>
<link rel="modulepreload" crossorigin nonce="{{ script_nonce }}" href="assets/Text-BvrU_MzZ.js">
<link rel="modulepreload" crossorigin nonce="{{ script_nonce }}" href="assets/ButtonGroup-Bmy1qIwo.js">
<link rel="stylesheet" crossorigin href="assets/ButtonGroup-D2kv0NCW.css">
<script type="module" crossorigin nonce="{{ script_nonce }}" src="assets/jobs-DR0hphyk.js"></script>
<link rel="modulepreload" crossorigin nonce="{{ script_nonce }}" href="assets/Layout-RaTf2L6T.js">
<link rel="modulepreload" crossorigin nonce="{{ script_nonce }}" href="assets/Text-xpxpyVZa.js">
<link rel="modulepreload" crossorigin nonce="{{ script_nonce }}" href="assets/theme-dawn-Bo1pPESL.js">
<link rel="modulepreload" crossorigin nonce="{{ script_nonce }}" href="assets/ButtonGroup-CClxClGv.js">
<link rel="stylesheet" crossorigin href="assets/theme-dawn-D2kv0NCW.css">
</head>
<body>

30
src/ui/templates/raw.html vendored Normal file
View file

@ -0,0 +1,30 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/x-icon" href="img/favicon.ico" />
<link rel="stylesheet" href="css/style.css" />
<link rel="stylesheet" href="css/flag-icons.min.css" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>BunkerWeb | Raw mode</title>
<script type="module" crossorigin nonce="{{ script_nonce }}" src="assets/raw-SXGFOvXL.js"></script>
<link rel="modulepreload" crossorigin nonce="{{ script_nonce }}" href="assets/Layout-RaTf2L6T.js">
<link rel="modulepreload" crossorigin nonce="{{ script_nonce }}" href="assets/theme-dawn-Bo1pPESL.js">
<link rel="modulepreload" crossorigin nonce="{{ script_nonce }}" href="assets/form-j3jOuvnO.js">
<link rel="stylesheet" crossorigin href="assets/theme-dawn-D2kv0NCW.css">
</head>
<body>
{% set data_server_flash = [] %}
{% with messages = get_flashed_messages(with_categories=true) %}
{% for category, message in messages %}
{% if data_server_flash.append({"type": "error" if category == "error" else "success", "title": "dashboard_error" if category == "error" else "dashboard_success", "message": message}) %}{% endif %}
{% endfor %}
{% endwith %}
<div class='hidden' data-csrf-token='{{ csrf_token() }}'></div>
<div class='hidden' data-server-global='{{data_server_global if data_server_global else {}}}'></div>
<div class='hidden' data-server-flash='{{data_server_flash|tojson}}'></div>
<div class='hidden' data-server-builder='{{data_server_builder}}'></div>
<div id='app'></div>
</body>
</html>

View file

@ -7,10 +7,12 @@
<link rel="stylesheet" href="css/flag-icons.min.css" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>BunkerWeb | Services</title>
<script type="module" crossorigin nonce="{{ script_nonce }}" src="assets/services-CSE-2AMK.js"></script>
<link rel="modulepreload" crossorigin nonce="{{ script_nonce }}" href="assets/Text-BvrU_MzZ.js">
<link rel="modulepreload" crossorigin nonce="{{ script_nonce }}" href="assets/ButtonGroup-Bmy1qIwo.js">
<link rel="stylesheet" crossorigin href="assets/ButtonGroup-D2kv0NCW.css">
<script type="module" crossorigin nonce="{{ script_nonce }}" src="assets/services-B2ZXRax3.js"></script>
<link rel="modulepreload" crossorigin nonce="{{ script_nonce }}" href="assets/Layout-RaTf2L6T.js">
<link rel="modulepreload" crossorigin nonce="{{ script_nonce }}" href="assets/Text-xpxpyVZa.js">
<link rel="modulepreload" crossorigin nonce="{{ script_nonce }}" href="assets/theme-dawn-Bo1pPESL.js">
<link rel="modulepreload" crossorigin nonce="{{ script_nonce }}" href="assets/ButtonGroup-CClxClGv.js">
<link rel="stylesheet" crossorigin href="assets/theme-dawn-D2kv0NCW.css">
</head>
<body>