mirror of
https://github.com/bunkerity/bunkerized-nginx
synced 2023-12-13 21:30:18 +01:00
623 lines
20 KiB
Python
623 lines
20 KiB
Python
from base64 import b64encode
|
|
from datetime import datetime
|
|
from typing import Union
|
|
from bs4 import BeautifulSoup, Tag
|
|
import magic
|
|
import json
|
|
import os
|
|
import re
|
|
|
|
|
|
def get_variables():
|
|
vars = {}
|
|
vars["DOCKER_HOST"] = "unix:///var/run/docker.sock"
|
|
vars["ABSOLUTE_URI"] = ""
|
|
vars["FLASK_SECRET"] = os.urandom(32)
|
|
vars["FLASK_ENV"] = "development"
|
|
vars["ADMIN_USERNAME"] = "admin"
|
|
vars["ADMIN_PASSWORD"] = "changeme"
|
|
|
|
for k in vars:
|
|
if k in os.environ:
|
|
vars[k] = os.environ[k]
|
|
|
|
return vars
|
|
|
|
|
|
def log(event):
|
|
with open("/var/log/nginx/ui.log", "a") as f:
|
|
f.write("[" + str(datetime.now().replace(microsecond=0)) + "] " + event + "\n")
|
|
|
|
|
|
def env_to_summary_class(var, value):
|
|
if type(var) is list and type(value) is list:
|
|
for i in range(0, len(var)):
|
|
if not isinstance(var[i], str):
|
|
continue
|
|
if re.search(value[i], var[i]):
|
|
return "check text-success"
|
|
return "times text-danger"
|
|
if not isinstance(var, str):
|
|
return "times text-danger"
|
|
if re.search(value, var):
|
|
return "check text-success"
|
|
return "times text-danger"
|
|
|
|
|
|
def form_service_gen(
|
|
_id,
|
|
_help,
|
|
label,
|
|
_type,
|
|
value,
|
|
name,
|
|
default,
|
|
selects,
|
|
regex,
|
|
from_html: bool = True,
|
|
root: Tag = None,
|
|
) -> str:
|
|
if root is None:
|
|
root = BeautifulSoup()
|
|
|
|
tooltip = Tag(
|
|
name="div",
|
|
attrs={
|
|
"class": "px-2 d-sm-inline",
|
|
"data-bs-toggle": "tooltip",
|
|
"data-bs-placement": "bottom",
|
|
"title": _help,
|
|
},
|
|
)
|
|
tooltip.append(Tag(name="i", attrs={"class": "fas fa-question-circle"}))
|
|
root.append(tooltip)
|
|
pt = ""
|
|
invalid_div = None
|
|
|
|
if _type in ("text", "number"):
|
|
edit = Tag(
|
|
name="input",
|
|
attrs={
|
|
"type": _type,
|
|
"class": "form-control",
|
|
"id": _id,
|
|
"name": name,
|
|
"value": value if value else default,
|
|
"pattern": regex,
|
|
},
|
|
)
|
|
|
|
invalid_div = Tag(name="div", attrs={"class": "invalid-feedback"})
|
|
invalid_div.append(
|
|
BeautifulSoup(
|
|
f"{label} is invalid and must match this pattern: <i><b>{regex}</b></i>",
|
|
"html.parser",
|
|
)
|
|
)
|
|
elif _type == "check":
|
|
edit = Tag(
|
|
name="div",
|
|
attrs={"class": "form-check form-switch"},
|
|
)
|
|
edit.append(
|
|
BeautifulSoup(
|
|
f"<input type='checkbox' role='switch' class='form-check-input' id='{_id}' name='{name}' {'checked' if value and value == 'yes' or not value and default and default == 'yes' else ''}>",
|
|
"html.parser",
|
|
)
|
|
)
|
|
edit.append(
|
|
Tag(
|
|
name="input",
|
|
attrs={
|
|
"type": "hidden",
|
|
"id": f"{_id}-hidden",
|
|
"name": name,
|
|
"value": "off",
|
|
},
|
|
)
|
|
)
|
|
pt = "pt-0"
|
|
elif _type == "select":
|
|
edit = Tag(
|
|
name="select",
|
|
attrs={
|
|
"type": "form-select",
|
|
"class": "form-control form-select",
|
|
"id": _id,
|
|
"name": name,
|
|
},
|
|
)
|
|
|
|
for select in selects:
|
|
edit.append(
|
|
BeautifulSoup(
|
|
f"<option value='{select}' {'selected' if value and select == value or not value and select == default else ''}>{select}</option>",
|
|
"html.parser",
|
|
)
|
|
)
|
|
|
|
label_tag = Tag(
|
|
name="label", attrs={"for": _id, "class": f"flex-grow-1 d-sm-inline {pt}"}
|
|
)
|
|
label_tag.append(label)
|
|
root.append(label_tag)
|
|
|
|
div = Tag(name="div", attrs={"class": "d-sm-inline"})
|
|
div.append(edit)
|
|
|
|
if invalid_div:
|
|
div.append(invalid_div)
|
|
|
|
root.append(div)
|
|
|
|
if from_html:
|
|
return root.prettify()
|
|
else:
|
|
return root
|
|
|
|
|
|
def form_service_gen_multiple(_id, label, params, root) -> Tag:
|
|
label_tag = Tag(
|
|
name="label",
|
|
attrs={"for": f"{_id}-btn", "class": "flex-grow-1 d-sm-inline"},
|
|
)
|
|
label_tag.append(label)
|
|
root.append(label_tag)
|
|
|
|
buttons = Tag(name="div", attrs={"class": "d-sm-inline", "id": _id})
|
|
|
|
add_button = Tag(
|
|
name="button",
|
|
attrs={
|
|
"class": "btn btn-outline-success",
|
|
"type": "button",
|
|
"onClick": f"addMultiple('{_id}', '{json.dumps(params)}');",
|
|
},
|
|
)
|
|
add_button.append(Tag(name="i", attrs={"class": "fas fa-plus"}))
|
|
add_button.append(" Add")
|
|
buttons.append(add_button)
|
|
|
|
del_button = Tag(
|
|
name="button",
|
|
attrs={
|
|
"class": "btn btn-outline-danger",
|
|
"type": "button",
|
|
"onClick": f"delMultiple('{_id}', '{json.dumps(params)}');",
|
|
},
|
|
)
|
|
del_button.append(Tag(name="i", attrs={"class": "fas fa-trash"}))
|
|
del_button.append(" Delete")
|
|
buttons.append(del_button)
|
|
|
|
root.append(buttons)
|
|
return root
|
|
|
|
|
|
def form_service_gen_multiple_values(_id, params, service) -> Union[Tag, str]:
|
|
script = Tag(name="script", attrs={"defer": True})
|
|
values = []
|
|
for env in sorted(
|
|
service,
|
|
reverse=True,
|
|
key=lambda x: int(re.search(r"\d+$", x).group()) if x[-1].isdigit() else 0,
|
|
):
|
|
for param, param_value in params.items():
|
|
suffix = env.replace(param, "")
|
|
if env.startswith(param):
|
|
values.append(
|
|
{
|
|
"default": service.get(
|
|
f"{param}{suffix}", param_value["default"]
|
|
),
|
|
"env": param,
|
|
"help": param_value["help"],
|
|
"id": param_value["id"],
|
|
"label": param_value["label"],
|
|
"selects": param_value.get("selects", []),
|
|
"type": param_value["type"],
|
|
}
|
|
)
|
|
|
|
if len(values) >= len(params):
|
|
script.append(f"addMultiple('{_id}', '{json.dumps(values)}');")
|
|
values = []
|
|
|
|
return script if script.children else ""
|
|
|
|
|
|
def form_plugin_gen(
|
|
service: dict,
|
|
plugin: dict,
|
|
action: str,
|
|
id_server_name: str = None,
|
|
*,
|
|
context: str = "multisite",
|
|
) -> str:
|
|
soup = BeautifulSoup()
|
|
soup.append(
|
|
Tag(
|
|
name="div",
|
|
attrs={
|
|
"class": "tab-pane fade",
|
|
"id": f"{action}-{plugin['id']}"
|
|
+ (f"-{id_server_name}" if id_server_name else ""),
|
|
"role": "tabpanel",
|
|
"aria-labelledby": f"{action}-{plugin['id']}"
|
|
+ (f"-{id_server_name}" if id_server_name else "")
|
|
+ "-tab",
|
|
},
|
|
)
|
|
)
|
|
multiple_plugins = {}
|
|
multiple = None
|
|
|
|
for setting, value in plugin["settings"].items():
|
|
if value["context"] == context:
|
|
if "multiple" in value:
|
|
if multiple != value["multiple"]:
|
|
multiple = value["multiple"]
|
|
multiple_plugins[multiple] = {}
|
|
multiple_plugins[multiple][setting] = value
|
|
multiple_plugins[multiple][setting]["env"] = setting
|
|
else:
|
|
div = Tag(
|
|
name="div",
|
|
attrs={
|
|
"class": "d-flex flex-row justify-content-between align-items-center mb-3",
|
|
"id": f"form-{action}"
|
|
+ (f"-{id_server_name}" if id_server_name else "")
|
|
+ f"-{value['id']}",
|
|
},
|
|
)
|
|
|
|
div = form_service_gen(
|
|
f"form-{action}"
|
|
+ (f"-{id_server_name}" if id_server_name else "")
|
|
+ f"-{value['id']}",
|
|
value["help"],
|
|
value["label"],
|
|
value["type"],
|
|
service.get(setting, value["default"]),
|
|
setting,
|
|
value["default"],
|
|
value.get("select", None),
|
|
value["regex"],
|
|
False,
|
|
div,
|
|
)
|
|
|
|
soup.div.append(div)
|
|
|
|
for multiple_plugin in multiple_plugins:
|
|
div = Tag(
|
|
name="div",
|
|
attrs={
|
|
"class": "d-flex flex-row justify-content-between align-items-center mb-3",
|
|
"id": f"form-{action}"
|
|
+ (f"-{id_server_name}" if id_server_name else "")
|
|
+ f"-{multiple_plugin}",
|
|
},
|
|
)
|
|
|
|
div = form_service_gen_multiple(
|
|
f"form-{action}"
|
|
+ (f"-{id_server_name}" if id_server_name else "")
|
|
+ f"-{multiple_plugin}",
|
|
plugin["name"],
|
|
multiple_plugins[multiple_plugin],
|
|
div,
|
|
)
|
|
if service:
|
|
div.append(
|
|
form_service_gen_multiple_values(
|
|
f"form-{action}"
|
|
+ (f"-{id_server_name}" if id_server_name else "")
|
|
+ f"-{multiple_plugin}",
|
|
multiple_plugins[multiple_plugin],
|
|
service,
|
|
)
|
|
)
|
|
|
|
soup.div.append(div)
|
|
|
|
return soup.prettify()
|
|
|
|
|
|
def path_to_dict(path, *, level: int = 0, is_cache: bool = False) -> dict:
|
|
d = {"name": os.path.basename(path)}
|
|
|
|
if os.path.isdir(path):
|
|
d.update(
|
|
{
|
|
"type": "folder",
|
|
"path": path,
|
|
"can_create_files": level > 0 and not is_cache,
|
|
"can_create_folders": level > 0 and not is_cache,
|
|
"can_edit": level > 1 and not is_cache,
|
|
"can_delete": level > 1 and not is_cache,
|
|
"children": [
|
|
path_to_dict(
|
|
os.path.join(path, x), level=level + 1, is_cache=is_cache
|
|
)
|
|
for x in sorted(os.listdir(path))
|
|
],
|
|
}
|
|
)
|
|
else:
|
|
d.update(
|
|
{
|
|
"type": "file",
|
|
"path": path,
|
|
"can_edit": level > 1 and not is_cache,
|
|
"can_download": is_cache,
|
|
}
|
|
)
|
|
|
|
magic_file = magic.from_file(path, mime=True)
|
|
|
|
if (
|
|
not is_cache
|
|
or magic_file.startswith("text/")
|
|
or magic_file.startswith("application/json")
|
|
):
|
|
with open(path, "rb") as f:
|
|
d["content"] = b64encode(f.read()).decode("utf-8")
|
|
|
|
return d
|
|
|
|
|
|
options = {
|
|
"can_edit": {
|
|
"button_style": "btn-outline-primary",
|
|
"data-bs-target": "#modal-edit-new-folder",
|
|
"icon": "fa-solid fa-pen-to-square",
|
|
},
|
|
"can_delete": {
|
|
"button_style": "btn-outline-danger",
|
|
"data-bs-target": "#modal-delete",
|
|
"icon": "fa-solid fa-trash-can",
|
|
},
|
|
"can_create_folders": {
|
|
"button_style": "btn-outline-success",
|
|
"data-bs-target": "#modal-edit-new-folder",
|
|
"icon": "fa-solid fa-folder",
|
|
},
|
|
"can_create_files": {
|
|
"button_style": "btn-outline-success",
|
|
"data-bs-target": "#modal-edit-new-file",
|
|
"icon": "fa-solid fa-file",
|
|
},
|
|
}
|
|
|
|
|
|
def gen_folders_tree_html(data: list) -> str:
|
|
html = "<ul class='folder-container'>"
|
|
|
|
for _dict in sorted(data, key=lambda x: x["type"] == "file", reverse=True):
|
|
path_id = _dict["path"].replace("/", "-")[1:]
|
|
root_li = Tag(
|
|
name="li",
|
|
attrs={
|
|
"class": f"{_dict['type']}-item d-flex align-items-center",
|
|
},
|
|
)
|
|
|
|
if _dict["type"] == "folder":
|
|
root_div = Tag(
|
|
name="div",
|
|
attrs={
|
|
"class": "collapse-div",
|
|
"data-bs-toggle": "collapse",
|
|
"href": f"#{path_id}",
|
|
"role": "button",
|
|
"aria-expanded": "true",
|
|
"aria-controls": path_id,
|
|
},
|
|
)
|
|
|
|
h6 = Tag(name="span", attrs={"class": "mb-0 pl-2"})
|
|
h6.append(_dict["name"])
|
|
|
|
if _dict.get("children", False):
|
|
root_div.append(
|
|
Tag(
|
|
name="span",
|
|
attrs={"class": f"fa-solid fa-play fa-xs rotate-icon down"},
|
|
)
|
|
)
|
|
|
|
root_div.append(h6)
|
|
|
|
root_li.append(root_div)
|
|
|
|
if any(
|
|
[
|
|
_dict["can_create_files"],
|
|
_dict["can_create_folders"],
|
|
_dict["can_edit"],
|
|
_dict["can_delete"],
|
|
]
|
|
):
|
|
div = Tag(
|
|
name="div", attrs={"class": "ms-2 d-sm-flex align-items-between"}
|
|
)
|
|
dropdown = None
|
|
|
|
if any(
|
|
[
|
|
_dict["can_create_files"],
|
|
_dict["can_create_folders"],
|
|
]
|
|
):
|
|
dropdown = Tag(name="div", attrs={"class": "badge dropdown"})
|
|
button = Tag(
|
|
name="button",
|
|
attrs={
|
|
"type": "button",
|
|
"class": "btn btn-outline-success",
|
|
"id": "dropdownSettingsButton",
|
|
"data-bs-toggle": "dropdown",
|
|
"aria-expanded": "false",
|
|
},
|
|
)
|
|
button.append(Tag(name="i", attrs={"class": "fa-solid fa-plus"}))
|
|
dropdown.append(button)
|
|
ul = Tag(
|
|
name="ul",
|
|
attrs={
|
|
"class": "dropdown-menu",
|
|
"aria-labelledby": "dropdownSettingsButton",
|
|
},
|
|
)
|
|
|
|
for option in options:
|
|
if _dict[option]:
|
|
if option in ("can_create_files", "can_create_folders"):
|
|
li = Tag(name="li")
|
|
|
|
button = Tag(
|
|
name="button",
|
|
attrs={
|
|
"class": "dropdown-item",
|
|
"data-bs-toggle": "modal",
|
|
"data-bs-target": options[option]["data-bs-target"],
|
|
"data-path": _dict["path"],
|
|
"data-action": "new",
|
|
},
|
|
)
|
|
button.append(
|
|
Tag(
|
|
name="i",
|
|
attrs={
|
|
"class": options[option]["icon"],
|
|
},
|
|
)
|
|
)
|
|
|
|
span = Tag(
|
|
name="span",
|
|
attrs={"class": "pl-2"},
|
|
)
|
|
span.append(option.split("_")[-1][0:-1].title())
|
|
button.append(span)
|
|
|
|
li.append(button)
|
|
ul.append(li)
|
|
else:
|
|
badge = Tag(name="div", attrs={"class": "badge px-1"})
|
|
action = option.split("_")[1]
|
|
button = Tag(
|
|
name="button",
|
|
attrs={
|
|
"class": f"btn {options[option]['button_style']}",
|
|
"data-bs-toggle": "modal",
|
|
"data-bs-target": options[option]["data-bs-target"],
|
|
"data-path": _dict["path"],
|
|
}
|
|
| (
|
|
{"data-action": action}
|
|
if action != "delete"
|
|
else {}
|
|
),
|
|
)
|
|
button.append(
|
|
Tag(name="i", attrs={"class": options[option]["icon"]})
|
|
)
|
|
|
|
badge.append(button)
|
|
div.append(badge)
|
|
|
|
if dropdown:
|
|
dropdown.append(ul)
|
|
div.append(dropdown)
|
|
|
|
root_li.append(div)
|
|
|
|
html += root_li.prettify()
|
|
html += (
|
|
f"<li class='collapse folder-wrapper show' id={path_id}>"
|
|
if _dict.get("children", False)
|
|
else "<li class='folder-wrapper'>"
|
|
)
|
|
|
|
if _dict.get("children", False):
|
|
html += gen_folders_tree_html(_dict["children"])
|
|
html += "</ul></li>"
|
|
else:
|
|
h6 = Tag(name="h6", attrs={"class": "mb-0"})
|
|
h6.append(_dict["name"])
|
|
root_li.append(h6)
|
|
|
|
div = Tag(name="div", attrs={"class": "ms-2 d-sm-flex align-items-between"})
|
|
|
|
if _dict["can_edit"]:
|
|
badge = Tag(name="div", attrs={"class": "badge px-1"})
|
|
button = Tag(
|
|
name="button",
|
|
attrs={
|
|
"class": f"btn btn-outline-primary",
|
|
"data-bs-toggle": "modal",
|
|
"data-bs-target": "#modal-edit-new-file",
|
|
"data-path": _dict["path"],
|
|
"data-content": _dict["content"],
|
|
"data-action": "edit",
|
|
},
|
|
)
|
|
button.append(
|
|
Tag(name="i", attrs={"class": "fa-solid fa-pen-to-square"})
|
|
)
|
|
badge.append(button)
|
|
div.append(badge)
|
|
|
|
badge = Tag(name="div", attrs={"class": "badge px-1"})
|
|
button = Tag(
|
|
name="button",
|
|
attrs={
|
|
"class": f"btn btn-outline-danger",
|
|
"data-bs-toggle": "modal",
|
|
"data-bs-target": "#modal-delete",
|
|
"data-path": _dict["path"],
|
|
},
|
|
)
|
|
button.append(Tag(name="i", attrs={"class": "fa-solid fa-trash-can"}))
|
|
badge.append(button)
|
|
div.append(badge)
|
|
elif "content" in _dict:
|
|
badge = Tag(name="div", attrs={"class": "badge px-1"})
|
|
button = Tag(
|
|
name="button",
|
|
attrs={
|
|
"class": f"btn btn-outline-secondary",
|
|
"data-bs-toggle": "modal",
|
|
"data-bs-target": "#modal-see-file",
|
|
"data-path": _dict["path"],
|
|
"data-content": _dict["content"],
|
|
},
|
|
)
|
|
button.append(Tag(name="i", attrs={"class": "fa-solid fa-eye"}))
|
|
badge.append(button)
|
|
div.append(badge)
|
|
|
|
if _dict["can_download"]:
|
|
badge = Tag(name="div", attrs={"class": "badge px-1"})
|
|
button = Tag(
|
|
name="button",
|
|
attrs={
|
|
"class": f"btn btn-outline-primary download-button",
|
|
"data-path": _dict["path"],
|
|
},
|
|
)
|
|
button.append(Tag(name="i", attrs={"class": "fa-solid fa-download"}))
|
|
badge.append(button)
|
|
div.append(badge)
|
|
|
|
root_li.append(div)
|
|
html += root_li.prettify()
|
|
|
|
return html
|
|
|
|
|
|
def check_settings(settings: dict, check: str) -> bool:
|
|
return any(setting["context"] == check for setting in settings.values())
|