Files
karkas/src/karkas_blocks/karkas_blocks/standard/config/miniapp_ui.py
2024-08-29 23:22:04 +03:00

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