Refactoring and cleanup codebase (50%...)
MVP dashboard users page
This commit is contained in:
parent
20465600b1
commit
9e2560f7c3
@ -9,3 +9,4 @@
|
||||
/db/
|
||||
/db/telezab.db
|
||||
/trash/
|
||||
/venv3.12.3/
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -11,3 +11,4 @@
|
||||
/logs/error.log
|
||||
/db/
|
||||
/db/telezab.db
|
||||
/venv3.12.3/
|
||||
@ -1,23 +1,21 @@
|
||||
import sqlite3
|
||||
|
||||
import telebot
|
||||
|
||||
import telezab
|
||||
from backend_locks import db_lock, bot
|
||||
from bot_database import get_admins, is_whitelisted, format_regions_list, get_sorted_regions, log_user_event, \
|
||||
get_user_subscribed_regions
|
||||
from config import DB_PATH
|
||||
from telezab import handle_my_subscriptions_button, handle_active_regions_button, handle_notification_mode_button
|
||||
from utils import show_main_menu, show_settings_menu
|
||||
from utilities.telegram_utilities import show_main_menu, show_settings_menu
|
||||
|
||||
|
||||
def handle_main_menu(message, chat_id, text):
|
||||
"""Обработка команд в главном меню."""
|
||||
if text == 'Регистрация':
|
||||
telezab.user_state_manager.set_state(chat_id, "REGISTRATION")
|
||||
telezab.state.set_state(chat_id, "REGISTRATION")
|
||||
telezab.handle_register(message)
|
||||
elif text == 'Настройки':
|
||||
telezab.user_state_manager.set_state(chat_id, "SETTINGS_MENU")
|
||||
telezab.state.set_state(chat_id, "SETTINGS_MENU")
|
||||
telezab.show_settings_menu(chat_id)
|
||||
elif text == 'Помощь':
|
||||
telezab.handle_help(message)
|
||||
@ -32,10 +30,10 @@ def handle_settings_menu(message, chat_id, text):
|
||||
"""Обработка команд в меню настроек."""
|
||||
admins_list = get_admins()
|
||||
if text.lower() == 'подписаться':
|
||||
telezab.user_state_manager.set_state(chat_id, "SUBSCRIBE")
|
||||
telezab.state.set_state(chat_id, "SUBSCRIBE")
|
||||
handle_subscribe_button(message)
|
||||
elif text.lower() == 'отписаться':
|
||||
telezab.user_state_manager.set_state(chat_id, "UNSUBSCRIBE")
|
||||
telezab.state.set_state(chat_id, "UNSUBSCRIBE")
|
||||
handle_unsubscribe_button(message)
|
||||
elif text.lower() == 'мои подписки':
|
||||
handle_my_subscriptions_button(message)
|
||||
@ -44,7 +42,7 @@ def handle_settings_menu(message, chat_id, text):
|
||||
elif text.lower() == "режим уведомлений":
|
||||
handle_notification_mode_button(message)
|
||||
elif text.lower() == 'назад':
|
||||
telezab.user_state_manager.set_state(chat_id, "MAIN_MENU")
|
||||
telezab.state.set_state(chat_id, "MAIN_MENU")
|
||||
show_main_menu(chat_id)
|
||||
else:
|
||||
bot.send_message(chat_id, "Команда не распознана.")
|
||||
@ -75,7 +73,7 @@ def process_subscription_button(message, chat_id, username):
|
||||
invalid_regions = []
|
||||
if message.text.lower() == 'отмена':
|
||||
bot.send_message(chat_id, "Действие отменено.")
|
||||
telezab.user_state_manager.set_state(chat_id, "SETTINGS_MENU")
|
||||
telezab.state.set_state(chat_id, "SETTINGS_MENU")
|
||||
return show_settings_menu(chat_id)
|
||||
if not all(part.strip().isdigit() for part in message.text.split(',')):
|
||||
markup = telebot.types.InlineKeyboardMarkup()
|
||||
@ -108,7 +106,7 @@ def process_subscription_button(message, chat_id, username):
|
||||
f"Регион с ID {', '.join(invalid_regions)} не существует. Введите корректные номера или 'отмена'.")
|
||||
bot.send_message(chat_id, f"Подписка на регионы: {', '.join(subbed_regions)} оформлена.")
|
||||
log_user_event(chat_id, username, f"Subscribed to regions: {', '.join(subbed_regions)}")
|
||||
telezab.user_state_manager.set_state(chat_id, "SETTINGS_MENU")
|
||||
telezab.state.set_state(chat_id, "SETTINGS_MENU")
|
||||
show_settings_menu(chat_id)
|
||||
|
||||
|
||||
@ -117,7 +115,7 @@ def handle_unsubscribe_button(message):
|
||||
if not is_whitelisted(chat_id):
|
||||
bot.send_message(chat_id, "Вы не авторизованы для использования этого бота.")
|
||||
telebot.logger.info(f"Unauthorized access attempt by {chat_id}")
|
||||
telezab.user_state_manager.set_state(chat_id, "REGISTRATION")
|
||||
telezab.state.set_state(chat_id, "REGISTRATION")
|
||||
return show_main_menu(chat_id)
|
||||
username = message.from_user.username
|
||||
if username:
|
||||
@ -129,7 +127,7 @@ def handle_unsubscribe_button(message):
|
||||
|
||||
if not user_regions:
|
||||
bot.send_message(chat_id, "Вы не подписаны ни на один регион.")
|
||||
telezab.user_state_manager.set_state(chat_id, "SETTINGS_MENU")
|
||||
telezab.state.set_state(chat_id, "SETTINGS_MENU")
|
||||
return show_settings_menu(chat_id)
|
||||
regions_list = format_regions_list(user_regions)
|
||||
markup = telebot.types.InlineKeyboardMarkup()
|
||||
@ -147,7 +145,7 @@ def process_unsubscription_button(message, chat_id, username):
|
||||
markup.add(telebot.types.InlineKeyboardButton(text="Отмена", callback_data=f"cancel_action"))
|
||||
if message.text.lower() == 'отмена':
|
||||
bot.send_message(chat_id, "Действие отменено.")
|
||||
telezab.user_state_manager.set_state(chat_id, "SETTINGS_MENU")
|
||||
telezab.state.set_state(chat_id, "SETTINGS_MENU")
|
||||
return show_settings_menu(chat_id)
|
||||
# Проверка, что введённая строка содержит только цифры и запятые
|
||||
if not all(part.strip().isdigit() for part in message.text.split(',')):
|
||||
@ -173,5 +171,5 @@ def process_unsubscription_button(message, chat_id, username):
|
||||
bot.send_message(chat_id, f"Регион с ID {', '.join(invalid_regions)} не найден в ваших подписках.")
|
||||
bot.send_message(chat_id, f"Отписка от регионов: {', '.join(unsubbed_regions)} выполнена.")
|
||||
log_user_event(chat_id, username, f"Unsubscribed from regions: {', '.join(unsubbed_regions)}")
|
||||
telezab.user_state_manager.set_state(chat_id, "SETTINGS_MENU")
|
||||
telezab.state.set_state(chat_id, "SETTINGS_MENU")
|
||||
show_settings_menu(chat_id)
|
||||
|
||||
176
backend_flask.py
176
backend_flask.py
@ -2,18 +2,37 @@ import logging
|
||||
import sqlite3
|
||||
|
||||
import telebot
|
||||
from flask import Flask, request, jsonify, render_template
|
||||
from flask import Flask, request, jsonify, render_template, flash, redirect, url_for
|
||||
from flask_ldap3_login.forms import LDAPLoginForm
|
||||
from flask_login import login_manager, login_user, logout_user, UserMixin
|
||||
|
||||
from frontend.dashboard import bp_dashboard, bp_api
|
||||
|
||||
import backend_bot
|
||||
import bot_database
|
||||
import telezab
|
||||
import utils
|
||||
import utilities.telegram_utilities as telegram_util
|
||||
from backend_locks import db_lock
|
||||
from config import BASE_URL, DB_PATH
|
||||
from utils import extract_region_number, format_message, validate_chat_id, validate_telegram_id, validate_email
|
||||
from utilities.telegram_utilities import extract_region_number, format_message, validate_chat_id, validate_telegram_id, validate_email
|
||||
|
||||
app = Flask(__name__, static_url_path='/telezab/static', template_folder='templates')
|
||||
# app.register_blueprint(webui)
|
||||
app.secret_key = "supersecretkey"
|
||||
app.register_blueprint(bp_dashboard)
|
||||
app.register_blueprint(bp_api)
|
||||
#
|
||||
# # Инициализация менеджеров
|
||||
# ldap_manager = LDAP3LoginManager(app)
|
||||
# login_manager = LoginManager(app)
|
||||
# login_manager.login_view = "login"
|
||||
|
||||
# Пользовательский класс
|
||||
class User(UserMixin):
|
||||
def __init__(self, dn, username):
|
||||
self.id = dn
|
||||
self.username = username
|
||||
|
||||
|
||||
# Настройка уровня логирования для Flask
|
||||
app.logger.setLevel(logging.INFO)
|
||||
@ -26,9 +45,9 @@ def webhook():
|
||||
data = request.get_json()
|
||||
app.logger.info(f"Получены данные: {data}")
|
||||
|
||||
# Генерация хеша события и логирование
|
||||
event_hash = bot_database.hash_data(data)
|
||||
app.logger.debug(f"Сгенерирован хеш для события: {event_hash}")
|
||||
# # Генерация хеша события и логирование
|
||||
# event_hash = bot_database.hash_data(data)
|
||||
# app.logger.debug(f"Сгенерирован хеш для события: {event_hash}")
|
||||
|
||||
# Работа с базой данных в блоке синхронизации
|
||||
with db_lock:
|
||||
@ -82,7 +101,7 @@ def webhook():
|
||||
app.logger.info(
|
||||
f"Формирование сообщения для пользователя {username} (chat_id={chat_id}) [{formatted_message}]")
|
||||
try:
|
||||
from rabbitmq import send_to_queue
|
||||
from utilities.rabbitmq import send_to_queue
|
||||
send_to_queue({'chat_id': chat_id, 'username': username, 'message': message})
|
||||
app.logger.debug(f"Сообщение поставлено в очередь для {chat_id} (@{username})")
|
||||
except Exception as e:
|
||||
@ -93,8 +112,8 @@ def webhook():
|
||||
if undelivered:
|
||||
query = 'INSERT OR IGNORE INTO events (hash, data, delivered) VALUES (?, ?, ?)'
|
||||
app.logger.debug(
|
||||
f"Сохранение события в базе данных: {query} (hash={event_hash}, delivered={False})")
|
||||
cursor.execute(query, (event_hash, str(data), False))
|
||||
f"Сохранение события в базе данных: {query} (delivered={False})")
|
||||
cursor.execute(query, (str(data), False))
|
||||
|
||||
# Коммитим изменения в базе данных
|
||||
conn.commit()
|
||||
@ -152,11 +171,11 @@ def add_user():
|
||||
if success:
|
||||
# INFO: Пользователь успешно добавлен в whitelist
|
||||
app.logger.info(f"Пользователь {telegram_id} добавлен в whitelist.")
|
||||
telezab.user_state_manager.set_state(chat_id, "MAIN_MENU")
|
||||
telezab.state.set_state(chat_id, "MAIN_MENU")
|
||||
|
||||
# DEBUG: Показ основного меню пользователю
|
||||
app.logger.debug(f"Отображение основного меню для пользователя с chat_id {chat_id}")
|
||||
utils.show_main_menu(chat_id)
|
||||
telegram_util.show_main_menu(chat_id)
|
||||
return jsonify(
|
||||
{"status": "success", "msg": f"User {telegram_id} with {user_email} added successfully"}), 200
|
||||
else:
|
||||
@ -238,76 +257,71 @@ def delete_user():
|
||||
app.logger.debug(f"Соединение с базой данных закрыто")
|
||||
|
||||
|
||||
@app.route(BASE_URL + '/users/get', methods=['GET'])
|
||||
def get_users():
|
||||
try:
|
||||
# INFO: Запрос на получение списка пользователей
|
||||
app.logger.info("Запрос на получение информации о пользователях получен")
|
||||
|
||||
with db_lock:
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# DEBUG: Запрос данных из таблицы whitelist
|
||||
app.logger.debug("Запрос данных пользователей из таблицы whitelist")
|
||||
cursor.execute('SELECT * FROM whitelist')
|
||||
users = cursor.fetchall()
|
||||
app.logger.debug("Формирование словаря пользователей")
|
||||
users_dict = {user_id: {'id': user_id, 'username': username, 'email': email, 'events': [], 'worker': '',
|
||||
'subscriptions': []}
|
||||
for user_id, username, email in users}
|
||||
|
||||
# DEBUG: Запрос данных событий пользователей
|
||||
app.logger.debug("Запрос событий пользователей из таблицы user_events")
|
||||
cursor.execute('SELECT chat_id, username, action, timestamp FROM user_events')
|
||||
events = cursor.fetchall()
|
||||
|
||||
# DEBUG: Обработка событий и добавление их в словарь пользователей
|
||||
for chat_id, username, action, timestamp in events:
|
||||
if chat_id in users_dict:
|
||||
event = {'type': action, 'date': timestamp}
|
||||
if "Subscribed to region" in action:
|
||||
region = action.split(": ")[-1]
|
||||
event['region'] = region
|
||||
users_dict[chat_id]['events'].append(event)
|
||||
|
||||
# DEBUG: Запрос данных подписок пользователей
|
||||
app.logger.debug("Запрос активных подписок пользователей из таблицы subscriptions")
|
||||
cursor.execute('SELECT chat_id, region_id FROM subscriptions WHERE active = 1')
|
||||
subscriptions = cursor.fetchall()
|
||||
|
||||
# DEBUG: Добавление подписок к пользователям
|
||||
for chat_id, region_id in subscriptions:
|
||||
if chat_id in users_dict:
|
||||
users_dict[chat_id]['subscriptions'].append(str(region_id))
|
||||
|
||||
# INFO: Формирование результата
|
||||
app.logger.info("Формирование результата для ответа")
|
||||
result = []
|
||||
for user in users_dict.values():
|
||||
ordered_user = {
|
||||
'email': user['email'],
|
||||
'username': user['username'],
|
||||
'id': user['id'],
|
||||
'worker': user['worker'],
|
||||
'events': user['events'],
|
||||
'subscriptions': ', '.join(user['subscriptions'])
|
||||
}
|
||||
result.append(ordered_user)
|
||||
|
||||
# INFO: Успешная отправка данных пользователей
|
||||
app.logger.info("Информация о пользователях успешно отправлена")
|
||||
return jsonify(result)
|
||||
|
||||
except Exception as e:
|
||||
# ERROR: Ошибка при получении информации о пользователях
|
||||
app.logger.error(f"Ошибка при получении информации о пользователях: {str(e)}")
|
||||
return jsonify({'status': 'error', 'message': str(e)}), 500
|
||||
|
||||
|
||||
@app.route(BASE_URL + '/users', methods=['GET'])
|
||||
def view_users():
|
||||
return render_template('users.html')
|
||||
# @app.route(BASE_URL + '/users/get', methods=['GET'])
|
||||
# def get_users():
|
||||
# try:
|
||||
# # INFO: Запрос на получение списка пользователей
|
||||
# app.logger.info("Запрос на получение информации о пользователях получен")
|
||||
#
|
||||
# with db_lock:
|
||||
# conn = sqlite3.connect(DB_PATH)
|
||||
# cursor = conn.cursor()
|
||||
#
|
||||
# # DEBUG: Запрос данных из таблицы whitelist
|
||||
# app.logger.debug("Запрос данных пользователей из таблицы whitelist")
|
||||
# cursor.execute('SELECT * FROM whitelist')
|
||||
# users = cursor.fetchall()
|
||||
# app.logger.debug("Формирование словаря пользователей")
|
||||
# users_dict = {user_id: {'id': user_id, 'username': username, 'email': email, 'events': [], 'worker': '',
|
||||
# 'subscriptions': []}
|
||||
# for user_id, username, email in users}
|
||||
#
|
||||
# # DEBUG: Запрос данных событий пользователей
|
||||
# app.logger.debug("Запрос событий пользователей из таблицы user_events")
|
||||
# cursor.execute('SELECT chat_id, username, action, timestamp FROM user_events')
|
||||
# events = cursor.fetchall()
|
||||
#
|
||||
# # DEBUG: Обработка событий и добавление их в словарь пользователей
|
||||
# for chat_id, username, action, timestamp in events:
|
||||
# if chat_id in users_dict:
|
||||
# event = {'type': action, 'date': timestamp}
|
||||
# if "Subscribed to region" in action:
|
||||
# region = action.split(": ")[-1]
|
||||
# event['region'] = region
|
||||
# users_dict[chat_id]['events'].append(event)
|
||||
#
|
||||
# # DEBUG: Запрос данных подписок пользователей
|
||||
# app.logger.debug("Запрос активных подписок пользователей из таблицы subscriptions")
|
||||
# cursor.execute('SELECT chat_id, region_id FROM subscriptions WHERE active = 1')
|
||||
# subscriptions = cursor.fetchall()
|
||||
#
|
||||
# # DEBUG: Добавление подписок к пользователям
|
||||
# for chat_id, region_id in subscriptions:
|
||||
# if chat_id in users_dict:
|
||||
# users_dict[chat_id]['subscriptions'].append(str(region_id))
|
||||
#
|
||||
# # INFO: Формирование результата
|
||||
# app.logger.info("Формирование результата для ответа")
|
||||
# result = []
|
||||
# for user in users_dict.values():
|
||||
# ordered_user = {
|
||||
# 'email': user['email'],
|
||||
# 'username': user['username'],
|
||||
# 'id': user['id'],
|
||||
# 'worker': user['worker'],
|
||||
# 'events': user['events'],
|
||||
# 'subscriptions': ', '.join(user['subscriptions'])
|
||||
# }
|
||||
# result.append(ordered_user)
|
||||
#
|
||||
# # INFO: Успешная отправка данных пользователей
|
||||
# app.logger.info("Информация о пользователях успешно отправлена")
|
||||
# return jsonify(result)
|
||||
#
|
||||
# except Exception as e:
|
||||
# # ERROR: Ошибка при получении информации о пользователях
|
||||
# app.logger.error(f"Ошибка при получении информации о пользователях: {str(e)}")
|
||||
# return jsonify({'status': 'error', 'message': str(e)}), 500
|
||||
|
||||
|
||||
@app.route(BASE_URL + '/debug/flask', methods=['POST'])
|
||||
|
||||
@ -8,7 +8,7 @@ from pyzabbix import ZabbixAPI
|
||||
|
||||
import backend_bot
|
||||
from config import ZABBIX_URL, ZABBIX_API_TOKEN
|
||||
from utils import show_main_menu
|
||||
from utilities.telegram_utilities import show_main_menu
|
||||
|
||||
|
||||
def get_triggers_for_group(chat_id, group_id):
|
||||
|
||||
@ -9,7 +9,14 @@ DB_PATH = 'db/telezab.db'
|
||||
SUPPORT_EMAIL = "shiftsupport-rtmis@rtmis.ru"
|
||||
BASE_URL = '/telezab'
|
||||
RABBITMQ_HOST = os.getenv('RABBITMQ_HOST')
|
||||
RABBITMQ_QUEUE = 'telegram_notifications'
|
||||
RABBITMQ_LOGIN = os.getenv('RABBITMQ_LOGIN')
|
||||
RABBITMQ_PASS = os.getenv('RABBITMQ_PASS')
|
||||
RABBITMQ_QUEUE = 'telegram_notifications'
|
||||
RABBITMQ_URL_FULL = f"amqp://{RABBITMQ_LOGIN}:{RABBITMQ_PASS}@{RABBITMQ_HOST}/"
|
||||
|
||||
import os
|
||||
|
||||
class Config:
|
||||
SQLALCHEMY_DATABASE_URI = f'sqlite:///{DB_PATH}'
|
||||
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||||
SECRET_KEY = os.environ.get('SECRET_KEY') or 'your-secret-key'
|
||||
177
frontend/dashboard.py
Normal file
177
frontend/dashboard.py
Normal file
@ -0,0 +1,177 @@
|
||||
from flask import Blueprint, render_template, jsonify, request, redirect, url_for
|
||||
from flask_login import login_required
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from sqlalchemy import create_engine, text
|
||||
from config import DB_PATH, BASE_URL
|
||||
from .models import Region # Импортируем модель региона
|
||||
|
||||
# Создаём Blueprint
|
||||
bp_dashboard = Blueprint('dashboard', __name__, url_prefix='/telezab/')
|
||||
bp_api = Blueprint('api', __name__, url_prefix='/telezab/rest/api')
|
||||
|
||||
db_engine = create_engine(f'sqlite:///{DB_PATH}')
|
||||
Session = sessionmaker(bind=db_engine)
|
||||
|
||||
# Роуты для отображения страниц
|
||||
@bp_dashboard.route('/')
|
||||
# @login_required
|
||||
def dashboard():
|
||||
return render_template('index.html')
|
||||
|
||||
@bp_dashboard.route('/users')
|
||||
# @login_required
|
||||
def users_page():
|
||||
return render_template('users.html')
|
||||
|
||||
@bp_dashboard.route('/logs')
|
||||
# @login_required
|
||||
def logs_page():
|
||||
return render_template('logs.html')
|
||||
|
||||
@bp_dashboard.route('/regions')
|
||||
# @login_required
|
||||
def regions_page():
|
||||
return render_template('regions.html')
|
||||
|
||||
# Роуты для API
|
||||
@bp_api.route('/users', methods=['GET'])
|
||||
def get_users():
|
||||
page = request.args.get('page', 1, type=int)
|
||||
per_page = request.args.get('per_page', 20, type=int)
|
||||
|
||||
session = Session()
|
||||
query = text("""
|
||||
SELECT w.chat_id, w.username, w.user_email ,s.region_id, s.disaster_only
|
||||
FROM whitelist w
|
||||
LEFT JOIN subscriptions s ON w.chat_id = s.chat_id AND s.active = 1
|
||||
""")
|
||||
|
||||
users = session.execute(query).fetchall()
|
||||
|
||||
# Если users пустые, выводим сообщение в консоль
|
||||
if not users:
|
||||
print("No users found")
|
||||
|
||||
# Группируем подписки по chat_id
|
||||
user_dict = {}
|
||||
for u in users:
|
||||
chat_id = u[0]
|
||||
if chat_id not in user_dict:
|
||||
disaster_only_text = "Только критические уведомления" if u[4] == 1 else "Все уведомления"
|
||||
# is_blocked = "Заблокирован" if u[5] == 1 else "Активен"
|
||||
user_dict[chat_id] = {
|
||||
'id': u[0],
|
||||
'username': u[1],
|
||||
'email': u[2],
|
||||
'subscriptions': [],
|
||||
'disaster_only': disaster_only_text,
|
||||
# 'status': is_blocked
|
||||
}
|
||||
if u[3]:
|
||||
user_dict[chat_id]['subscriptions'].append(u[3])
|
||||
|
||||
users_list = list(user_dict.values())
|
||||
total_users = len(users_list)
|
||||
total_pages = (total_users + per_page - 1) // per_page
|
||||
start = (page - 1) * per_page
|
||||
end = start + per_page
|
||||
users_page = users_list[start:end]
|
||||
|
||||
session.close()
|
||||
|
||||
return jsonify({
|
||||
'users': users_page,
|
||||
'total_users': total_users,
|
||||
'total_pages': total_pages,
|
||||
'current_page': page,
|
||||
'per_page': per_page
|
||||
})
|
||||
|
||||
@bp_api.route('/regions', methods=['GET', 'POST'])
|
||||
def manage_regions():
|
||||
session = Session()
|
||||
if request.method == 'POST':
|
||||
data = request.json
|
||||
region_id = data.get('region_id')
|
||||
name = data.get('name')
|
||||
active = data.get('active', True)
|
||||
|
||||
region = Region(region_id=region_id, region_name=name, active=active)
|
||||
session.add(region)
|
||||
session.commit()
|
||||
return jsonify({'status': 'success'})
|
||||
|
||||
regions = session.query(Region).all()
|
||||
session.close()
|
||||
return jsonify([{'region_id': r.region_id, 'name': r.region_name, 'active': r.active} for r in regions])
|
||||
|
||||
@bp_api.route('/regions/<int:region_id>', methods=['PUT', 'DELETE'])
|
||||
def edit_region(region_id):
|
||||
session = Session()
|
||||
region = session.query(Region).filter_by(region_id=region_id).first()
|
||||
|
||||
if request.method == 'PUT':
|
||||
data = request.json
|
||||
region.region_name = data.get('name', region.region_name)
|
||||
region.active = data.get('active', region.active)
|
||||
session.commit()
|
||||
session.close()
|
||||
return jsonify({'status': 'updated'})
|
||||
|
||||
elif request.method == 'DELETE':
|
||||
session.delete(region)
|
||||
session.commit()
|
||||
session.close()
|
||||
return jsonify({'status': 'deleted'})
|
||||
|
||||
@bp_api.route('/users/<int:user_id>', methods=['GET'])
|
||||
def get_user(user_id):
|
||||
session = Session()
|
||||
user = session.execute(text("SELECT * FROM whitelist WHERE chat_id = :id"), {'id': user_id}).fetchone()
|
||||
session.close()
|
||||
if not user:
|
||||
return jsonify({'error': 'Пользователь не найден'}), 404
|
||||
return jsonify({'id': user.chat_id, 'username': user.username, 'email': user.user_email, 'blocked': user.is_blocked})
|
||||
|
||||
# @bp_api.route('/users/<int:user_id>/block', methods=['POST'])
|
||||
# def block_user(user_id):
|
||||
# session = Session()
|
||||
# session.execute(text("UPDATE whitelist SET is_blocked = False WHERE chat_id = :id"), {'id': user_id})
|
||||
# session.commit()
|
||||
# session.close()
|
||||
# return jsonify({'status': 'updated'})
|
||||
@bp_api.route('/users/<int:user_id>/block', methods=['POST'])
|
||||
def block_user(user_id):
|
||||
session = Session()
|
||||
|
||||
# Получаем текущий статус блокировки пользователя
|
||||
result = session.execute(text("SELECT is_blocked FROM whitelist WHERE chat_id = :id"), {'id': user_id}).fetchone()
|
||||
|
||||
if result:
|
||||
is_blocked = result[0] # Текущее значение блокировки
|
||||
|
||||
# Если пользователь заблокирован, разблокируем его, если разблокирован - блокируем
|
||||
new_status = not is_blocked
|
||||
|
||||
# Обновляем статус блокировки в базе данных
|
||||
session.execute(
|
||||
text("UPDATE whitelist SET is_blocked = :new_status WHERE chat_id = :id"),
|
||||
{'new_status': new_status, 'id': user_id}
|
||||
)
|
||||
session.commit()
|
||||
|
||||
session.close()
|
||||
return jsonify({'status': 'updated', 'new_status': new_status})
|
||||
else:
|
||||
session.close()
|
||||
return jsonify({'status': 'error', 'message': 'User not found'}), 404
|
||||
|
||||
|
||||
|
||||
@bp_api.route('/users/<int:user_id>', methods=['DELETE'])
|
||||
def delete_user(user_id):
|
||||
session = Session()
|
||||
session.execute(text("DELETE FROM whitelist WHERE chat_id = :id"), {'id': user_id})
|
||||
session.commit()
|
||||
session.close()
|
||||
return jsonify({'status': 'deleted'})
|
||||
19
frontend/models.py
Normal file
19
frontend/models.py
Normal file
@ -0,0 +1,19 @@
|
||||
from utilities.database import db # Импортируем db из backend_flask.py
|
||||
|
||||
class User(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
username = db.Column(db.String(80), unique=True, nullable=False)
|
||||
is_blocked = db.Column(db.Boolean, default=False)
|
||||
actions = db.Column(db.String(500))
|
||||
subscriptions = db.Column(db.String(500))
|
||||
|
||||
class Region(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(80), nullable=False)
|
||||
active = db.Column(db.Boolean, default=True)
|
||||
|
||||
class Log(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
username = db.Column(db.String(80), nullable=False)
|
||||
action = db.Column(db.String(500), nullable=False)
|
||||
timestamp = db.Column(db.DateTime, default=db.func.current_timestamp())
|
||||
14
frontend/routes/auth.py
Normal file
14
frontend/routes/auth.py
Normal file
@ -0,0 +1,14 @@
|
||||
from flask import Blueprint, render_template, request, redirect, url_for
|
||||
|
||||
auth_bp = Blueprint('auth', __name__)
|
||||
|
||||
@auth_bp.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
if request.method == 'POST':
|
||||
# Обработка логики авторизации
|
||||
pass
|
||||
return render_template('login.html')
|
||||
|
||||
@auth_bp.route('/')
|
||||
def index():
|
||||
return redirect(url_for('auth.login'))
|
||||
9
frontend/routes/logs.py
Normal file
9
frontend/routes/logs.py
Normal file
@ -0,0 +1,9 @@
|
||||
from flask import Blueprint, render_template
|
||||
from frontend.models import Log
|
||||
|
||||
logs_bp = Blueprint('logs', __name__)
|
||||
|
||||
@logs_bp.route('/logs')
|
||||
def logs():
|
||||
logs = Log.query.all()
|
||||
return render_template('logs.html', logs=logs)
|
||||
9
frontend/routes/regions.py
Normal file
9
frontend/routes/regions.py
Normal file
@ -0,0 +1,9 @@
|
||||
from flask import Blueprint, render_template
|
||||
from frontend.models import Region
|
||||
|
||||
regions_bp = Blueprint('regions', __name__)
|
||||
|
||||
@regions_bp.route('/regions')
|
||||
def regions():
|
||||
regions = Region.query.all()
|
||||
return render_template('regions.html', regions=regions)
|
||||
9
frontend/routes/users.py
Normal file
9
frontend/routes/users.py
Normal file
@ -0,0 +1,9 @@
|
||||
from flask import Blueprint, render_template
|
||||
from frontend.models import User
|
||||
|
||||
users_bp = Blueprint('users', __name__)
|
||||
|
||||
@users_bp.route('/users')
|
||||
def users():
|
||||
user = User.query.all()
|
||||
return render_template('users.html', users=user)
|
||||
107
region_api.py
107
region_api.py
@ -1,107 +0,0 @@
|
||||
import sqlite3
|
||||
import logging
|
||||
from threading import Lock
|
||||
|
||||
db_lock = Lock()
|
||||
|
||||
# Инициализируем логгер
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.setLevel(logging.DEBUG) # Устанавливаем уровень логирования
|
||||
|
||||
|
||||
class RegionAPI:
|
||||
def __init__(self, db_path):
|
||||
self.db_path = db_path
|
||||
|
||||
def add_region(self, region_id: int, region_name: str):
|
||||
logger.info(f"Запрос на добавление региона: id={region_id}, name={region_name}")
|
||||
|
||||
# Проверка валидности region_id
|
||||
if not str(region_id).isdigit():
|
||||
logger.error(f"region_id {region_id} не является числом.")
|
||||
return {"status": "failure", "message": "Region_id must be digit only"}
|
||||
|
||||
with db_lock, sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
logger.debug(f"Проверка существования региона с id={region_id}")
|
||||
cursor.execute('SELECT COUNT(*) FROM regions WHERE region_id = ?', (region_id,))
|
||||
count = cursor.fetchone()[0]
|
||||
|
||||
if count == 0:
|
||||
# Добавляем новый регион
|
||||
cursor.execute('INSERT INTO regions (region_id, region_name, active) VALUES (?, ?, 1)',
|
||||
(region_id, region_name))
|
||||
conn.commit()
|
||||
logger.info(f"Регион с id={region_id} успешно добавлен.")
|
||||
return {"status": "success", "message": "Region added successfully"}
|
||||
else:
|
||||
logger.warning(f"Регион с id={region_id} уже существует.")
|
||||
return {"status": "error", "message": "Region already exists"}
|
||||
|
||||
def remove_region(self, region_id):
|
||||
logger.info(f"Запрос на удаление региона: id={region_id}")
|
||||
|
||||
with db_lock, sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
logger.debug(f"Проверка существования региона с id={region_id}")
|
||||
cursor.execute('SELECT COUNT(*) FROM regions WHERE region_id = ?', (region_id,))
|
||||
count = cursor.fetchone()[0]
|
||||
|
||||
if count == 0:
|
||||
logger.warning(f"Регион с id={region_id} не найден.")
|
||||
return {"status": "error", "message": "Region not found"}
|
||||
else:
|
||||
cursor.execute('DELETE FROM regions WHERE region_id = ?', (region_id,))
|
||||
conn.commit()
|
||||
logger.info(f"Регион с id={region_id} успешно удалён.")
|
||||
return {"status": "success", "message": "Region removed successfully"}
|
||||
|
||||
def get_regions(self):
|
||||
logger.info("Запрос на получение списка регионов.")
|
||||
|
||||
with db_lock, sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
logger.debug("Извлечение данных из таблицы regions.")
|
||||
cursor.execute('SELECT region_id, region_name, active FROM regions')
|
||||
regions = cursor.fetchall()
|
||||
logger.info(f"Получено {len(regions)} регионов.")
|
||||
return [{"region_id": r[0], "region_name": r[1], "regions_active": r[2]} for r in regions]
|
||||
|
||||
def change_region_status(self, region_id, active):
|
||||
logger.info(f"Запрос на изменение статуса региона: id={region_id}, статус={active}")
|
||||
|
||||
with db_lock, sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
logger.debug(f"Проверка существования региона с id={region_id}")
|
||||
cursor.execute('SELECT COUNT(*) FROM regions WHERE region_id = ?', (region_id,))
|
||||
count = cursor.fetchone()[0]
|
||||
|
||||
if count == 0:
|
||||
logger.warning(f"Регион с id={region_id} не найден.")
|
||||
return {"status": "error", "message": "Region not found"}
|
||||
else:
|
||||
cursor.execute('UPDATE regions SET active = ? WHERE region_id = ?', (active, region_id))
|
||||
conn.commit()
|
||||
logger.info(f"Статус региона с id={region_id} успешно изменён.")
|
||||
return {"status": "success", "message": "Region status updated successfully"}
|
||||
|
||||
def update_region_status(self, region_id, active):
|
||||
logger.info(f"Запрос на обновление статуса региона: id={region_id}, активность={active}")
|
||||
|
||||
with db_lock, sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Проверяем существование региона
|
||||
logger.debug(f"Проверка существования региона с id={region_id}")
|
||||
cursor.execute("SELECT region_name FROM regions WHERE region_id = ?", (region_id,))
|
||||
result = cursor.fetchone()
|
||||
if not result:
|
||||
logger.warning(f"Регион с id={region_id} не найден.")
|
||||
return {"status": "error", "message": "Регион не найден"}
|
||||
|
||||
# Обновляем статус активности региона
|
||||
cursor.execute("UPDATE regions SET active = ? WHERE region_id = ?", (int(active), region_id))
|
||||
conn.commit()
|
||||
action = "Активирован" if active else "Отключён"
|
||||
logger.info(f"Регион с id={region_id} {action}.")
|
||||
return {"status": "success", "message": f"Регион {region_id} {action}"}
|
||||
@ -11,10 +11,16 @@ click==8.1.8
|
||||
colorama==0.4.6
|
||||
exceptiongroup==1.2.2
|
||||
Flask==3.1.0
|
||||
flask-ldap3-login==1.0.2
|
||||
Flask-Login==0.6.3
|
||||
Flask-SQLAlchemy==3.1.1
|
||||
Flask-WTF==1.2.2
|
||||
frozenlist==1.5.0
|
||||
greenlet==3.1.1
|
||||
idna==3.10
|
||||
itsdangerous==2.2.0
|
||||
Jinja2==3.1.5
|
||||
ldap3==2.9.1
|
||||
MarkupSafe==3.0.2
|
||||
multidict==6.1.0
|
||||
packaging==24.2
|
||||
@ -22,13 +28,17 @@ pamqp==3.3.0
|
||||
pika==1.3.2
|
||||
pika-stubs==0.1.3
|
||||
propcache==0.2.1
|
||||
pyasn1==0.6.1
|
||||
pyTelegramBotAPI==4.26.0
|
||||
python-dotenv==1.0.1
|
||||
pytz==2025.1
|
||||
pyzabbix==1.3.1
|
||||
requests==2.32.3
|
||||
schedule==1.2.2
|
||||
SQLAlchemy==2.0.38
|
||||
telebot==0.0.5
|
||||
typing_extensions==4.12.2
|
||||
urllib3==2.3.0
|
||||
Werkzeug==3.1.3
|
||||
WTForms==3.2.1
|
||||
yarl==1.18.3
|
||||
|
||||
@ -1,25 +1 @@
|
||||
/* Добавим выравнивание и отступы для кнопок управления */
|
||||
.table-hover tbody tr td {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.table-hover tbody tr td .btn-manage {
|
||||
margin-left: 20px; /* Отступ кнопки от названия региона */
|
||||
}
|
||||
|
||||
th, td {
|
||||
text-align: left; /* Выровнять содержимое слева */
|
||||
}
|
||||
|
||||
.table-hover tbody tr {
|
||||
height: 50px; /* Сделать строки таблицы выше для лучшего визуального эффекта */
|
||||
}
|
||||
|
||||
.modal-footer .btn {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
/* Стили для пагинации */
|
||||
.d-flex .btn {
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
@ -1,55 +1,173 @@
|
||||
$(document).ready(function() {
|
||||
// Получаем список сотрудников
|
||||
$.getJSON('/telezab/users/get', function(data) {
|
||||
var userList = $('#user-list');
|
||||
userList.empty();
|
||||
data.forEach(function(user) {
|
||||
var email = user.email;
|
||||
var name = email.split('@')[0].replace(/\./g, ' ').replace(/\b\w/g, char => char.toUpperCase());
|
||||
var listItem = $('<li class="list-group-item"></li>').text(name).data('user', user);
|
||||
userList.append(listItem);
|
||||
let currentPage = 1;
|
||||
let totalPages = 1;
|
||||
const perPage = 20;
|
||||
|
||||
// Функция загрузки пользователей
|
||||
function loadUsers(page) {
|
||||
if (page < 1 || page > totalPages) return;
|
||||
currentPage = page;
|
||||
|
||||
fetch(`/telezab/rest/api/users?page=${currentPage}&per_page=${perPage}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
totalPages = data.total_pages;
|
||||
updateUsersTable(data.users);
|
||||
updatePagination(data.current_page, data.total_pages);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error fetching users:', error);
|
||||
});
|
||||
}
|
||||
|
||||
// Функция обновления таблицы пользователей
|
||||
function updateUsersTable(users) {
|
||||
const tableBody = document.getElementById('users-table').querySelector('tbody');
|
||||
tableBody.innerHTML = '';
|
||||
|
||||
users.forEach(user => {
|
||||
const row = document.createElement('tr');
|
||||
row.innerHTML = `
|
||||
<td>${user.id}</td>
|
||||
<td>${user.username}</td>
|
||||
<td>${user.email}</td>
|
||||
<td>${user.subscriptions.join(', ') || 'Нет подписок'}</td>
|
||||
<td>${user.disaster_only}</td>
|
||||
<td>${user.status}</td>
|
||||
<td><button class="btn btn-primary editUserBtn" data-id="${user.id}">Редактировать</button></td>
|
||||
`;
|
||||
tableBody.appendChild(row);
|
||||
});
|
||||
|
||||
// Обработчик кликов по пользователям
|
||||
$('#user-list').on('click', '.list-group-item', function() {
|
||||
// Удаляем активный класс у всех элементов списка
|
||||
$('.list-group-item').removeClass('active');
|
||||
setupEditButtons();
|
||||
}
|
||||
|
||||
// Добавляем активный класс к выбранному элементу
|
||||
$(this).addClass('active');
|
||||
|
||||
var user = $(this).data('user');
|
||||
$('#user-info').removeClass('d-none');
|
||||
$('#user-name').text(user.email.split('@')[0].replace(/\./g, ' ').replace(/\b\w/g, char => char.toUpperCase()));
|
||||
|
||||
// Отображаем регионы в одну строку
|
||||
var regions = $('#user-regions');
|
||||
regions.empty();
|
||||
if (user.subscriptions) {
|
||||
var subscriptions = user.subscriptions.split(',').map(function(sub) { return sub.trim(); });
|
||||
if (subscriptions.length > 0) {
|
||||
regions.text(subscriptions.join(', '));
|
||||
} else {
|
||||
regions.text('Нет подписок');
|
||||
}
|
||||
} else {
|
||||
regions.text('Нет подписок');
|
||||
}
|
||||
|
||||
// Отображаем действия
|
||||
var events = $('#user-events');
|
||||
events.empty();
|
||||
if (user.events && user.events.length > 0) {
|
||||
user.events.forEach(function(event) {
|
||||
var eventText = event.type;
|
||||
if (event.region) {
|
||||
eventText += ' (Регион: ' + event.region + ')';
|
||||
}
|
||||
events.append('<div><strong>' + event.date + '</strong> - ' + eventText + '</div>');
|
||||
// Функция для обработки кнопок "Редактировать"
|
||||
function setupEditButtons() {
|
||||
document.querySelectorAll(".editUserBtn").forEach(button => {
|
||||
button.addEventListener("click", function () {
|
||||
const userId = this.dataset.id;
|
||||
openUserModal(userId);
|
||||
});
|
||||
} else {
|
||||
events.append('<div>Нет действий</div>');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Функция открытия модального окна
|
||||
function openUserModal(userId) {
|
||||
fetch(`/telezab/rest/api/users/${userId}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
document.getElementById("userId").innerText = data.id;
|
||||
document.getElementById("username").innerText = data.username;
|
||||
document.getElementById("userEmail").innerText = data.email;
|
||||
|
||||
const blockBtn = document.getElementById("toggleBlockUser");
|
||||
blockBtn.innerText = data.blocked ? "Разблокировать" : "Заблокировать";
|
||||
blockBtn.onclick = function () {
|
||||
toggleUserBlock(userId);
|
||||
};
|
||||
|
||||
document.getElementById("viewUserEvents").onclick = function () {
|
||||
viewUserEvents(userId);
|
||||
};
|
||||
|
||||
document.getElementById("deleteUser").onclick = function () {
|
||||
deleteUser(userId);
|
||||
};
|
||||
|
||||
// Использование Bootstrap для показа модального окна
|
||||
var myModal = new bootstrap.Modal(document.getElementById('userModal'), {
|
||||
keyboard: false
|
||||
});
|
||||
myModal.show(); // Открытие модального окна
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error fetching user data:', error);
|
||||
});
|
||||
}
|
||||
|
||||
// Обработчик закрытия модального окна
|
||||
document.querySelector(".btn-close").addEventListener("click", function () {
|
||||
var myModal = new bootstrap.Modal(document.getElementById('userModal'));
|
||||
myModal.hide(); // Закрытие модального окна
|
||||
});
|
||||
|
||||
|
||||
// Функция для блокировки/разблокировки пользователя
|
||||
function toggleUserBlock(userId) {
|
||||
fetch(`/telezab/rest/api/users/${userId}/block`, { method: "POST" })
|
||||
.then(() => {
|
||||
alert("Статус пользователя изменён");
|
||||
|
||||
// Скрыть модальное окно с помощью Bootstrap (это не отменяет событий закрытия)
|
||||
var myModal = new bootstrap.Modal(document.getElementById('userModal'));
|
||||
myModal.hide(); // Закрытие модального окна
|
||||
|
||||
loadUsers(currentPage); // Перезагрузите список пользователей
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Ошибка при изменении статуса пользователя:", error);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Функция просмотра действий пользователя
|
||||
function viewUserEvents(userId) {
|
||||
window.location.href = `/telezab/rest/api/users/${userId}/events`;
|
||||
}
|
||||
|
||||
// Функция для удаления пользователя
|
||||
function deleteUser(userId) {
|
||||
if (confirm("Вы уверены, что хотите удалить пользователя?")) {
|
||||
fetch(`/telezab/rest/api/users/${userId}`, { method: "DELETE" })
|
||||
.then(() => {
|
||||
alert("Пользователь удалён");
|
||||
var myModal = new bootstrap.Modal(document.getElementById('userModal'));
|
||||
myModal.hide();
|
||||
loadUsers(currentPage); // Перезагрузите список пользователей
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
window.onclick = function (event) {
|
||||
if (event.target === document.getElementById("userModal")) {
|
||||
$('#userModal').modal('hide'); // Закрываем модальное окно с помощью Bootstrap
|
||||
}
|
||||
};
|
||||
|
||||
// Функция обновления пагинации
|
||||
function updatePagination(currentPage, totalPages) {
|
||||
const paginationContainer = document.getElementById('pagination');
|
||||
paginationContainer.innerHTML = '';
|
||||
|
||||
const prevButton = document.createElement('li');
|
||||
prevButton.classList.add('page-item');
|
||||
prevButton.classList.toggle('disabled', currentPage === 1);
|
||||
prevButton.innerHTML = `<a class="page-link" href="#" aria-label="Previous" onclick="loadUsers(${currentPage - 1})">«</a>`;
|
||||
paginationContainer.appendChild(prevButton);
|
||||
|
||||
for (let page = 1; page <= totalPages; page++) {
|
||||
const pageItem = document.createElement('li');
|
||||
pageItem.classList.add('page-item');
|
||||
pageItem.classList.toggle('active', page === currentPage);
|
||||
|
||||
const pageLink = document.createElement('a');
|
||||
pageLink.classList.add('page-link');
|
||||
pageLink.href = "#";
|
||||
pageLink.textContent = page;
|
||||
pageLink.onclick = () => loadUsers(page);
|
||||
|
||||
pageItem.appendChild(pageLink);
|
||||
paginationContainer.appendChild(pageItem);
|
||||
}
|
||||
|
||||
const nextButton = document.createElement('li');
|
||||
nextButton.classList.add('page-item');
|
||||
nextButton.classList.toggle('disabled', currentPage === totalPages);
|
||||
nextButton.innerHTML = `<a class="page-link" href="#" aria-label="Next" onclick="loadUsers(${currentPage + 1})">»</a>`;
|
||||
paginationContainer.appendChild(nextButton);
|
||||
}
|
||||
|
||||
// Запуск загрузки данных
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
loadUsers(currentPage);
|
||||
});
|
||||
|
||||
28
telezab.py
28
telezab.py
@ -2,11 +2,9 @@ import asyncio
|
||||
import logging
|
||||
import sqlite3
|
||||
from threading import Thread
|
||||
|
||||
import telebot
|
||||
from pyzabbix import ZabbixAPI
|
||||
from telebot import types
|
||||
|
||||
import backend_bot
|
||||
import bot_database
|
||||
from backend_flask import app
|
||||
@ -14,13 +12,14 @@ from backend_locks import bot
|
||||
from backend_locks import db_lock
|
||||
from backend_zabbix import get_triggers_for_group, get_triggers_for_all_groups
|
||||
from config import *
|
||||
from log_manager import LogManager
|
||||
from rabbitmq import consume_from_queue
|
||||
from user_state_manager import UserStateManager
|
||||
from utils import show_main_menu, show_settings_menu
|
||||
from utilities.log_manager import LogManager
|
||||
from utilities.rabbitmq import consume_from_queue
|
||||
from utilities.telegram_utilities import show_main_menu, show_settings_menu
|
||||
from utilities.user_state_manager import UserStateManager
|
||||
|
||||
|
||||
# Инициализируем класс UserStateManager
|
||||
user_state_manager = UserStateManager()
|
||||
state = UserStateManager()
|
||||
|
||||
# Инициализация LogManager
|
||||
log_manager = LogManager(log_dir='logs', retention_days=30)
|
||||
@ -31,12 +30,6 @@ telebot.logger = logging.getLogger('telebot')
|
||||
# Важно: вызов schedule_log_rotation для планировки ротации и архивации логов
|
||||
log_manager.schedule_log_rotation()
|
||||
|
||||
|
||||
# # Lock for database operations
|
||||
# db_lock = Lock()
|
||||
|
||||
# 25 messages per second
|
||||
|
||||
# Handle /help command to provide instructions
|
||||
@bot.message_handler(commands=['help'])
|
||||
def handle_help(message):
|
||||
@ -92,7 +85,7 @@ def handle_menu_selection(message):
|
||||
backend_bot.bot.send_message(chat_id, "Вы не авторизованы для использования этого бота.")
|
||||
return
|
||||
# Получаем текущее состояние пользователя
|
||||
current_state = user_state_manager.get_state(chat_id)
|
||||
current_state = state.get_state(chat_id)
|
||||
# Обработка команд в зависимости от состояния
|
||||
if current_state == "MAIN_MENU":
|
||||
backend_bot.handle_main_menu(message, chat_id, text)
|
||||
@ -116,7 +109,7 @@ def handle_cancel_action(call):
|
||||
backend_bot.bot.clear_step_handler_by_chat_id(chat_id)
|
||||
backend_bot.bot.send_message(chat_id, f"Действие отменено")
|
||||
backend_bot.bot.edit_message_reply_markup(chat_id, message_id, reply_markup=None)
|
||||
user_state_manager.set_state(chat_id, "SETTINGS_MENU")
|
||||
state.set_state(chat_id, "SETTINGS_MENU")
|
||||
show_settings_menu(chat_id)
|
||||
return
|
||||
|
||||
@ -128,7 +121,7 @@ def handle_cancel_active_triggers(call):
|
||||
backend_bot.bot.clear_step_handler_by_chat_id(chat_id)
|
||||
backend_bot.bot.send_message(chat_id, f"Действие отменено")
|
||||
backend_bot.bot.edit_message_reply_markup(chat_id, message_id, reply_markup=None)
|
||||
user_state_manager.set_state(chat_id, "MAIN_MENU")
|
||||
state.set_state(chat_id, "MAIN_MENU")
|
||||
show_main_menu(chat_id)
|
||||
return
|
||||
|
||||
@ -229,7 +222,7 @@ def handle_notification_mode_selection(call):
|
||||
telebot.logger.info(f"Notification mode for user ({chat_id}) updated to: {mode_text}")
|
||||
|
||||
# Логируем изменение состояния пользователя
|
||||
user_state_manager.set_state(chat_id, "SETTINGS_MENU")
|
||||
state.set_state(chat_id, "SETTINGS_MENU")
|
||||
telebot.logger.debug(f"User state for {chat_id} set to SETTINGS_MENU.")
|
||||
|
||||
# Показываем меню настроек
|
||||
@ -381,7 +374,6 @@ def run_flask():
|
||||
def main():
|
||||
# Инициализация базы данных
|
||||
bot_database.init_db()
|
||||
|
||||
# Запуск Flask и бота в отдельных потоках
|
||||
Thread(target=run_flask, daemon=True).start()
|
||||
Thread(target=run_polling, daemon=True).start()
|
||||
|
||||
44
templates/base.html
Normal file
44
templates/base.html
Normal file
@ -0,0 +1,44 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{% block title %}Dashboard{% endblock %}</title>
|
||||
<link href="{{ url_for('static', filename='css/bootstrap.min.css') }}" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<!-- Навигационное меню -->
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="#">Dashboard</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"
|
||||
aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.endpoint == 'dashboard.dashboard' %}active{% endif %}" href="{{ url_for('dashboard.dashboard') }}">Главная</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.endpoint == 'dashboard.users_page' %}active{% endif %}" href="{{ url_for('dashboard.users_page') }}">Пользователи</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.endpoint == 'dashboard.regions_page' %}active{% endif %}" href="{{ url_for('dashboard.regions_page') }}">Регионы</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.endpoint == 'dashboard.logs_page' %}active{% endif %}" href="{{ url_for('dashboard.logs_page') }}">Логи</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Основной контент -->
|
||||
<div class="container mt-4">
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
<script src="{{ url_for('static', filename='js/jquery-3.6.0.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/bootstrap.bundle.min.js') }}"></script>
|
||||
</body>
|
||||
</html>
|
||||
145
templates/dashboard.html
Normal file
145
templates/dashboard.html
Normal file
@ -0,0 +1,145 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Dashboard</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container mt-4">
|
||||
<h1 class="mb-4">Панель управления</h1>
|
||||
<!-- Таблица пользователей -->
|
||||
<h2>Пользователи</h2>
|
||||
<table id="users-table" class="table table-bordered table-striped">
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Имя</th>
|
||||
<th>Email</th>
|
||||
<th>Подписки</th>
|
||||
<th>Режим</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- Данные загружаются через JS -->
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- Контейнер пагинации -->
|
||||
<nav aria-label="Page navigation example">
|
||||
<ul class="pagination" id="pagination">
|
||||
<!-- Кнопка предыдущей страницы -->
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="#" aria-label="Previous" onclick="loadUsers(currentPage - 1)">
|
||||
<span aria-hidden="true">«</span>
|
||||
<span class="sr-only">Previous</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!-- Номера страниц (будет динамически генерироваться) -->
|
||||
<!-- Пример -->
|
||||
<!-- <li class="page-item"><a class="page-link" href="#">1</a></li> -->
|
||||
|
||||
<!-- Кнопка следующей страницы -->
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="#" aria-label="Next" onclick="loadUsers(currentPage + 1)">
|
||||
<span aria-hidden="true">»</span>
|
||||
<span class="sr-only">Next</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<script src="{{ url_for('static', filename='js/jquery-3.6.0.min.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/bootstrap.bundle.min.js') }}"></script>
|
||||
{#<script src="{{ url_for('static', filename='js/regions.js') }}"></script>#}
|
||||
<script>
|
||||
let currentPage = 1; // Переменная для текущей страницы
|
||||
let totalPages = 1; // Переменная для общего количества страниц (инициализируем значением 1 по умолчанию)
|
||||
const perPage = 20; // Количество элементов на странице
|
||||
|
||||
// Функция для загрузки данных
|
||||
function loadUsers(page) {
|
||||
if (page < 1 || page > totalPages) return; // Проверка на допустимые страницы
|
||||
currentPage = page; // Обновляем текущую страницу
|
||||
|
||||
// Получаем данные с сервера
|
||||
fetch(`/telezab/dashboard/users?page=${currentPage}&per_page=${perPage}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
totalPages = data.total_pages; // Обновляем totalPages из ответа сервера
|
||||
updateUsersTable(data.users); // Обновляем таблицу пользователей
|
||||
updatePagination(data.current_page, data.total_pages); // Обновляем пагинацию
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error fetching users:', error);
|
||||
});
|
||||
}
|
||||
|
||||
// Функция для обновления таблицы пользователей
|
||||
function updateUsersTable(users) {
|
||||
const tableBody = document.getElementById('users-table').querySelector('tbody');
|
||||
tableBody.innerHTML = ''; // Очищаем таблицу
|
||||
|
||||
users.forEach(user => {
|
||||
const row = document.createElement('tr');
|
||||
row.innerHTML = `
|
||||
<td>${user.id}</td>
|
||||
<td>${user.username}</td>
|
||||
<td>${user.email}</td>
|
||||
<td>${user.subscriptions.join(', ') || 'Нет подписок'}</td>
|
||||
<td>${user.disaster_only}</td>
|
||||
`;
|
||||
tableBody.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
// Функция для обновления пагинации
|
||||
function updatePagination(currentPage, totalPages) {
|
||||
const paginationContainer = document.getElementById('pagination');
|
||||
paginationContainer.innerHTML = ''; // Очищаем пагинацию
|
||||
|
||||
// Кнопка "Предыдущая" (слева)
|
||||
const prevButton = document.createElement('li');
|
||||
prevButton.classList.add('page-item');
|
||||
prevButton.classList.toggle('disabled', currentPage === 1); // Отключить кнопку если текущая страница - 1
|
||||
prevButton.innerHTML = `<a class="page-link" href="#" aria-label="Previous" onclick="loadUsers(${currentPage - 1})">
|
||||
<span aria-hidden="true">«</span>
|
||||
<span class="sr-only">Previous</span>
|
||||
</a>`;
|
||||
paginationContainer.appendChild(prevButton);
|
||||
|
||||
// Генерация кнопок с номерами страниц
|
||||
for (let page = 1; page <= totalPages; page++) {
|
||||
const pageItem = document.createElement('li');
|
||||
pageItem.classList.add('page-item');
|
||||
pageItem.classList.toggle('active', page === currentPage); // Выделяем текущую страницу
|
||||
|
||||
const pageLink = document.createElement('a');
|
||||
pageLink.classList.add('page-link');
|
||||
pageLink.href = "#";
|
||||
pageLink.textContent = page;
|
||||
pageLink.onclick = () => loadUsers(page);
|
||||
|
||||
pageItem.appendChild(pageLink);
|
||||
paginationContainer.appendChild(pageItem);
|
||||
}
|
||||
|
||||
// Кнопка "Следующая" (справа)
|
||||
const nextButton = document.createElement('li');
|
||||
nextButton.classList.add('page-item');
|
||||
nextButton.classList.toggle('disabled', currentPage === totalPages); // Отключить кнопку если текущая страница - последняя
|
||||
nextButton.innerHTML = `<a class="page-link" href="#" aria-label="Next" onclick="loadUsers(${currentPage + 1})">
|
||||
<span aria-hidden="true">»</span>
|
||||
<span class="sr-only">Next</span>
|
||||
</a>`;
|
||||
paginationContainer.appendChild(nextButton);
|
||||
}
|
||||
|
||||
// Инициализация загрузки пользователей на первой странице
|
||||
loadUsers(currentPage);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,10 +1,55 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Index of WebUI for Telezab</title>
|
||||
</head>
|
||||
<body>
|
||||
THIS IS WEBUI FOR TELEZAB BOT
|
||||
</body>
|
||||
</html>
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Dashboard Home{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1 class="text-center mb-4">Добро пожаловать в Dashboard</h1>
|
||||
<p class="text-center">Выберите один из разделов:</p>
|
||||
|
||||
<div class="row">
|
||||
<!-- Карточка пользователей -->
|
||||
<div class="col-md-4">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body text-center">
|
||||
<h5 class="card-title">Пользователи</h5>
|
||||
<p class="card-text">Просмотр и управление пользователями.</p>
|
||||
<a href="{{ url_for('dashboard.users_page') }}" class="btn btn-primary">Перейти</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Карточка регионов -->
|
||||
<div class="col-md-4">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body text-center">
|
||||
<h5 class="card-title">Регионы</h5>
|
||||
<p class="card-text">Просмотр и настройка регионов.</p>
|
||||
<a href="{{ url_for('dashboard.regions_page') }}" class="btn btn-primary">Перейти</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Карточка логов -->
|
||||
<div class="col-md-4">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body text-center">
|
||||
<h5 class="card-title">Логи</h5>
|
||||
<p class="card-text">Просмотр событий и логов.</p>
|
||||
<a href="{{ url_for('dashboard.logs_page') }}" class="btn btn-primary">Перейти</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Карточка Teams Chat -->
|
||||
<div class="col-md-4">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body text-center">
|
||||
<h5 class="card-title">Создание чатов Teams</h5>
|
||||
<p class="card-text">Создание чатов в VK Teams в случае аварий</p>
|
||||
<a href="{{ url_for('dashboard.logs_page') }}" class="btn btn-primary">Перейти</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
20
templates/login.html
Normal file
20
templates/login.html
Normal file
@ -0,0 +1,20 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2>Login</h2>
|
||||
<form method="POST" action="{{ url_for('login') }}">
|
||||
<div class="form-group">
|
||||
<label for="username">Username</label>
|
||||
<input type="text" class="form-control" id="username" name="username" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">Password</label>
|
||||
<input type="password" class="form-control" id="password" name="password" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Login</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
10
templates/logs.html
Normal file
10
templates/logs.html
Normal file
@ -0,0 +1,10 @@
|
||||
<!-- templates/logs.html -->
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Логи{% endblock %}
|
||||
{% block content %}
|
||||
<h2>Логи</h2>
|
||||
<pre id="logs-container"></pre>
|
||||
{% endblock %}
|
||||
{% block scripts %}
|
||||
<script src="{{ url_for('static', filename='js/logs.js') }}"></script>
|
||||
{% endblock %}
|
||||
@ -1,53 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Region Management</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/bootstrap.min.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/regions.css') }}">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container mt-4">
|
||||
<div class="row">
|
||||
<!-- Левый контейнер со списком регионов -->
|
||||
<div class="col-md-6">
|
||||
<h3>Список Регионов</h3>
|
||||
<div class="list-group" id="region-list">
|
||||
<!-- Список регионов будет добавляться сюда -->
|
||||
</div>
|
||||
<!-- Пагинация -->
|
||||
<nav aria-label="Page navigation">
|
||||
<ul class="pagination" id="pagination">
|
||||
<!-- Кнопки пагинации будут добавляться сюда -->
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
<!-- Правый контейнер с информацией о пользователях -->
|
||||
<div class="col-md-6">
|
||||
<h3>Пользователи</h3>
|
||||
<div id="curent-region">
|
||||
|
||||
</div>
|
||||
<div id="user-info">
|
||||
<!-- Информация о пользователях будет добавляться сюда -->
|
||||
</div>
|
||||
<ul id="user-list">
|
||||
<!-- Список пользователей будет добавляться сюда -->
|
||||
</ul>
|
||||
<!-- Кнопки управления регионами -->
|
||||
|
||||
<div class="d-flex justify-content-end mt-3">
|
||||
<button type="button" class="btn btn-danger me-2" id="delete-region">Удалить регион</button>
|
||||
<button type="button" class="btn btn-warning me-2" id="disable-region">Отключить регион</button>
|
||||
<button type="button" class="btn btn-success" id="activate-region">Активировать регион</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="{{ url_for('static', filename='js/jquery-3.6.0.min.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/bootstrap.bundle.min.js') }}"></script>
|
||||
<!-- templates/regions.html -->
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Регионы{% endblock %}
|
||||
{% block content %}
|
||||
<h2>Регионы</h2>
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Название</th>
|
||||
<th>Активен</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="regions-table">
|
||||
</tbody>
|
||||
</table>
|
||||
{% endblock %}
|
||||
{% block scripts %}
|
||||
<script src="{{ url_for('static', filename='js/regions.js') }}"></script>
|
||||
</body>
|
||||
</html>
|
||||
{% endblock %}
|
||||
@ -1,37 +1,64 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Сотрудники</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/bootstrap.min.css') }}">
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<head>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/users.css') }}">
|
||||
<script src="{{ url_for('static', filename='js/jquery-3.6.0.min.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/bootstrap.bundle.min.js') }}"></script>
|
||||
<title>Пользователи</title>
|
||||
</head>
|
||||
<div class="container">
|
||||
<h1>Пользователи</h1>
|
||||
|
||||
<!-- Таблица пользователей -->
|
||||
<table class="table" id="users-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Chat ID</th>
|
||||
<th>Telegram ID</th>
|
||||
<th>Email</th>
|
||||
<th>Подписки</th>
|
||||
<th>Тип уведомлений</th>
|
||||
<th>Статус</th>
|
||||
<th>Действия</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- Данные будут загружаться сюда динамически -->
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- Пагинация -->
|
||||
<nav>
|
||||
<ul class="pagination" id="pagination">
|
||||
<!-- Страницы будут генерироваться динамически -->
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- Модальное окно -->
|
||||
<div id="userModal" class="modal fade" tabindex="-1" aria-labelledby="userModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="userModalLabel">Редактирование пользователя</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Chat ID: <span id="userId"></span></p>
|
||||
<p>Telegram ID: <span id="username"></span></p>
|
||||
<p>Email: <span id="userEmail"></span></p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button id="toggleBlockUser" class="btn btn-warning">Заблокировать</button>
|
||||
<button id="viewUserEvents" class="btn btn-info">Посмотреть действия</button>
|
||||
<button id="deleteUser" class="btn btn-danger">Удалить</button>
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Закрыть</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Подключаем внешний JavaScript файл -->
|
||||
<script src="{{ url_for('static', filename='js/users.js') }}"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container mt-4">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<ul id="user-list" class="list-group">
|
||||
<!-- Список сотрудников будет вставлен сюда -->
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<div id="user-info" class="d-none">
|
||||
<h3 id="user-name"></h3>
|
||||
<h5>Подписки на регионы</h5>
|
||||
<ul id="user-regions" class="list-group">
|
||||
<!-- Подписки на регионы будут вставлены сюда -->
|
||||
</ul>
|
||||
<h5>Действия</h5>
|
||||
<div id="user-events" class="overflow-auto" style="max-height: 300px;">
|
||||
<!-- Действия будут вставлены сюда -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
37
templates/users_old.html
Normal file
37
templates/users_old.html
Normal file
@ -0,0 +1,37 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Сотрудники</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/bootstrap.min.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/users.css') }}">
|
||||
<script src="{{ url_for('static', filename='js/jquery-3.6.0.min.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/bootstrap.bundle.min.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/users.js') }}"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container mt-4">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<ul id="user-list" class="list-group">
|
||||
<!-- Список сотрудников будет вставлен сюда -->
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<div id="user-info" class="d-none">
|
||||
<h3 id="user-name"></h3>
|
||||
<h5>Подписки на регионы</h5>
|
||||
<ul id="user-regions" class="list-group">
|
||||
<!-- Подписки на регионы будут вставлены сюда -->
|
||||
</ul>
|
||||
<h5>Действия</h5>
|
||||
<div id="user-events" class="overflow-auto" style="max-height: 300px;">
|
||||
<!-- Действия будут вставлены сюда -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
3
utilities/database.py
Normal file
3
utilities/database.py
Normal file
@ -0,0 +1,3 @@
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
|
||||
db = SQLAlchemy() # Создаем экземпляр SQLAlchemy
|
||||
@ -62,12 +62,12 @@ class LogManager:
|
||||
},
|
||||
'handlers': {
|
||||
'telebot_console': {
|
||||
'class': 'log_manager.UTF8StreamHandler',
|
||||
'class': 'utilities.log_manager.UTF8StreamHandler',
|
||||
'stream': 'ext://sys.stdout',
|
||||
'formatter': 'default',
|
||||
},
|
||||
'flask_console': {
|
||||
'class': 'log_manager.UTF8StreamHandler',
|
||||
'class': 'utilities.log_manager.UTF8StreamHandler',
|
||||
'stream': 'ext://sys.stdout',
|
||||
'formatter': 'werkzeug',
|
||||
},
|
||||
@ -91,10 +91,10 @@ def escape_telegram_chars(text):
|
||||
def show_main_menu(chat_id):
|
||||
markup = telebot.types.ReplyKeyboardMarkup(one_time_keyboard=True, resize_keyboard=True)
|
||||
if bot_database.is_whitelisted(chat_id):
|
||||
telezab.user_state_manager.set_state(chat_id, "MAIN_MENU")
|
||||
telezab.state.set_state(chat_id, "MAIN_MENU")
|
||||
markup.add('Настройки', 'Помощь', 'Активные события')
|
||||
else:
|
||||
telezab.user_state_manager.set_state(chat_id, "REGISTRATION")
|
||||
telezab.state.set_state(chat_id, "REGISTRATION")
|
||||
markup.add('Регистрация')
|
||||
backend_bot.bot.send_message(chat_id, "Выберите действие:", reply_markup=markup)
|
||||
|
||||
@ -109,7 +109,7 @@ def create_settings_keyboard(chat_id, admins_list):
|
||||
|
||||
def show_settings_menu(chat_id):
|
||||
if not bot_database.is_whitelisted(chat_id):
|
||||
telezab.user_state_manager.set_state(chat_id, "REGISTRATION")
|
||||
telezab.state.set_state(chat_id, "REGISTRATION")
|
||||
backend_bot.bot.send_message(chat_id, "Вы неавторизованы для использования этого бота")
|
||||
return
|
||||
admins_list = bot_database.get_admins()
|
||||
@ -1,18 +0,0 @@
|
||||
from jinja2 import TemplateNotFound
|
||||
|
||||
from flask import Blueprint, render_template, jsonify, abort
|
||||
|
||||
webui = Blueprint('webui', __name__, url_prefix='/telezab')
|
||||
|
||||
|
||||
@webui.route('/', defaults={'index'})
|
||||
def index():
|
||||
try:
|
||||
return render_template(index.html)
|
||||
except TemplateNotFound:
|
||||
abort(404)
|
||||
|
||||
|
||||
@webui.route('/data')
|
||||
def get_data():
|
||||
return jsonify({"message": "Данные из frontend!"})
|
||||
@ -1,13 +0,0 @@
|
||||
from flask import Blueprint, jsonify
|
||||
|
||||
webui = Blueprint('webui', __name__, url_prefix='/telezab')
|
||||
|
||||
@webui.route("/heartbeat")
|
||||
def heartbeat():
|
||||
return jsonify({"status": "healthy"})
|
||||
|
||||
|
||||
@webui.route('/', defaults={'path': ''})
|
||||
@webui.route('/<path:path>')
|
||||
def catch_all():
|
||||
return webui.send_static_file("index.html")
|
||||
Loading…
x
Reference in New Issue
Block a user