mirror of
https://gitflic.ru/project/maks1ms/ocab.git
synced 2024-12-23 16:23:01 +03:00
wip
This commit is contained in:
parent
9fa23f18b9
commit
2becd33774
@ -78,7 +78,7 @@ class OCAB:
|
|||||||
singleton = Singleton()
|
singleton = Singleton()
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
config = get_module("standard.config", "config")
|
config = get_module("standard.config", "config")
|
||||||
app.mount("/webapp", singleton.storage["webapp"])
|
app.mount(config.get("miniapp::prefix"), singleton.storage["webapp"])
|
||||||
await register_bot_webhook(app, singleton.bot, singleton.dp)
|
await register_bot_webhook(app, singleton.bot, singleton.dp)
|
||||||
await singleton.bot.set_webhook(config.get("core::webhook::public_url"))
|
await singleton.bot.set_webhook(config.get("core::webhook::public_url"))
|
||||||
hyperConfig = HyperConfig()
|
hyperConfig = HyperConfig()
|
||||||
|
@ -15,6 +15,7 @@ def register_settings_page():
|
|||||||
path="/settings",
|
path="/settings",
|
||||||
blueprint=get_miniapp_blueprint(config, prefix),
|
blueprint=get_miniapp_blueprint(config, prefix),
|
||||||
prefix=prefix,
|
prefix=prefix,
|
||||||
|
role="ADMIN",
|
||||||
)
|
)
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -1,13 +1,22 @@
|
|||||||
|
import asyncio
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from .config_manager import ConfigManager
|
from .config_manager import ConfigManager
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import dash_bootstrap_components as dbc
|
import dash_bootstrap_components as dbc
|
||||||
|
import flask
|
||||||
from dash_extensions.enrich import ALL, Input, Output, State, dcc, html
|
from dash_extensions.enrich import ALL, Input, Output, State, dcc, html
|
||||||
|
|
||||||
DASH_AVAILABLE = True
|
DASH_AVAILABLE = True
|
||||||
except ImportError:
|
except ImportError:
|
||||||
DASH_AVAILABLE = False
|
DASH_AVAILABLE = False
|
||||||
|
|
||||||
|
from ocab_core.modules_system.public_api import get_module
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from ocab_modules.standard.roles import Roles as IRoles
|
||||||
|
|
||||||
|
|
||||||
def create_control(key: str, config: ConfigManager):
|
def create_control(key: str, config: ConfigManager):
|
||||||
value = config.get(key)
|
value = config.get(key)
|
||||||
@ -126,6 +135,10 @@ def create_settings_components(tree, level=0):
|
|||||||
|
|
||||||
|
|
||||||
def get_miniapp_blueprint(config: ConfigManager, prefix: str):
|
def get_miniapp_blueprint(config: ConfigManager, prefix: str):
|
||||||
|
Roles: "type[IRoles]" = get_module("standard.roles", "Roles")
|
||||||
|
|
||||||
|
roles = Roles()
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from dash_extensions.enrich import DashBlueprint
|
from dash_extensions.enrich import DashBlueprint
|
||||||
@ -171,6 +184,28 @@ def get_miniapp_blueprint(config: ConfigManager, prefix: str):
|
|||||||
)
|
)
|
||||||
def save_settings(n_clicks, values, keys):
|
def save_settings(n_clicks, values, keys):
|
||||||
if n_clicks > 0:
|
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: добавить валидацию значений
|
# TODO: добавить валидацию значений
|
||||||
|
|
||||||
updated_settings = {}
|
updated_settings = {}
|
||||||
@ -178,12 +213,18 @@ def get_miniapp_blueprint(config: ConfigManager, prefix: str):
|
|||||||
key: str = id_dict["key"]
|
key: str = id_dict["key"]
|
||||||
if prefix:
|
if prefix:
|
||||||
key = key.removeprefix(f"{prefix}-")
|
key = key.removeprefix(f"{prefix}-")
|
||||||
updated_settings[key] = value
|
|
||||||
|
meta = config.get_meta(key)
|
||||||
|
|
||||||
|
if meta["type"] == "password":
|
||||||
|
if value: # Only update if a new value is provided
|
||||||
|
updated_settings[key] = value
|
||||||
|
else:
|
||||||
|
updated_settings[key] = value
|
||||||
|
|
||||||
config.mass_set(updated_settings)
|
config.mass_set(updated_settings)
|
||||||
config.save()
|
config.save()
|
||||||
|
|
||||||
# locale.setlocale(locale.LC_TIME, "ru_RU.UTF-8")
|
|
||||||
now = datetime.datetime.now()
|
now = datetime.datetime.now()
|
||||||
date_str = now.strftime("%H:%M:%S")
|
date_str = now.strftime("%H:%M:%S")
|
||||||
|
|
||||||
|
@ -1,13 +1,104 @@
|
|||||||
import flask
|
import flask
|
||||||
from aiogram.utils.web_app import safe_parse_webapp_init_data
|
from aiogram.utils.web_app import safe_parse_webapp_init_data
|
||||||
from dash import Dash
|
from dash import Dash
|
||||||
from dash_extensions.enrich import Input
|
from dash_extensions.enrich import Input, Output
|
||||||
from flask import request
|
from flask import request
|
||||||
|
|
||||||
|
# TODO: добавить прокидывание BASE_PATH, т.к. это параметр из настроек
|
||||||
|
|
||||||
|
WEBAPP_LOADER_TEMPLATE = """
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ru">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>OCAB</title>
|
||||||
|
<script src="https://telegram.org/js/telegram-web-app.js"></script>
|
||||||
|
<script>
|
||||||
|
window.addEventListener('message', function(event) {
|
||||||
|
if (event.origin !== window.location.origin) return;
|
||||||
|
if (event.data.type === 'iframe-url-changed') {
|
||||||
|
history.pushState(
|
||||||
|
null,
|
||||||
|
'',
|
||||||
|
window.BASE_PATH + event.data.pathname.substring(
|
||||||
|
window.INTERNAL_PATH.length
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
window.addEventListener('popstate', function(event) {
|
||||||
|
var iframe = document.getElementById('app-frame');
|
||||||
|
var iframeWindow = iframe.contentWindow;
|
||||||
|
iframeWindow.history.back();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
#app-frame {
|
||||||
|
display:none;
|
||||||
|
width:100%;
|
||||||
|
height:100vh;
|
||||||
|
border:none;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="loading">Loading...</div>
|
||||||
|
<iframe id="app-frame"></iframe>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const tg = window.Telegram.WebApp;
|
||||||
|
document.cookie = `tg_init_data=${JSON.stringify(tg.initData)}; path=/`;
|
||||||
|
|
||||||
|
// if (!tg.initData) return;
|
||||||
|
|
||||||
|
const iframe = document.getElementById('app-frame');
|
||||||
|
|
||||||
|
// Константы для путей
|
||||||
|
const BASE_PATH = '/webapp';
|
||||||
|
const INTERNAL_PATH = '/webapp/_internal';
|
||||||
|
|
||||||
|
window.BASE_PATH = BASE_PATH;
|
||||||
|
window.INTERNAL_PATH = INTERNAL_PATH
|
||||||
|
|
||||||
|
// Текущий путь страницы
|
||||||
|
const currentPath = window.location.pathname;
|
||||||
|
|
||||||
|
// Формируем новый путь для iframe
|
||||||
|
let iframeSrc = INTERNAL_PATH;
|
||||||
|
|
||||||
|
// Если текущий путь начинается с BASE_PATH, убираем BASE_PATH из текущего пути
|
||||||
|
if (currentPath.startsWith(BASE_PATH)
|
||||||
|
&& currentPath.length > BASE_PATH.length) {
|
||||||
|
iframeSrc += currentPath.substring(BASE_PATH.length);
|
||||||
|
} else if (currentPath !== '/') {
|
||||||
|
iframeSrc += currentPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
iframe.src = iframeSrc;
|
||||||
|
|
||||||
|
iframe.onload = function() {
|
||||||
|
document.getElementById('loading').style.display = 'none';
|
||||||
|
iframe.style.display = 'block';
|
||||||
|
};
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
def get_auth_server(bot_token: str):
|
def get_auth_server(bot_token: str):
|
||||||
server = flask.Flask(__name__)
|
server = flask.Flask(__name__)
|
||||||
|
|
||||||
|
@server.route("/<path:rest>")
|
||||||
|
@server.route("/")
|
||||||
|
def webapp_loader(rest=None):
|
||||||
|
return flask.Response(WEBAPP_LOADER_TEMPLATE, mimetype="text/html")
|
||||||
|
|
||||||
@server.before_request
|
@server.before_request
|
||||||
def add_auth_data():
|
def add_auth_data():
|
||||||
init_data = request.cookies.get("tg_init_data")
|
init_data = request.cookies.get("tg_init_data")
|
||||||
@ -21,13 +112,35 @@ def get_auth_server(bot_token: str):
|
|||||||
return server
|
return server
|
||||||
|
|
||||||
|
|
||||||
def setup_auth_clientcallback(app: Dash):
|
def setup_auth_clientcallbacks(app: Dash):
|
||||||
app.clientside_callback(
|
app.clientside_callback(
|
||||||
"""
|
"""
|
||||||
function(n_inervals) {
|
function(n_intervals) {
|
||||||
const tg = window.Telegram.WebApp;
|
if (window.webAppData) {
|
||||||
document.cookie = `tg_init_data=${JSON.stringify(tg.initData)}; path=/`;
|
return window.webAppData;
|
||||||
|
}
|
||||||
|
|
||||||
|
function receiveMessage(event) {
|
||||||
|
if (event.data.type === 'webAppData') {
|
||||||
|
window.webAppData = event.data.webApp;
|
||||||
|
window.removeEventListener('message', receiveMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('message', receiveMessage, false);
|
||||||
|
|
||||||
|
return window.dash_clientside.no_update;
|
||||||
}
|
}
|
||||||
""",
|
""",
|
||||||
Input("init-telegram-interval", "n_intervals"),
|
Output("hidden-div", "children"),
|
||||||
|
Input("interval-component", "n_intervals"),
|
||||||
|
)
|
||||||
|
|
||||||
|
app.clientside_callback(
|
||||||
|
"""
|
||||||
|
function(pathname) {
|
||||||
|
window.parent.postMessage({ type: 'iframe-url-changed', pathname }, '*');
|
||||||
|
}
|
||||||
|
""",
|
||||||
|
Input("url", "pathname"),
|
||||||
)
|
)
|
||||||
|
@ -1,26 +1,30 @@
|
|||||||
|
import asyncio
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import dash
|
import dash
|
||||||
import dash_bootstrap_components as dbc
|
import dash_bootstrap_components as dbc
|
||||||
|
import flask
|
||||||
from dash_extensions.enrich import DashBlueprint, DashProxy, Input, Output, dcc, html
|
from dash_extensions.enrich import DashBlueprint, DashProxy, Input, Output, dcc, html
|
||||||
from dash_extensions.pages import setup_page_components
|
from dash_extensions.pages import setup_page_components
|
||||||
|
|
||||||
from ocab_core.modules_system.public_api import get_module
|
from ocab_core.modules_system.public_api import get_module, log
|
||||||
|
|
||||||
from .dash_telegram_auth import get_auth_server, setup_auth_clientcallback
|
from .dash_telegram_auth import get_auth_server, setup_auth_clientcallbacks
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ocab_modules.standard.config import IConfig
|
from ocab_modules.standard.config import IConfig
|
||||||
|
from ocab_modules.standard.roles import Roles as IRoles
|
||||||
|
|
||||||
pages = OrderedDict()
|
pages = OrderedDict()
|
||||||
|
|
||||||
|
|
||||||
def register_page(name, path, blueprint, prefix=""):
|
def register_page(name, path, blueprint, prefix="", role="USER"):
|
||||||
pages[path] = {
|
pages[path] = {
|
||||||
"name": name,
|
"name": name,
|
||||||
"blueprint": blueprint,
|
"blueprint": blueprint,
|
||||||
"prefix": prefix,
|
"prefix": prefix,
|
||||||
|
"role": role,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -33,9 +37,14 @@ def register_home_page():
|
|||||||
register_home_page()
|
register_home_page()
|
||||||
|
|
||||||
config: "IConfig" = get_module("standard.config", "config")
|
config: "IConfig" = get_module("standard.config", "config")
|
||||||
|
Roles: "type[IRoles]" = get_module("standard.roles", "Roles")
|
||||||
|
|
||||||
|
|
||||||
def create_dash_app(requests_pathname_prefix: str = None) -> dash.Dash:
|
def create_dash_app(requests_pathname_prefix: str = None) -> dash.Dash:
|
||||||
|
log(requests_pathname_prefix)
|
||||||
|
|
||||||
|
real_prefix = f"{requests_pathname_prefix}_internal/"
|
||||||
|
|
||||||
server = get_auth_server(config.get("core::token"))
|
server = get_auth_server(config.get("core::token"))
|
||||||
|
|
||||||
app = DashProxy(
|
app = DashProxy(
|
||||||
@ -47,10 +56,13 @@ def create_dash_app(requests_pathname_prefix: str = None) -> dash.Dash:
|
|||||||
dbc.icons.BOOTSTRAP,
|
dbc.icons.BOOTSTRAP,
|
||||||
],
|
],
|
||||||
external_scripts=[
|
external_scripts=[
|
||||||
|
#
|
||||||
"https://telegram.org/js/telegram-web-app.js"
|
"https://telegram.org/js/telegram-web-app.js"
|
||||||
], # Add Telegram Mini Apps script to <head>
|
],
|
||||||
server=server,
|
server=server,
|
||||||
requests_pathname_prefix=requests_pathname_prefix,
|
requests_pathname_prefix=real_prefix,
|
||||||
|
routes_pathname_prefix="/_internal/",
|
||||||
|
# requests_pathname_prefix=requests_pathname_prefix,
|
||||||
meta_tags=[
|
meta_tags=[
|
||||||
{"name": "viewport", "content": "width=device-width, initial-scale=1"},
|
{"name": "viewport", "content": "width=device-width, initial-scale=1"},
|
||||||
],
|
],
|
||||||
@ -63,30 +75,8 @@ def create_dash_app(requests_pathname_prefix: str = None) -> dash.Dash:
|
|||||||
|
|
||||||
# Register pages
|
# Register pages
|
||||||
for path, page in pages.items():
|
for path, page in pages.items():
|
||||||
# dash.register_page(page["name"], path=path, layout=page["layout"])
|
|
||||||
page["blueprint"].register(app, path, prefix=page["prefix"])
|
page["blueprint"].register(app, path, prefix=page["prefix"])
|
||||||
|
|
||||||
# Create sidebar
|
|
||||||
sidebar = dbc.Offcanvas(
|
|
||||||
id="offcanvas",
|
|
||||||
title="Меню",
|
|
||||||
is_open=False,
|
|
||||||
children=[
|
|
||||||
dbc.Nav(
|
|
||||||
[
|
|
||||||
dbc.NavLink(
|
|
||||||
page["name"],
|
|
||||||
href=f"{requests_pathname_prefix}{path.lstrip('/')}",
|
|
||||||
id={"type": "nav-link", "index": path},
|
|
||||||
)
|
|
||||||
for path, page in pages.items()
|
|
||||||
],
|
|
||||||
vertical=True,
|
|
||||||
pills=True,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
# Create navbar
|
# Create navbar
|
||||||
navbar = dbc.Navbar(
|
navbar = dbc.Navbar(
|
||||||
dbc.Container(
|
dbc.Container(
|
||||||
@ -104,24 +94,67 @@ def create_dash_app(requests_pathname_prefix: str = None) -> dash.Dash:
|
|||||||
dark=True,
|
dark=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Define app layout
|
roles = Roles()
|
||||||
app.layout = html.Div(
|
|
||||||
[
|
|
||||||
dcc.Location(id="url", refresh=False),
|
|
||||||
dcc.Interval(
|
|
||||||
id="init-telegram-interval",
|
|
||||||
interval=100,
|
|
||||||
n_intervals=0,
|
|
||||||
max_intervals=1,
|
|
||||||
),
|
|
||||||
navbar,
|
|
||||||
sidebar,
|
|
||||||
dash.page_container,
|
|
||||||
setup_page_components(),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
setup_auth_clientcallback(app)
|
def create_layout():
|
||||||
|
user = getattr(flask.g, "user", None)
|
||||||
|
|
||||||
|
if not user:
|
||||||
|
return html.Div()
|
||||||
|
|
||||||
|
user_id = user["id"]
|
||||||
|
user_permission = asyncio.run(roles.get_user_permission(user_id)) or "USER"
|
||||||
|
|
||||||
|
available_pages = {
|
||||||
|
path: page
|
||||||
|
for path, page in pages.items()
|
||||||
|
if (isinstance(page["role"], list) and user_permission in page["role"])
|
||||||
|
or page["role"] == user_permission
|
||||||
|
or page["role"] == "USER"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create sidebar
|
||||||
|
sidebar = dbc.Offcanvas(
|
||||||
|
id="offcanvas",
|
||||||
|
title="Меню",
|
||||||
|
is_open=False,
|
||||||
|
children=[
|
||||||
|
dbc.Nav(
|
||||||
|
[
|
||||||
|
dbc.NavLink(
|
||||||
|
page["name"],
|
||||||
|
href=f"{real_prefix}/{path.lstrip('/')}",
|
||||||
|
id={"type": "nav-link", "index": path},
|
||||||
|
)
|
||||||
|
for path, page in available_pages.items()
|
||||||
|
],
|
||||||
|
vertical=True,
|
||||||
|
pills=True,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
layout = html.Div(
|
||||||
|
[
|
||||||
|
dcc.Location(id="url", refresh=False),
|
||||||
|
dcc.Interval(
|
||||||
|
id="init-telegram-interval",
|
||||||
|
interval=100,
|
||||||
|
n_intervals=0,
|
||||||
|
max_intervals=1,
|
||||||
|
),
|
||||||
|
navbar,
|
||||||
|
sidebar,
|
||||||
|
dash.page_container,
|
||||||
|
setup_page_components(),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
return layout
|
||||||
|
|
||||||
|
app.layout = create_layout
|
||||||
|
|
||||||
|
setup_auth_clientcallbacks(app)
|
||||||
|
|
||||||
# Открытие на кнопку меню
|
# Открытие на кнопку меню
|
||||||
app.clientside_callback(
|
app.clientside_callback(
|
||||||
|
Loading…
Reference in New Issue
Block a user