mirror of
https://gitflic.ru/project/alt-gnome/karkas.git
synced 2025-10-22 18:37:30 +03:00
268 lines
7.2 KiB
Python
268 lines
7.2 KiB
Python
import asyncio
|
|
from typing import TYPE_CHECKING
|
|
|
|
try:
|
|
import dash_bootstrap_components as dbc
|
|
import flask
|
|
from dash_extensions.enrich import ALL, Input, Output, State, dcc, html
|
|
|
|
DASH_AVAILABLE = True
|
|
except ImportError:
|
|
DASH_AVAILABLE = False
|
|
|
|
from karkas_core.modules_system.public_api import get_module
|
|
|
|
if TYPE_CHECKING:
|
|
from karkas_blocks.standard.roles import Roles as IRoles
|
|
|
|
from .config_manager import ConfigManager
|
|
|
|
|
|
def create_control(key: str, config: "ConfigManager"):
|
|
value = config.get(key)
|
|
meta = config.get_meta(key)
|
|
|
|
component_id = {
|
|
"type": "setting",
|
|
"key": key,
|
|
}
|
|
|
|
label_text = meta.get("pretty_name") or key
|
|
|
|
if meta.get("type") in ["string", "int", "float", "password"]:
|
|
input_type = {
|
|
"string": "text",
|
|
"int": "number",
|
|
"float": "number",
|
|
"password": "password",
|
|
}.get(meta.get("type"), "text")
|
|
|
|
input_props = {
|
|
"id": component_id,
|
|
"type": input_type,
|
|
}
|
|
|
|
if meta.get("type") != "password":
|
|
input_props["value"] = value
|
|
|
|
if meta.get("type") == "int":
|
|
input_props["step"] = 1
|
|
input_props["pattern"] = r"\d+"
|
|
elif meta.get("type") == "float":
|
|
input_props["step"] = "any"
|
|
|
|
component = dbc.Input(**input_props, invalid=False)
|
|
|
|
elif meta.get("type") == "select":
|
|
options = [{"label": opt, "value": opt} for opt in meta.get("options", [])]
|
|
component = dcc.Dropdown(
|
|
id=component_id,
|
|
options=options,
|
|
value=value,
|
|
style={
|
|
"padding-left": 0,
|
|
"padding-right": 0,
|
|
},
|
|
)
|
|
elif meta.get("type") == "checkbox":
|
|
component = dbc.Checkbox(
|
|
id=component_id,
|
|
checked=value,
|
|
label=label_text,
|
|
)
|
|
else:
|
|
return None
|
|
|
|
row = []
|
|
if meta.get("type") != "checkbox":
|
|
row.append(dbc.Label(label_text))
|
|
|
|
row.append(component)
|
|
|
|
if meta.get("description"):
|
|
row.append(dbc.FormText(meta.get("description")))
|
|
|
|
return dbc.Row(row, className="mb-3 mx-1")
|
|
|
|
|
|
def build_settings_tree(config: "ConfigManager"):
|
|
tree = {}
|
|
|
|
for key, value in config._metadata.items():
|
|
if not value["visible"]:
|
|
continue
|
|
|
|
parts = key.split("::")
|
|
control = create_control(key, config)
|
|
|
|
current = tree
|
|
for i, part in enumerate(parts[:-1]):
|
|
if part not in current:
|
|
current[part] = {"__controls": []}
|
|
current = current[part]
|
|
|
|
current["__controls"].append(control)
|
|
|
|
return tree
|
|
|
|
|
|
def create_card(category, controls):
|
|
return dbc.Card(
|
|
[
|
|
dbc.CardHeader(html.H3(category, className="mb-0")),
|
|
dbc.CardBody(controls),
|
|
],
|
|
className="mb-3",
|
|
)
|
|
|
|
|
|
def create_settings_components(tree, level=0):
|
|
components = []
|
|
|
|
for category, subtree in tree.items():
|
|
if category == "__controls":
|
|
continue
|
|
|
|
controls = subtree.get("__controls", [])
|
|
subcomponents = create_settings_components(subtree, level + 1)
|
|
|
|
if controls or subcomponents:
|
|
card_content = controls + subcomponents
|
|
card = create_card(category, card_content)
|
|
components.append(card)
|
|
|
|
return components
|
|
|
|
|
|
def get_miniapp_blueprint(config: "ConfigManager", prefix: str):
|
|
Roles: "type[IRoles]" = get_module("standard.roles", "Roles")
|
|
|
|
roles = Roles()
|
|
|
|
import datetime
|
|
|
|
from dash_extensions.enrich import DashBlueprint
|
|
|
|
bp = DashBlueprint()
|
|
|
|
def create_layout():
|
|
settings_tree = build_settings_tree(config)
|
|
settings_components = create_settings_components(settings_tree)
|
|
|
|
layout = html.Div(
|
|
[
|
|
html.Script(),
|
|
html.H1("Настройки"),
|
|
dbc.Form(settings_components),
|
|
html.Div(id="save-confirmation"),
|
|
dbc.Button(
|
|
"Сохранить",
|
|
id="save-settings",
|
|
color="primary",
|
|
className="mt-3 w-100",
|
|
n_clicks=0,
|
|
),
|
|
html.Div(id="settings-update-trigger", style={"display": "none"}),
|
|
dcc.Store(id="settings-store"),
|
|
],
|
|
style={
|
|
"padding": "20px",
|
|
},
|
|
)
|
|
return layout
|
|
|
|
bp.layout = create_layout
|
|
|
|
@bp.callback(
|
|
Output("save-confirmation", "children"),
|
|
Output("settings-store", "data"),
|
|
Input("save-settings", "n_clicks"),
|
|
State({"type": "setting", "key": ALL}, "value"),
|
|
State({"type": "setting", "key": ALL}, "id"),
|
|
prevent_initial_call=True,
|
|
allow_duplicate=True,
|
|
)
|
|
def save_settings(n_clicks, values, keys):
|
|
if n_clicks > 0:
|
|
user = getattr(flask.g, "user", None)
|
|
|
|
if user is None:
|
|
return (
|
|
dbc.Alert(
|
|
"Вы не авторизованы!",
|
|
color="danger",
|
|
duration=10000,
|
|
),
|
|
"-",
|
|
)
|
|
|
|
if not asyncio.run(roles.check_admin_permission(user["id"])):
|
|
return (
|
|
dbc.Alert(
|
|
"Вы не администратор!",
|
|
color="danger",
|
|
duration=10000,
|
|
),
|
|
"-",
|
|
)
|
|
|
|
# TODO: add values validation
|
|
|
|
updated_settings = {}
|
|
for value, id_dict in zip(values, keys):
|
|
key: str = id_dict["key"]
|
|
if prefix:
|
|
key = key.removeprefix(f"{prefix}-")
|
|
|
|
meta = config.get_meta(key)
|
|
|
|
if meta["type"] == "password":
|
|
# Is updated only if new value is specified
|
|
if value:
|
|
updated_settings[key] = value
|
|
else:
|
|
updated_settings[key] = value
|
|
|
|
config.mass_set(updated_settings)
|
|
config.save()
|
|
|
|
now = datetime.datetime.now()
|
|
date_str = now.strftime("%H:%M:%S")
|
|
|
|
return (
|
|
dbc.Alert(
|
|
f"Настройки сохранены в {date_str}",
|
|
color="success",
|
|
duration=10000,
|
|
),
|
|
date_str,
|
|
)
|
|
|
|
bp.clientside_callback(
|
|
"""
|
|
function(n_clicks) {
|
|
const buttonSelector = '#%s-save-settings';
|
|
if (n_clicks > 0) {
|
|
document.querySelector(buttonSelector).disabled = true;
|
|
}
|
|
}
|
|
"""
|
|
% (prefix),
|
|
Input("save-settings", "n_clicks"),
|
|
)
|
|
|
|
bp.clientside_callback(
|
|
"""
|
|
function(data) {
|
|
const buttonSelector = '#%s-save-settings';
|
|
if (data) {
|
|
document.querySelector(buttonSelector).disabled = false;
|
|
}
|
|
}
|
|
"""
|
|
% (prefix),
|
|
Input("settings-store", "data"),
|
|
)
|
|
|
|
return bp
|