Refactoring and cleanup codebase (50%...)
MVP dashboard users page
This commit is contained in:
parent
20465600b1
commit
9e2560f7c3
@ -9,3 +9,4 @@
|
|||||||
/db/
|
/db/
|
||||||
/db/telezab.db
|
/db/telezab.db
|
||||||
/trash/
|
/trash/
|
||||||
|
/venv3.12.3/
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -11,3 +11,4 @@
|
|||||||
/logs/error.log
|
/logs/error.log
|
||||||
/db/
|
/db/
|
||||||
/db/telezab.db
|
/db/telezab.db
|
||||||
|
/venv3.12.3/
|
||||||
@ -1,23 +1,21 @@
|
|||||||
import sqlite3
|
import sqlite3
|
||||||
|
|
||||||
import telebot
|
import telebot
|
||||||
|
|
||||||
import telezab
|
import telezab
|
||||||
from backend_locks import db_lock, bot
|
from backend_locks import db_lock, bot
|
||||||
from bot_database import get_admins, is_whitelisted, format_regions_list, get_sorted_regions, log_user_event, \
|
from bot_database import get_admins, is_whitelisted, format_regions_list, get_sorted_regions, log_user_event, \
|
||||||
get_user_subscribed_regions
|
get_user_subscribed_regions
|
||||||
from config import DB_PATH
|
from config import DB_PATH
|
||||||
from telezab import handle_my_subscriptions_button, handle_active_regions_button, handle_notification_mode_button
|
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):
|
def handle_main_menu(message, chat_id, text):
|
||||||
"""Обработка команд в главном меню."""
|
"""Обработка команд в главном меню."""
|
||||||
if text == 'Регистрация':
|
if text == 'Регистрация':
|
||||||
telezab.user_state_manager.set_state(chat_id, "REGISTRATION")
|
telezab.state.set_state(chat_id, "REGISTRATION")
|
||||||
telezab.handle_register(message)
|
telezab.handle_register(message)
|
||||||
elif text == 'Настройки':
|
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)
|
telezab.show_settings_menu(chat_id)
|
||||||
elif text == 'Помощь':
|
elif text == 'Помощь':
|
||||||
telezab.handle_help(message)
|
telezab.handle_help(message)
|
||||||
@ -32,10 +30,10 @@ def handle_settings_menu(message, chat_id, text):
|
|||||||
"""Обработка команд в меню настроек."""
|
"""Обработка команд в меню настроек."""
|
||||||
admins_list = get_admins()
|
admins_list = get_admins()
|
||||||
if text.lower() == 'подписаться':
|
if text.lower() == 'подписаться':
|
||||||
telezab.user_state_manager.set_state(chat_id, "SUBSCRIBE")
|
telezab.state.set_state(chat_id, "SUBSCRIBE")
|
||||||
handle_subscribe_button(message)
|
handle_subscribe_button(message)
|
||||||
elif text.lower() == 'отписаться':
|
elif text.lower() == 'отписаться':
|
||||||
telezab.user_state_manager.set_state(chat_id, "UNSUBSCRIBE")
|
telezab.state.set_state(chat_id, "UNSUBSCRIBE")
|
||||||
handle_unsubscribe_button(message)
|
handle_unsubscribe_button(message)
|
||||||
elif text.lower() == 'мои подписки':
|
elif text.lower() == 'мои подписки':
|
||||||
handle_my_subscriptions_button(message)
|
handle_my_subscriptions_button(message)
|
||||||
@ -44,7 +42,7 @@ def handle_settings_menu(message, chat_id, text):
|
|||||||
elif text.lower() == "режим уведомлений":
|
elif text.lower() == "режим уведомлений":
|
||||||
handle_notification_mode_button(message)
|
handle_notification_mode_button(message)
|
||||||
elif text.lower() == 'назад':
|
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)
|
show_main_menu(chat_id)
|
||||||
else:
|
else:
|
||||||
bot.send_message(chat_id, "Команда не распознана.")
|
bot.send_message(chat_id, "Команда не распознана.")
|
||||||
@ -75,7 +73,7 @@ def process_subscription_button(message, chat_id, username):
|
|||||||
invalid_regions = []
|
invalid_regions = []
|
||||||
if message.text.lower() == 'отмена':
|
if message.text.lower() == 'отмена':
|
||||||
bot.send_message(chat_id, "Действие отменено.")
|
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)
|
return show_settings_menu(chat_id)
|
||||||
if not all(part.strip().isdigit() for part in message.text.split(',')):
|
if not all(part.strip().isdigit() for part in message.text.split(',')):
|
||||||
markup = telebot.types.InlineKeyboardMarkup()
|
markup = telebot.types.InlineKeyboardMarkup()
|
||||||
@ -108,7 +106,7 @@ def process_subscription_button(message, chat_id, username):
|
|||||||
f"Регион с ID {', '.join(invalid_regions)} не существует. Введите корректные номера или 'отмена'.")
|
f"Регион с ID {', '.join(invalid_regions)} не существует. Введите корректные номера или 'отмена'.")
|
||||||
bot.send_message(chat_id, f"Подписка на регионы: {', '.join(subbed_regions)} оформлена.")
|
bot.send_message(chat_id, f"Подписка на регионы: {', '.join(subbed_regions)} оформлена.")
|
||||||
log_user_event(chat_id, username, f"Subscribed to regions: {', '.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)
|
show_settings_menu(chat_id)
|
||||||
|
|
||||||
|
|
||||||
@ -117,7 +115,7 @@ def handle_unsubscribe_button(message):
|
|||||||
if not is_whitelisted(chat_id):
|
if not is_whitelisted(chat_id):
|
||||||
bot.send_message(chat_id, "Вы не авторизованы для использования этого бота.")
|
bot.send_message(chat_id, "Вы не авторизованы для использования этого бота.")
|
||||||
telebot.logger.info(f"Unauthorized access attempt by {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)
|
return show_main_menu(chat_id)
|
||||||
username = message.from_user.username
|
username = message.from_user.username
|
||||||
if username:
|
if username:
|
||||||
@ -129,7 +127,7 @@ def handle_unsubscribe_button(message):
|
|||||||
|
|
||||||
if not user_regions:
|
if not user_regions:
|
||||||
bot.send_message(chat_id, "Вы не подписаны ни на один регион.")
|
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)
|
return show_settings_menu(chat_id)
|
||||||
regions_list = format_regions_list(user_regions)
|
regions_list = format_regions_list(user_regions)
|
||||||
markup = telebot.types.InlineKeyboardMarkup()
|
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"))
|
markup.add(telebot.types.InlineKeyboardButton(text="Отмена", callback_data=f"cancel_action"))
|
||||||
if message.text.lower() == 'отмена':
|
if message.text.lower() == 'отмена':
|
||||||
bot.send_message(chat_id, "Действие отменено.")
|
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)
|
return show_settings_menu(chat_id)
|
||||||
# Проверка, что введённая строка содержит только цифры и запятые
|
# Проверка, что введённая строка содержит только цифры и запятые
|
||||||
if not all(part.strip().isdigit() for part in message.text.split(',')):
|
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"Регион с ID {', '.join(invalid_regions)} не найден в ваших подписках.")
|
||||||
bot.send_message(chat_id, f"Отписка от регионов: {', '.join(unsubbed_regions)} выполнена.")
|
bot.send_message(chat_id, f"Отписка от регионов: {', '.join(unsubbed_regions)} выполнена.")
|
||||||
log_user_event(chat_id, username, f"Unsubscribed from regions: {', '.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)
|
show_settings_menu(chat_id)
|
||||||
|
|||||||
176
backend_flask.py
176
backend_flask.py
@ -2,18 +2,37 @@ import logging
|
|||||||
import sqlite3
|
import sqlite3
|
||||||
|
|
||||||
import telebot
|
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 backend_bot
|
||||||
import bot_database
|
import bot_database
|
||||||
import telezab
|
import telezab
|
||||||
import utils
|
import utilities.telegram_utilities as telegram_util
|
||||||
from backend_locks import db_lock
|
from backend_locks import db_lock
|
||||||
from config import BASE_URL, DB_PATH
|
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 = Flask(__name__, static_url_path='/telezab/static', template_folder='templates')
|
||||||
# app.register_blueprint(webui)
|
# 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
|
# Настройка уровня логирования для Flask
|
||||||
app.logger.setLevel(logging.INFO)
|
app.logger.setLevel(logging.INFO)
|
||||||
@ -26,9 +45,9 @@ def webhook():
|
|||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
app.logger.info(f"Получены данные: {data}")
|
app.logger.info(f"Получены данные: {data}")
|
||||||
|
|
||||||
# Генерация хеша события и логирование
|
# # Генерация хеша события и логирование
|
||||||
event_hash = bot_database.hash_data(data)
|
# event_hash = bot_database.hash_data(data)
|
||||||
app.logger.debug(f"Сгенерирован хеш для события: {event_hash}")
|
# app.logger.debug(f"Сгенерирован хеш для события: {event_hash}")
|
||||||
|
|
||||||
# Работа с базой данных в блоке синхронизации
|
# Работа с базой данных в блоке синхронизации
|
||||||
with db_lock:
|
with db_lock:
|
||||||
@ -82,7 +101,7 @@ def webhook():
|
|||||||
app.logger.info(
|
app.logger.info(
|
||||||
f"Формирование сообщения для пользователя {username} (chat_id={chat_id}) [{formatted_message}]")
|
f"Формирование сообщения для пользователя {username} (chat_id={chat_id}) [{formatted_message}]")
|
||||||
try:
|
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})
|
send_to_queue({'chat_id': chat_id, 'username': username, 'message': message})
|
||||||
app.logger.debug(f"Сообщение поставлено в очередь для {chat_id} (@{username})")
|
app.logger.debug(f"Сообщение поставлено в очередь для {chat_id} (@{username})")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -93,8 +112,8 @@ def webhook():
|
|||||||
if undelivered:
|
if undelivered:
|
||||||
query = 'INSERT OR IGNORE INTO events (hash, data, delivered) VALUES (?, ?, ?)'
|
query = 'INSERT OR IGNORE INTO events (hash, data, delivered) VALUES (?, ?, ?)'
|
||||||
app.logger.debug(
|
app.logger.debug(
|
||||||
f"Сохранение события в базе данных: {query} (hash={event_hash}, delivered={False})")
|
f"Сохранение события в базе данных: {query} (delivered={False})")
|
||||||
cursor.execute(query, (event_hash, str(data), False))
|
cursor.execute(query, (str(data), False))
|
||||||
|
|
||||||
# Коммитим изменения в базе данных
|
# Коммитим изменения в базе данных
|
||||||
conn.commit()
|
conn.commit()
|
||||||
@ -152,11 +171,11 @@ def add_user():
|
|||||||
if success:
|
if success:
|
||||||
# INFO: Пользователь успешно добавлен в whitelist
|
# INFO: Пользователь успешно добавлен в whitelist
|
||||||
app.logger.info(f"Пользователь {telegram_id} добавлен в 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: Показ основного меню пользователю
|
# DEBUG: Показ основного меню пользователю
|
||||||
app.logger.debug(f"Отображение основного меню для пользователя с chat_id {chat_id}")
|
app.logger.debug(f"Отображение основного меню для пользователя с chat_id {chat_id}")
|
||||||
utils.show_main_menu(chat_id)
|
telegram_util.show_main_menu(chat_id)
|
||||||
return jsonify(
|
return jsonify(
|
||||||
{"status": "success", "msg": f"User {telegram_id} with {user_email} added successfully"}), 200
|
{"status": "success", "msg": f"User {telegram_id} with {user_email} added successfully"}), 200
|
||||||
else:
|
else:
|
||||||
@ -238,76 +257,71 @@ def delete_user():
|
|||||||
app.logger.debug(f"Соединение с базой данных закрыто")
|
app.logger.debug(f"Соединение с базой данных закрыто")
|
||||||
|
|
||||||
|
|
||||||
@app.route(BASE_URL + '/users/get', methods=['GET'])
|
# @app.route(BASE_URL + '/users/get', methods=['GET'])
|
||||||
def get_users():
|
# def get_users():
|
||||||
try:
|
# try:
|
||||||
# INFO: Запрос на получение списка пользователей
|
# # INFO: Запрос на получение списка пользователей
|
||||||
app.logger.info("Запрос на получение информации о пользователях получен")
|
# app.logger.info("Запрос на получение информации о пользователях получен")
|
||||||
|
#
|
||||||
with db_lock:
|
# with db_lock:
|
||||||
conn = sqlite3.connect(DB_PATH)
|
# conn = sqlite3.connect(DB_PATH)
|
||||||
cursor = conn.cursor()
|
# cursor = conn.cursor()
|
||||||
|
#
|
||||||
# DEBUG: Запрос данных из таблицы whitelist
|
# # DEBUG: Запрос данных из таблицы whitelist
|
||||||
app.logger.debug("Запрос данных пользователей из таблицы whitelist")
|
# app.logger.debug("Запрос данных пользователей из таблицы whitelist")
|
||||||
cursor.execute('SELECT * FROM whitelist')
|
# cursor.execute('SELECT * FROM whitelist')
|
||||||
users = cursor.fetchall()
|
# users = cursor.fetchall()
|
||||||
app.logger.debug("Формирование словаря пользователей")
|
# app.logger.debug("Формирование словаря пользователей")
|
||||||
users_dict = {user_id: {'id': user_id, 'username': username, 'email': email, 'events': [], 'worker': '',
|
# users_dict = {user_id: {'id': user_id, 'username': username, 'email': email, 'events': [], 'worker': '',
|
||||||
'subscriptions': []}
|
# 'subscriptions': []}
|
||||||
for user_id, username, email in users}
|
# for user_id, username, email in users}
|
||||||
|
#
|
||||||
# DEBUG: Запрос данных событий пользователей
|
# # DEBUG: Запрос данных событий пользователей
|
||||||
app.logger.debug("Запрос событий пользователей из таблицы user_events")
|
# app.logger.debug("Запрос событий пользователей из таблицы user_events")
|
||||||
cursor.execute('SELECT chat_id, username, action, timestamp FROM user_events')
|
# cursor.execute('SELECT chat_id, username, action, timestamp FROM user_events')
|
||||||
events = cursor.fetchall()
|
# events = cursor.fetchall()
|
||||||
|
#
|
||||||
# DEBUG: Обработка событий и добавление их в словарь пользователей
|
# # DEBUG: Обработка событий и добавление их в словарь пользователей
|
||||||
for chat_id, username, action, timestamp in events:
|
# for chat_id, username, action, timestamp in events:
|
||||||
if chat_id in users_dict:
|
# if chat_id in users_dict:
|
||||||
event = {'type': action, 'date': timestamp}
|
# event = {'type': action, 'date': timestamp}
|
||||||
if "Subscribed to region" in action:
|
# if "Subscribed to region" in action:
|
||||||
region = action.split(": ")[-1]
|
# region = action.split(": ")[-1]
|
||||||
event['region'] = region
|
# event['region'] = region
|
||||||
users_dict[chat_id]['events'].append(event)
|
# users_dict[chat_id]['events'].append(event)
|
||||||
|
#
|
||||||
# DEBUG: Запрос данных подписок пользователей
|
# # DEBUG: Запрос данных подписок пользователей
|
||||||
app.logger.debug("Запрос активных подписок пользователей из таблицы subscriptions")
|
# app.logger.debug("Запрос активных подписок пользователей из таблицы subscriptions")
|
||||||
cursor.execute('SELECT chat_id, region_id FROM subscriptions WHERE active = 1')
|
# cursor.execute('SELECT chat_id, region_id FROM subscriptions WHERE active = 1')
|
||||||
subscriptions = cursor.fetchall()
|
# subscriptions = cursor.fetchall()
|
||||||
|
#
|
||||||
# DEBUG: Добавление подписок к пользователям
|
# # DEBUG: Добавление подписок к пользователям
|
||||||
for chat_id, region_id in subscriptions:
|
# for chat_id, region_id in subscriptions:
|
||||||
if chat_id in users_dict:
|
# if chat_id in users_dict:
|
||||||
users_dict[chat_id]['subscriptions'].append(str(region_id))
|
# users_dict[chat_id]['subscriptions'].append(str(region_id))
|
||||||
|
#
|
||||||
# INFO: Формирование результата
|
# # INFO: Формирование результата
|
||||||
app.logger.info("Формирование результата для ответа")
|
# app.logger.info("Формирование результата для ответа")
|
||||||
result = []
|
# result = []
|
||||||
for user in users_dict.values():
|
# for user in users_dict.values():
|
||||||
ordered_user = {
|
# ordered_user = {
|
||||||
'email': user['email'],
|
# 'email': user['email'],
|
||||||
'username': user['username'],
|
# 'username': user['username'],
|
||||||
'id': user['id'],
|
# 'id': user['id'],
|
||||||
'worker': user['worker'],
|
# 'worker': user['worker'],
|
||||||
'events': user['events'],
|
# 'events': user['events'],
|
||||||
'subscriptions': ', '.join(user['subscriptions'])
|
# 'subscriptions': ', '.join(user['subscriptions'])
|
||||||
}
|
# }
|
||||||
result.append(ordered_user)
|
# result.append(ordered_user)
|
||||||
|
#
|
||||||
# INFO: Успешная отправка данных пользователей
|
# # INFO: Успешная отправка данных пользователей
|
||||||
app.logger.info("Информация о пользователях успешно отправлена")
|
# app.logger.info("Информация о пользователях успешно отправлена")
|
||||||
return jsonify(result)
|
# return jsonify(result)
|
||||||
|
#
|
||||||
except Exception as e:
|
# except Exception as e:
|
||||||
# ERROR: Ошибка при получении информации о пользователях
|
# # ERROR: Ошибка при получении информации о пользователях
|
||||||
app.logger.error(f"Ошибка при получении информации о пользователях: {str(e)}")
|
# app.logger.error(f"Ошибка при получении информации о пользователях: {str(e)}")
|
||||||
return jsonify({'status': 'error', 'message': str(e)}), 500
|
# 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 + '/debug/flask', methods=['POST'])
|
@app.route(BASE_URL + '/debug/flask', methods=['POST'])
|
||||||
|
|||||||
@ -8,7 +8,7 @@ from pyzabbix import ZabbixAPI
|
|||||||
|
|
||||||
import backend_bot
|
import backend_bot
|
||||||
from config import ZABBIX_URL, ZABBIX_API_TOKEN
|
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):
|
def get_triggers_for_group(chat_id, group_id):
|
||||||
|
|||||||
@ -9,7 +9,14 @@ DB_PATH = 'db/telezab.db'
|
|||||||
SUPPORT_EMAIL = "shiftsupport-rtmis@rtmis.ru"
|
SUPPORT_EMAIL = "shiftsupport-rtmis@rtmis.ru"
|
||||||
BASE_URL = '/telezab'
|
BASE_URL = '/telezab'
|
||||||
RABBITMQ_HOST = os.getenv('RABBITMQ_HOST')
|
RABBITMQ_HOST = os.getenv('RABBITMQ_HOST')
|
||||||
RABBITMQ_QUEUE = 'telegram_notifications'
|
|
||||||
RABBITMQ_LOGIN = os.getenv('RABBITMQ_LOGIN')
|
RABBITMQ_LOGIN = os.getenv('RABBITMQ_LOGIN')
|
||||||
RABBITMQ_PASS = os.getenv('RABBITMQ_PASS')
|
RABBITMQ_PASS = os.getenv('RABBITMQ_PASS')
|
||||||
|
RABBITMQ_QUEUE = 'telegram_notifications'
|
||||||
RABBITMQ_URL_FULL = f"amqp://{RABBITMQ_LOGIN}:{RABBITMQ_PASS}@{RABBITMQ_HOST}/"
|
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
|
colorama==0.4.6
|
||||||
exceptiongroup==1.2.2
|
exceptiongroup==1.2.2
|
||||||
Flask==3.1.0
|
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
|
frozenlist==1.5.0
|
||||||
|
greenlet==3.1.1
|
||||||
idna==3.10
|
idna==3.10
|
||||||
itsdangerous==2.2.0
|
itsdangerous==2.2.0
|
||||||
Jinja2==3.1.5
|
Jinja2==3.1.5
|
||||||
|
ldap3==2.9.1
|
||||||
MarkupSafe==3.0.2
|
MarkupSafe==3.0.2
|
||||||
multidict==6.1.0
|
multidict==6.1.0
|
||||||
packaging==24.2
|
packaging==24.2
|
||||||
@ -22,13 +28,17 @@ pamqp==3.3.0
|
|||||||
pika==1.3.2
|
pika==1.3.2
|
||||||
pika-stubs==0.1.3
|
pika-stubs==0.1.3
|
||||||
propcache==0.2.1
|
propcache==0.2.1
|
||||||
|
pyasn1==0.6.1
|
||||||
pyTelegramBotAPI==4.26.0
|
pyTelegramBotAPI==4.26.0
|
||||||
python-dotenv==1.0.1
|
python-dotenv==1.0.1
|
||||||
pytz==2025.1
|
pytz==2025.1
|
||||||
pyzabbix==1.3.1
|
pyzabbix==1.3.1
|
||||||
requests==2.32.3
|
requests==2.32.3
|
||||||
schedule==1.2.2
|
schedule==1.2.2
|
||||||
|
SQLAlchemy==2.0.38
|
||||||
telebot==0.0.5
|
telebot==0.0.5
|
||||||
|
typing_extensions==4.12.2
|
||||||
urllib3==2.3.0
|
urllib3==2.3.0
|
||||||
Werkzeug==3.1.3
|
Werkzeug==3.1.3
|
||||||
|
WTForms==3.2.1
|
||||||
yarl==1.18.3
|
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() {
|
let currentPage = 1;
|
||||||
// Получаем список сотрудников
|
let totalPages = 1;
|
||||||
$.getJSON('/telezab/users/get', function(data) {
|
const perPage = 20;
|
||||||
var userList = $('#user-list');
|
|
||||||
userList.empty();
|
// Функция загрузки пользователей
|
||||||
data.forEach(function(user) {
|
function loadUsers(page) {
|
||||||
var email = user.email;
|
if (page < 1 || page > totalPages) return;
|
||||||
var name = email.split('@')[0].replace(/\./g, ' ').replace(/\b\w/g, char => char.toUpperCase());
|
currentPage = page;
|
||||||
var listItem = $('<li class="list-group-item"></li>').text(name).data('user', user);
|
|
||||||
userList.append(listItem);
|
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);
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
// Обработчик кликов по пользователям
|
|
||||||
$('#user-list').on('click', '.list-group-item', function() {
|
|
||||||
// Удаляем активный класс у всех элементов списка
|
|
||||||
$('.list-group-item').removeClass('active');
|
|
||||||
|
|
||||||
// Добавляем активный класс к выбранному элементу
|
|
||||||
$(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');
|
function updateUsersTable(users) {
|
||||||
events.empty();
|
const tableBody = document.getElementById('users-table').querySelector('tbody');
|
||||||
if (user.events && user.events.length > 0) {
|
tableBody.innerHTML = '';
|
||||||
user.events.forEach(function(event) {
|
|
||||||
var eventText = event.type;
|
users.forEach(user => {
|
||||||
if (event.region) {
|
const row = document.createElement('tr');
|
||||||
eventText += ' (Регион: ' + event.region + ')';
|
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);
|
||||||
|
});
|
||||||
|
|
||||||
|
setupEditButtons();
|
||||||
}
|
}
|
||||||
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 logging
|
||||||
import sqlite3
|
import sqlite3
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
|
||||||
import telebot
|
import telebot
|
||||||
from pyzabbix import ZabbixAPI
|
from pyzabbix import ZabbixAPI
|
||||||
from telebot import types
|
from telebot import types
|
||||||
|
|
||||||
import backend_bot
|
import backend_bot
|
||||||
import bot_database
|
import bot_database
|
||||||
from backend_flask import app
|
from backend_flask import app
|
||||||
@ -14,13 +12,14 @@ from backend_locks import bot
|
|||||||
from backend_locks import db_lock
|
from backend_locks import db_lock
|
||||||
from backend_zabbix import get_triggers_for_group, get_triggers_for_all_groups
|
from backend_zabbix import get_triggers_for_group, get_triggers_for_all_groups
|
||||||
from config import *
|
from config import *
|
||||||
from log_manager import LogManager
|
from utilities.log_manager import LogManager
|
||||||
from rabbitmq import consume_from_queue
|
from utilities.rabbitmq import consume_from_queue
|
||||||
from user_state_manager import UserStateManager
|
from utilities.telegram_utilities import show_main_menu, show_settings_menu
|
||||||
from utils import show_main_menu, show_settings_menu
|
from utilities.user_state_manager import UserStateManager
|
||||||
|
|
||||||
|
|
||||||
# Инициализируем класс UserStateManager
|
# Инициализируем класс UserStateManager
|
||||||
user_state_manager = UserStateManager()
|
state = UserStateManager()
|
||||||
|
|
||||||
# Инициализация LogManager
|
# Инициализация LogManager
|
||||||
log_manager = LogManager(log_dir='logs', retention_days=30)
|
log_manager = LogManager(log_dir='logs', retention_days=30)
|
||||||
@ -31,12 +30,6 @@ telebot.logger = logging.getLogger('telebot')
|
|||||||
# Важно: вызов schedule_log_rotation для планировки ротации и архивации логов
|
# Важно: вызов schedule_log_rotation для планировки ротации и архивации логов
|
||||||
log_manager.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
|
# Handle /help command to provide instructions
|
||||||
@bot.message_handler(commands=['help'])
|
@bot.message_handler(commands=['help'])
|
||||||
def handle_help(message):
|
def handle_help(message):
|
||||||
@ -92,7 +85,7 @@ def handle_menu_selection(message):
|
|||||||
backend_bot.bot.send_message(chat_id, "Вы не авторизованы для использования этого бота.")
|
backend_bot.bot.send_message(chat_id, "Вы не авторизованы для использования этого бота.")
|
||||||
return
|
return
|
||||||
# Получаем текущее состояние пользователя
|
# Получаем текущее состояние пользователя
|
||||||
current_state = user_state_manager.get_state(chat_id)
|
current_state = state.get_state(chat_id)
|
||||||
# Обработка команд в зависимости от состояния
|
# Обработка команд в зависимости от состояния
|
||||||
if current_state == "MAIN_MENU":
|
if current_state == "MAIN_MENU":
|
||||||
backend_bot.handle_main_menu(message, chat_id, text)
|
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.clear_step_handler_by_chat_id(chat_id)
|
||||||
backend_bot.bot.send_message(chat_id, f"Действие отменено")
|
backend_bot.bot.send_message(chat_id, f"Действие отменено")
|
||||||
backend_bot.bot.edit_message_reply_markup(chat_id, message_id, reply_markup=None)
|
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)
|
show_settings_menu(chat_id)
|
||||||
return
|
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.clear_step_handler_by_chat_id(chat_id)
|
||||||
backend_bot.bot.send_message(chat_id, f"Действие отменено")
|
backend_bot.bot.send_message(chat_id, f"Действие отменено")
|
||||||
backend_bot.bot.edit_message_reply_markup(chat_id, message_id, reply_markup=None)
|
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)
|
show_main_menu(chat_id)
|
||||||
return
|
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}")
|
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.")
|
telebot.logger.debug(f"User state for {chat_id} set to SETTINGS_MENU.")
|
||||||
|
|
||||||
# Показываем меню настроек
|
# Показываем меню настроек
|
||||||
@ -381,7 +374,6 @@ def run_flask():
|
|||||||
def main():
|
def main():
|
||||||
# Инициализация базы данных
|
# Инициализация базы данных
|
||||||
bot_database.init_db()
|
bot_database.init_db()
|
||||||
|
|
||||||
# Запуск Flask и бота в отдельных потоках
|
# Запуск Flask и бота в отдельных потоках
|
||||||
Thread(target=run_flask, daemon=True).start()
|
Thread(target=run_flask, daemon=True).start()
|
||||||
Thread(target=run_polling, 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>
|
{% extends "base.html" %}
|
||||||
<html lang="en">
|
|
||||||
<head>
|
{% block title %}Dashboard Home{% endblock %}
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>Index of WebUI for Telezab</title>
|
{% block content %}
|
||||||
</head>
|
<h1 class="text-center mb-4">Добро пожаловать в Dashboard</h1>
|
||||||
<body>
|
<p class="text-center">Выберите один из разделов:</p>
|
||||||
THIS IS WEBUI FOR TELEZAB BOT
|
|
||||||
</body>
|
<div class="row">
|
||||||
</html>
|
<!-- Карточка пользователей -->
|
||||||
|
<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>
|
<!-- templates/regions.html -->
|
||||||
<html lang="en">
|
{% extends "base.html" %}
|
||||||
<head>
|
{% block title %}Регионы{% endblock %}
|
||||||
<meta charset="UTF-8">
|
{% block content %}
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<h2>Регионы</h2>
|
||||||
<title>Region Management</title>
|
<table class="table table-bordered">
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/bootstrap.min.css') }}">
|
<thead>
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/regions.css') }}">
|
<tr>
|
||||||
</head>
|
<th>ID</th>
|
||||||
<body>
|
<th>Название</th>
|
||||||
<div class="container mt-4">
|
<th>Активен</th>
|
||||||
<div class="row">
|
</tr>
|
||||||
<!-- Левый контейнер со списком регионов -->
|
</thead>
|
||||||
<div class="col-md-6">
|
<tbody id="regions-table">
|
||||||
<h3>Список Регионов</h3>
|
</tbody>
|
||||||
<div class="list-group" id="region-list">
|
</table>
|
||||||
<!-- Список регионов будет добавляться сюда -->
|
{% endblock %}
|
||||||
</div>
|
{% block scripts %}
|
||||||
<!-- Пагинация -->
|
|
||||||
<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>
|
|
||||||
<script src="{{ url_for('static', filename='js/regions.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/regions.js') }}"></script>
|
||||||
</body>
|
{% endblock %}
|
||||||
</html>
|
|
||||||
@ -1,37 +1,64 @@
|
|||||||
<!DOCTYPE html>
|
{% extends 'base.html' %}
|
||||||
<html lang="ru">
|
|
||||||
|
{% block content %}
|
||||||
<head>
|
<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') }}">
|
<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>
|
<title>Пользователи</title>
|
||||||
<script src="{{ url_for('static', filename='js/bootstrap.bundle.min.js') }}"></script>
|
|
||||||
<script src="{{ url_for('static', filename='js/users.js') }}"></script>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<div class="container">
|
||||||
<div class="container mt-4">
|
<h1>Пользователи</h1>
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-4">
|
<!-- Таблица пользователей -->
|
||||||
<ul id="user-list" class="list-group">
|
<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>
|
</ul>
|
||||||
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-8">
|
|
||||||
<div id="user-info" class="d-none">
|
<!-- Модальное окно -->
|
||||||
<h3 id="user-name"></h3>
|
<div id="userModal" class="modal fade" tabindex="-1" aria-labelledby="userModalLabel" aria-hidden="true">
|
||||||
<h5>Подписки на регионы</h5>
|
<div class="modal-dialog">
|
||||||
<ul id="user-regions" class="list-group">
|
<div class="modal-content">
|
||||||
<!-- Подписки на регионы будут вставлены сюда -->
|
<div class="modal-header">
|
||||||
</ul>
|
<h5 class="modal-title" id="userModalLabel">Редактирование пользователя</h5>
|
||||||
<h5>Действия</h5>
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
<div id="user-events" class="overflow-auto" style="max-height: 300px;">
|
</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>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
<!-- Подключаем внешний JavaScript файл -->
|
||||||
|
<script src="{{ url_for('static', filename='js/users.js') }}"></script>
|
||||||
|
|
||||||
|
{% 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': {
|
'handlers': {
|
||||||
'telebot_console': {
|
'telebot_console': {
|
||||||
'class': 'log_manager.UTF8StreamHandler',
|
'class': 'utilities.log_manager.UTF8StreamHandler',
|
||||||
'stream': 'ext://sys.stdout',
|
'stream': 'ext://sys.stdout',
|
||||||
'formatter': 'default',
|
'formatter': 'default',
|
||||||
},
|
},
|
||||||
'flask_console': {
|
'flask_console': {
|
||||||
'class': 'log_manager.UTF8StreamHandler',
|
'class': 'utilities.log_manager.UTF8StreamHandler',
|
||||||
'stream': 'ext://sys.stdout',
|
'stream': 'ext://sys.stdout',
|
||||||
'formatter': 'werkzeug',
|
'formatter': 'werkzeug',
|
||||||
},
|
},
|
||||||
@ -91,10 +91,10 @@ def escape_telegram_chars(text):
|
|||||||
def show_main_menu(chat_id):
|
def show_main_menu(chat_id):
|
||||||
markup = telebot.types.ReplyKeyboardMarkup(one_time_keyboard=True, resize_keyboard=True)
|
markup = telebot.types.ReplyKeyboardMarkup(one_time_keyboard=True, resize_keyboard=True)
|
||||||
if bot_database.is_whitelisted(chat_id):
|
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('Настройки', 'Помощь', 'Активные события')
|
markup.add('Настройки', 'Помощь', 'Активные события')
|
||||||
else:
|
else:
|
||||||
telezab.user_state_manager.set_state(chat_id, "REGISTRATION")
|
telezab.state.set_state(chat_id, "REGISTRATION")
|
||||||
markup.add('Регистрация')
|
markup.add('Регистрация')
|
||||||
backend_bot.bot.send_message(chat_id, "Выберите действие:", reply_markup=markup)
|
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):
|
def show_settings_menu(chat_id):
|
||||||
if not bot_database.is_whitelisted(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, "Вы неавторизованы для использования этого бота")
|
backend_bot.bot.send_message(chat_id, "Вы неавторизованы для использования этого бота")
|
||||||
return
|
return
|
||||||
admins_list = bot_database.get_admins()
|
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