diff --git a/config.py b/config.py index 9eb19e0..3e41e86 100644 --- a/config.py +++ b/config.py @@ -23,3 +23,6 @@ DATABASE_URL = "sqlite:///database/DogAcademy.db" # Обновлено на п # Иконки SETTINGS_IMG = "assets/settings.png" + +# Уровни уведомлений (для дальнейшей настройки) +NOTIFICATION_LEVEL = "info" # Возможные значения: "info", "warning", "error" \ No newline at end of file diff --git a/database/DogAcademy.db b/database/DogAcademy.db index 695b547..4e62794 100644 Binary files a/database/DogAcademy.db and b/database/DogAcademy.db differ diff --git a/database/db_events.py b/database/db_events.py index 38d4ac7..7493fab 100644 --- a/database/db_events.py +++ b/database/db_events.py @@ -1,5 +1,5 @@ from database.db_session import get_session -from database.models import Auth +from database.models import Auth, Notifications, Users from sqlalchemy.exc import SQLAlchemyError def create_user(login, password): @@ -26,3 +26,20 @@ def check_user(login, password): return False finally: session.close() + + +def log_db_event(event_message, root): + # Логирование события с базы данных + try: + # Пример добавления события в лог + with open('logs/database_logs.txt', 'a') as log_file: + log_file.write(event_message + "\n") + + # Уведомление для администратора + notification = Notifications(root) + notification.show_info("Событие", f"Событие успешно записано: {event_message}") + + except Exception as e: + # Если ошибка при записи в лог + notification = Notifications(root) + notification.show_error("Ошибка", f"Ошибка при записи в лог: {str(e)}") \ No newline at end of file diff --git a/database/db_session.py b/database/db_session.py index 70b39dc..e2729c3 100644 --- a/database/db_session.py +++ b/database/db_session.py @@ -1,4 +1,3 @@ -# database/db_session.py from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker from config import DATABASE_URL @@ -22,4 +21,4 @@ def init_db(): def get_session(): """Возвращает сессию для работы с базой данных.""" - return Session() + return Session() \ No newline at end of file diff --git a/database/models.py b/database/models.py index 4198539..2189319 100644 --- a/database/models.py +++ b/database/models.py @@ -1,5 +1,6 @@ -from sqlalchemy import Column, Integer, String, ForeignKey, Text +from sqlalchemy import Column, Integer, String, ForeignKey, Text, DateTime from sqlalchemy.orm import relationship +from sqlalchemy.sql import func from sqlalchemy.ext.declarative import declarative_base Base = declarative_base() @@ -26,6 +27,8 @@ class Users(Base): # Связи auth = relationship("Auth", back_populates="user") # Обратная связь с Auth dog = relationship("Dogs", back_populates="users") # Связь с таблицей Dogs + game_sessions = relationship("GameSession", back_populates="user") # Связь с таблицей GameSession + notifications = relationship("Notifications", back_populates="user") # Связь с уведомлениями class Dogs(Base): @@ -47,9 +50,36 @@ class Questions(Base): __tablename__ = 'questions' question_id = Column(Integer, primary_key=True) dog_id = Column(Integer, ForeignKey('dogs.dog_id')) - question_text = Column(Text, nullable=False) + question_text = Column(Text, nullable=False) # Исправлено поле image_url = Column(String) helpful_info = Column(Text) + incorrect_attempts = Column(Integer, default=0) # Связь с таблицей Dogs dog = relationship("Dogs", back_populates="questions") + + +class GameSession(Base): + __tablename__ = 'game_sessions' + session_id = Column(Integer, primary_key=True) + user_id = Column(Integer, ForeignKey('users.user_id')) + level = Column(Integer, nullable=False) + score = Column(Integer, default=0) + duration = Column(Integer) # Время игры в секундах + start_time = Column(DateTime, default=func.now()) # Исправлено + end_time = Column(DateTime, nullable=True) + + # Связь с таблицей Users + user = relationship("Users", back_populates="game_sessions") + +class Notifications(Base): + __tablename__ = 'notifications' + notification_id = Column(Integer, primary_key=True) + user_id = Column(Integer, ForeignKey('users.user_id')) + message = Column(Text, nullable=False) + timestamp = Column(DateTime, default=func.now()) + is_read = Column(Integer, default=0) # 0 - не прочитано, 1 - прочитано + + # Связь с таблицей Users + user = relationship("Users", back_populates="notifications") + diff --git a/ishodniki/admin_panel.psd b/ishodniki/admin_panel.psd new file mode 100644 index 0000000..e39ac44 Binary files /dev/null and b/ishodniki/admin_panel.psd differ diff --git a/logs/logfile.log b/logs/logfile.log new file mode 100644 index 0000000..1b57012 --- /dev/null +++ b/logs/logfile.log @@ -0,0 +1,329 @@ +2024-11-20 12:59:15 - BEGIN (implicit) +2024-11-20 12:59:15 - SELECT count(*) AS count_1 +FROM (SELECT users.user_id AS users_user_id, users.dog_id AS users_dog_id, users.username AS users_username, users.level AS users_level, users.achievement AS users_achievement +FROM users) AS anon_1 +2024-11-20 12:59:15 - [generated in 0.00054s] () +2024-11-20 12:59:15 - SELECT game_sessions.level AS game_sessions_level, count(game_sessions.session_id) AS count_1 +FROM game_sessions GROUP BY game_sessions.level +2024-11-20 12:59:15 - [generated in 0.00030s] () +2024-11-20 13:00:56 - BEGIN (implicit) +2024-11-20 13:00:56 - PRAGMA main.table_info("auth") +2024-11-20 13:00:56 - [raw sql] () +2024-11-20 13:00:56 - PRAGMA temp.table_info("auth") +2024-11-20 13:00:56 - [raw sql] () +2024-11-20 13:00:56 - PRAGMA main.table_info("users") +2024-11-20 13:00:56 - [raw sql] () +2024-11-20 13:00:56 - PRAGMA temp.table_info("users") +2024-11-20 13:00:56 - [raw sql] () +2024-11-20 13:00:56 - PRAGMA main.table_info("dogs") +2024-11-20 13:00:56 - [raw sql] () +2024-11-20 13:00:56 - PRAGMA temp.table_info("dogs") +2024-11-20 13:00:56 - [raw sql] () +2024-11-20 13:00:56 - PRAGMA main.table_info("questions") +2024-11-20 13:00:56 - [raw sql] () +2024-11-20 13:00:56 - PRAGMA temp.table_info("questions") +2024-11-20 13:00:56 - [raw sql] () +2024-11-20 13:00:56 - PRAGMA main.table_info("game_sessions") +2024-11-20 13:00:56 - [raw sql] () +2024-11-20 13:00:56 - PRAGMA temp.table_info("game_sessions") +2024-11-20 13:00:56 - [raw sql] () +2024-11-20 13:00:56 - +CREATE TABLE auth ( + user_id INTEGER NOT NULL, + login VARCHAR NOT NULL, + password VARCHAR NOT NULL, + PRIMARY KEY (user_id), + UNIQUE (login) +) + + +2024-11-20 13:00:56 - [no key 0.00011s] () +2024-11-20 13:00:56 - +CREATE TABLE dogs ( + dog_id INTEGER NOT NULL, + breed VARCHAR, + characteristics TEXT, + behavior TEXT, + care_info TEXT, + admin_comments TEXT, + PRIMARY KEY (dog_id) +) + + +2024-11-20 13:00:56 - [no key 0.00017s] () +2024-11-20 13:00:56 - +CREATE TABLE users ( + user_id INTEGER NOT NULL, + dog_id INTEGER, + username VARCHAR NOT NULL, + level INTEGER, + achievement TEXT, + PRIMARY KEY (user_id), + FOREIGN KEY(user_id) REFERENCES auth (user_id), + FOREIGN KEY(dog_id) REFERENCES dogs (dog_id) +) + + +2024-11-20 13:00:56 - [no key 0.00019s] () +2024-11-20 13:00:56 - +CREATE TABLE questions ( + question_id INTEGER NOT NULL, + dog_id INTEGER, + question_text TEXT NOT NULL, + image_url VARCHAR, + helpful_info TEXT, + incorrect_attempts INTEGER, + PRIMARY KEY (question_id), + FOREIGN KEY(dog_id) REFERENCES dogs (dog_id) +) + + +2024-11-20 13:00:56 - [no key 0.00017s] () +2024-11-20 13:00:57 - +CREATE TABLE game_sessions ( + session_id INTEGER NOT NULL, + user_id INTEGER, + level INTEGER NOT NULL, + score INTEGER, + duration INTEGER, + start_time DATETIME, + end_time DATETIME, + PRIMARY KEY (session_id), + FOREIGN KEY(user_id) REFERENCES users (user_id) +) + + +2024-11-20 13:00:57 - [no key 0.00023s] () +2024-11-20 13:00:57 - COMMIT +2024-11-20 13:01:13 - BEGIN (implicit) +2024-11-20 13:01:13 - SELECT count(*) AS count_1 +FROM (SELECT users.user_id AS users_user_id, users.dog_id AS users_dog_id, users.username AS users_username, users.level AS users_level, users.achievement AS users_achievement +FROM users) AS anon_1 +2024-11-20 13:01:13 - [generated in 0.00032s] () +2024-11-20 13:01:13 - SELECT game_sessions.level AS game_sessions_level, count(game_sessions.session_id) AS count_1 +FROM game_sessions GROUP BY game_sessions.level +2024-11-20 13:01:13 - [generated in 0.00025s] () +2024-11-20 13:01:13 - SELECT questions.question_text AS questions_question_text, questions.incorrect_attempts AS questions_incorrect_attempts +FROM questions ORDER BY questions.incorrect_attempts DESC +2024-11-20 13:01:13 - [generated in 0.00028s] () +2024-11-20 13:01:13 - SELECT avg(game_sessions.duration) AS avg_1 +FROM game_sessions +2024-11-20 13:01:13 - [generated in 0.00023s] () +2024-11-20 13:01:13 - ROLLBACK +2024-11-20 13:01:13 - BEGIN (implicit) +2024-11-20 13:01:13 - SELECT game_sessions.start_time AS game_sessions_start_time +FROM game_sessions +2024-11-20 13:01:13 - [generated in 0.00028s] () +2024-11-20 13:01:13 - ROLLBACK +2024-11-20 13:40:04 - BEGIN (implicit) +2024-11-20 13:40:04 - PRAGMA main.table_info("auth") +2024-11-20 13:40:04 - [raw sql] () +2024-11-20 13:40:04 - PRAGMA temp.table_info("auth") +2024-11-20 13:40:04 - [raw sql] () +2024-11-20 13:40:04 - PRAGMA main.table_info("users") +2024-11-20 13:40:04 - [raw sql] () +2024-11-20 13:40:04 - PRAGMA temp.table_info("users") +2024-11-20 13:40:04 - [raw sql] () +2024-11-20 13:40:04 - PRAGMA main.table_info("dogs") +2024-11-20 13:40:04 - [raw sql] () +2024-11-20 13:40:04 - PRAGMA temp.table_info("dogs") +2024-11-20 13:40:04 - [raw sql] () +2024-11-20 13:40:04 - PRAGMA main.table_info("questions") +2024-11-20 13:40:04 - [raw sql] () +2024-11-20 13:40:04 - PRAGMA temp.table_info("questions") +2024-11-20 13:40:04 - [raw sql] () +2024-11-20 13:40:04 - PRAGMA main.table_info("game_sessions") +2024-11-20 13:40:04 - [raw sql] () +2024-11-20 13:40:04 - PRAGMA temp.table_info("game_sessions") +2024-11-20 13:40:04 - [raw sql] () +2024-11-20 13:40:04 - PRAGMA main.table_info("notifications") +2024-11-20 13:40:04 - [raw sql] () +2024-11-20 13:40:04 - PRAGMA temp.table_info("notifications") +2024-11-20 13:40:04 - [raw sql] () +2024-11-20 13:40:04 - +CREATE TABLE auth ( + user_id INTEGER NOT NULL, + login VARCHAR NOT NULL, + password VARCHAR NOT NULL, + PRIMARY KEY (user_id), + UNIQUE (login) +) + + +2024-11-20 13:40:04 - [no key 0.00011s] () +2024-11-20 13:40:04 - +CREATE TABLE dogs ( + dog_id INTEGER NOT NULL, + breed VARCHAR, + characteristics TEXT, + behavior TEXT, + care_info TEXT, + admin_comments TEXT, + PRIMARY KEY (dog_id) +) + + +2024-11-20 13:40:04 - [no key 0.00019s] () +2024-11-20 13:40:04 - +CREATE TABLE users ( + user_id INTEGER NOT NULL, + dog_id INTEGER, + username VARCHAR NOT NULL, + level INTEGER, + achievement TEXT, + PRIMARY KEY (user_id), + FOREIGN KEY(user_id) REFERENCES auth (user_id), + FOREIGN KEY(dog_id) REFERENCES dogs (dog_id) +) + + +2024-11-20 13:40:04 - [no key 0.00023s] () +2024-11-20 13:40:04 - +CREATE TABLE questions ( + question_id INTEGER NOT NULL, + dog_id INTEGER, + question_text TEXT NOT NULL, + image_url VARCHAR, + helpful_info TEXT, + incorrect_attempts INTEGER, + PRIMARY KEY (question_id), + FOREIGN KEY(dog_id) REFERENCES dogs (dog_id) +) + + +2024-11-20 13:40:04 - [no key 0.00020s] () +2024-11-20 13:40:04 - +CREATE TABLE game_sessions ( + session_id INTEGER NOT NULL, + user_id INTEGER, + level INTEGER NOT NULL, + score INTEGER, + duration INTEGER, + start_time DATETIME, + end_time DATETIME, + PRIMARY KEY (session_id), + FOREIGN KEY(user_id) REFERENCES users (user_id) +) + + +2024-11-20 13:40:04 - [no key 0.00020s] () +2024-11-20 13:40:05 - +CREATE TABLE notifications ( + notification_id INTEGER NOT NULL, + user_id INTEGER, + message TEXT NOT NULL, + timestamp DATETIME, + is_read INTEGER, + PRIMARY KEY (notification_id), + FOREIGN KEY(user_id) REFERENCES users (user_id) +) + + +2024-11-20 13:40:05 - [no key 0.00017s] () +2024-11-20 13:40:05 - COMMIT +2024-11-20 17:07:30 - BEGIN (implicit) +2024-11-20 17:07:30 - PRAGMA main.table_info("auth") +2024-11-20 17:07:30 - [raw sql] () +2024-11-20 17:07:30 - PRAGMA temp.table_info("auth") +2024-11-20 17:07:30 - [raw sql] () +2024-11-20 17:07:30 - PRAGMA main.table_info("users") +2024-11-20 17:07:30 - [raw sql] () +2024-11-20 17:07:30 - PRAGMA temp.table_info("users") +2024-11-20 17:07:30 - [raw sql] () +2024-11-20 17:07:30 - PRAGMA main.table_info("dogs") +2024-11-20 17:07:30 - [raw sql] () +2024-11-20 17:07:30 - PRAGMA temp.table_info("dogs") +2024-11-20 17:07:30 - [raw sql] () +2024-11-20 17:07:30 - PRAGMA main.table_info("questions") +2024-11-20 17:07:30 - [raw sql] () +2024-11-20 17:07:30 - PRAGMA temp.table_info("questions") +2024-11-20 17:07:30 - [raw sql] () +2024-11-20 17:07:30 - PRAGMA main.table_info("game_sessions") +2024-11-20 17:07:30 - [raw sql] () +2024-11-20 17:07:30 - PRAGMA temp.table_info("game_sessions") +2024-11-20 17:07:30 - [raw sql] () +2024-11-20 17:07:30 - PRAGMA main.table_info("notifications") +2024-11-20 17:07:30 - [raw sql] () +2024-11-20 17:07:30 - PRAGMA temp.table_info("notifications") +2024-11-20 17:07:30 - [raw sql] () +2024-11-20 17:07:30 - +CREATE TABLE auth ( + user_id INTEGER NOT NULL, + login VARCHAR NOT NULL, + password VARCHAR NOT NULL, + PRIMARY KEY (user_id), + UNIQUE (login) +) + + +2024-11-20 17:07:30 - [no key 0.00010s] () +2024-11-20 17:07:30 - +CREATE TABLE dogs ( + dog_id INTEGER NOT NULL, + breed VARCHAR, + characteristics TEXT, + behavior TEXT, + care_info TEXT, + admin_comments TEXT, + PRIMARY KEY (dog_id) +) + + +2024-11-20 17:07:30 - [no key 0.00025s] () +2024-11-20 17:07:30 - +CREATE TABLE users ( + user_id INTEGER NOT NULL, + dog_id INTEGER, + username VARCHAR NOT NULL, + level INTEGER, + achievement TEXT, + PRIMARY KEY (user_id), + FOREIGN KEY(user_id) REFERENCES auth (user_id), + FOREIGN KEY(dog_id) REFERENCES dogs (dog_id) +) + + +2024-11-20 17:07:30 - [no key 0.00023s] () +2024-11-20 17:07:30 - +CREATE TABLE questions ( + question_id INTEGER NOT NULL, + dog_id INTEGER, + question_text TEXT NOT NULL, + image_url VARCHAR, + helpful_info TEXT, + incorrect_attempts INTEGER, + PRIMARY KEY (question_id), + FOREIGN KEY(dog_id) REFERENCES dogs (dog_id) +) + + +2024-11-20 17:07:30 - [no key 0.00019s] () +2024-11-20 17:07:30 - +CREATE TABLE game_sessions ( + session_id INTEGER NOT NULL, + user_id INTEGER, + level INTEGER NOT NULL, + score INTEGER, + duration INTEGER, + start_time DATETIME, + end_time DATETIME, + PRIMARY KEY (session_id), + FOREIGN KEY(user_id) REFERENCES users (user_id) +) + + +2024-11-20 17:07:30 - [no key 0.00038s] () +2024-11-20 17:07:30 - +CREATE TABLE notifications ( + notification_id INTEGER NOT NULL, + user_id INTEGER, + message TEXT NOT NULL, + timestamp DATETIME, + is_read INTEGER, + PRIMARY KEY (notification_id), + FOREIGN KEY(user_id) REFERENCES users (user_id) +) + + +2024-11-20 17:07:30 - [no key 0.00018s] () +2024-11-20 17:07:30 - COMMIT diff --git a/src/admin_functions/admin_logging.py b/src/admin_functions/admin_logging.py index 5a2204b..411a4d2 100644 --- a/src/admin_functions/admin_logging.py +++ b/src/admin_functions/admin_logging.py @@ -2,22 +2,45 @@ import tkinter as tk from tkinter import ttk import csv from src.utils import clear_frame +import logging +from datetime import datetime +BACKGROUND_COLOR = "#403d49" +TEXT_COLOR = "#b2acc0" +HEADER_COLOR = "#2f2b38" +BUTTON_COLOR = "#444444" + +logging.basicConfig( + filename="logs/logfile.log", + level=logging.INFO, + format="%(asctime)s - %(message)s", + datefmt="%Y-%m-%d %H:%M:%S" +) + + +def log_action(action, user): + logging.info(f"{action} - Пользователь: {user}") + def show_logs(frame): - """Отображение логов действий пользователей.""" + """Отображение логов действий пользователей в тёмной теме.""" clear_frame(frame) - tk.Label(frame, text="Логи действий", font=("Comic Sans MS", 16)).pack() + tk.Label(frame, text="Логи действий", font=("Comic Sans MS", 16), bg=BACKGROUND_COLOR, fg=TEXT_COLOR).pack(pady=10) - table = ttk.Treeview(frame, columns=("Время", "Действие", "Пользователь"), show="headings") + # Настройка таблицы с логами + table = ttk.Treeview(frame, columns=("Время", "Действие", "Пользователь"), show="headings", style="Dark.Treeview") table.heading("Время", text="Время") table.heading("Действие", text="Действие") table.heading("Пользователь", text="Пользователь") table.pack(fill="both", expand=True) - # Добавьте логи для примера + # Добавление логов для примера table.insert("", "end", values=("2024-11-19 12:30", "Добавление вопроса", "admin")) table.insert("", "end", values=("2024-11-19 13:00", "Удаление пользователя", "moderator")) + # Применение стиля к таблице + style = ttk.Style() + style.configure("Dark.Treeview", background=BACKGROUND_COLOR, foreground=TEXT_COLOR, fieldbackground=BACKGROUND_COLOR) + style.configure("Dark.Treeview.Heading", background=HEADER_COLOR, foreground=TEXT_COLOR) def export_logs(): data = [ diff --git a/src/admin_functions/db_management.py b/src/admin_functions/db_management.py index 23c9fe0..b1c161f 100644 --- a/src/admin_functions/db_management.py +++ b/src/admin_functions/db_management.py @@ -1,7 +1,7 @@ import tkinter as tk from tkinter import ttk from sqlalchemy.orm import sessionmaker -from database.models import Questions +from database.models import Questions, Notifications from database.db_session import engine from src.utils import clear_frame @@ -33,4 +33,20 @@ def manage_questions(frame): for question in questions: table.insert("", "end", values=(question.id, question.text, question.answer)) - session.close() \ No newline at end of file + session.close() + +def add_user_to_db(user_data, root): + # Логика добавления пользователя в базу данных + try: + # Пример кода для добавления в базу (зависит от реализации вашей базы данных) + # db_session.add(user_data) + # db_session.commit() + + # Если добавление прошло успешно + notification = Notifications(root) + notification.show_info("Успех", f"Пользователь {user_data['username']} успешно добавлен!") + + except Exception as e: + # Если возникла ошибка + notification = Notifications(root) + notification.show_error("Ошибка", f"Ошибка при добавлении пользователя: {str(e)}") \ No newline at end of file diff --git a/src/admin_functions/notification.py b/src/admin_functions/notification.py new file mode 100644 index 0000000..2d17ae5 --- /dev/null +++ b/src/admin_functions/notification.py @@ -0,0 +1,21 @@ +from tkinter import messagebox + +class Notification: + def __init__(self, root): + self.root = root + + def show_info(self, title, message): + """Отображение информационного уведомления""" + messagebox.showinfo(title, message) + + def show_warning(self, title, message): + """Отображение предупреждения""" + messagebox.showwarning(title, message) + + def show_error(self, title, message): + """Отображение ошибки""" + messagebox.showerror(title, message) + + def show_notification(self, title, message): + """Отображение общего уведомления""" + self.show_info(title, message) diff --git a/src/admin_functions/security.py b/src/admin_functions/security.py new file mode 100644 index 0000000..e69de29 diff --git a/src/admin_functions/statistics.py b/src/admin_functions/statistics.py index adb4ca2..c86431f 100644 --- a/src/admin_functions/statistics.py +++ b/src/admin_functions/statistics.py @@ -3,22 +3,96 @@ from tkinter import ttk import matplotlib.pyplot as plt from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg from src.utils import clear_frame +from database.db_session import get_session +from sqlalchemy.sql import func +from database.models import Users, GameSession, Questions # Пример моделей def show_statistics(frame): - """Отображение статистики пользователей и уровней.""" + """Отображение статистики.""" clear_frame(frame) - tk.Label(frame, text="Статистика пользователей", font=("Comic Sans MS", 16)).pack() + tk.Label(frame, text="Статистика", font=("Comic Sans MS", 16), bg="#403d49", fg="#b2acc0").pack(pady=10) - # Пример: график с количеством пользователей - fig, ax = plt.subplots() - ax.bar(["Level 1", "Level 2", "Level 3"], [10, 15, 8]) # Пример данных - ax.set_title("Популярность уровней") - ax.set_xlabel("Уровни") - ax.set_ylabel("Количество прохождений") + # Таблица с общей статистикой + table = ttk.Treeview(frame, columns=("Metric", "Value"), show="headings", height=5) + table.heading("Metric", text="Параметр") + table.heading("Value", text="Значение") + table.pack(pady=10, padx=20, fill="x", expand=True) + + # Получение данных для таблицы + stats = gather_statistics() + for metric, value in stats.items(): + table.insert("", tk.END, values=(metric, value)) + + # График активности пользователей + tk.Label(frame, text="Активность пользователей", font=("Comic Sans MS", 14), bg="#403d49", fg="#b2acc0").pack(pady=10) + + fig, ax = plt.subplots(figsize=(6, 4)) + time_labels, activity_values = get_user_activity() + ax.plot(time_labels, activity_values, marker="o") + ax.set_title("Активность пользователей по времени") + ax.set_xlabel("Время") + ax.set_ylabel("Количество действий") + ax.grid() canvas = FigureCanvasTkAgg(fig, master=frame) canvas.get_tk_widget().pack(fill="both", expand=True) canvas.draw() +def gather_statistics(): + """Собирает основные метрики для таблицы статистики.""" + session = get_session() + # Количество зарегистрированных пользователей + user_count = session.query(Users).count() + # Популярные уровни + level_data = session.query(GameSession.level, func.count(GameSession.session_id)).group_by(GameSession.level).all() + popular_levels = sorted(level_data, key=lambda x: x[1], reverse=True)[:3] + + # Трудные вопросы + question_data = session.query(Questions.question_text, Questions.incorrect_attempts).order_by(Questions.incorrect_attempts.desc()).all() + hardest_questions = question_data[:3] + + # Средняя продолжительность игры + avg_duration = session.query(func.avg(GameSession.duration)).scalar() or 0 + + # Состояние базы данных + db_size = get_database_size() + + session.close() + + return { + "Количество пользователей": user_count, + "Популярные уровни": ", ".join([f"Уровень {lvl} ({cnt} раз)" for lvl, cnt in popular_levels]), + "Трудные вопросы": ", ".join([f"'{text}' ({cnt} ошибок)" for text, cnt in hardest_questions]), + "Средняя продолжительность игры": f"{avg_duration:.2f} секунд", + "Объем базы данных": f"{db_size} КБ" + } + +def get_user_activity(): + """Генерирует данные для графика активности пользователей.""" + session = get_session() + activity_data = session.query(GameSession.start_time).all() + + activity_by_hour = {} + for time in activity_data: + hour = time.start_time.hour + activity_by_hour[hour] = activity_by_hour.get(hour, 0) + 1 + + session.close() + + if not activity_by_hour: + return ["Нет данных"], [0] + + hours = sorted(activity_by_hour.keys()) + activity = [activity_by_hour[hour] for hour in hours] + hours = [f"{hour}:00" for hour in hours] + return hours, activity + +def get_database_size(): + """Возвращает размер базы данных в КБ.""" + import os + db_path = "database/db.sqlite" + if os.path.exists(db_path): + return round(os.path.getsize(db_path) / 1024, 2) + return 0 diff --git a/src/ui/admin_ui.py b/src/ui/admin_ui.py index 7390bb8..3561b5a 100644 --- a/src/ui/admin_ui.py +++ b/src/ui/admin_ui.py @@ -3,6 +3,9 @@ from PIL import Image, ImageTk from config import SETTINGS_IMG from src.admin_functions import db_management, admin_logging, statistics, content, knowledge_base from src.utils import clear_frame # Импортируем общую функцию для очистки фрейма +from database.db_session import get_session +from database.models import Notifications +from src.admin_functions.notification import Notification # Конфигурация цветов из config.py @@ -14,12 +17,26 @@ MENU_COLOR = "#2f2b38" MENU_OPACITY = 0.9 # Прозрачность меню class AdminApp: - def __init__(self, root): + def __init__(self, root, master): self.root = root + self.master = master self.root.title("Админ-Панель") self.root.geometry("1920x1080") self.root.config(bg=BACKGROUND_COLOR) + self.notification = Notification(self.master) + + def edit_database(self): + # Логика редактирования базы данных + # Например, успешное редактирование + self.notification.show_info("Успех", "База данных успешно обновлена!") + + def show_error(self, message): + self.notification.show_error("Ошибка", message) + + def show_warning(self, message): + self.notification.show_warning("Предупреждение", message) + # Верхняя панель self.top_bar = tk.Frame(self.root, bg=TOP_BAR_COLOR, height=60) self.top_bar.pack(side="top", fill="x") @@ -71,43 +88,94 @@ class AdminApp: def toggle_menu(self): """Показ или скрытие меню.""" - print( - f"Кнопка меню нажата. Меню сейчас {'видимо' if self.menu_visible else 'скрыто'}") # Отладка - if self.menu_visible: # Используем флаг для проверки состояния - print("Скрываем меню") # Отладка - self.menu_frame.lower() # Скрываем меню + if self.menu_visible: + self.menu_frame.lower() self.menu_visible = False else: - print("Показываем меню") # Отладка - self.menu_frame.lift() # Показываем меню + self.menu_frame.lift() self.menu_visible = True - self.populate_menu() # Наполнение меню элементами + self.populate_menu() def populate_menu(self): # Очистка меню for widget in self.menu_frame.winfo_children(): widget.destroy() - # Создание пунктов меню - self.create_menu_section("Работа с базой данных", [ - ("Редактирование пользователей", db_management.edit_users), - ("Управление вопросами", db_management.manage_questions), - ("Просмотр таблиц", db_management.view_tables), - ]) - self.create_menu_section("Управление игровым контентом", [ - ("Создание и настройка уровней", content.manage_levels), - ("Настройка параметров собаки", content.manage_dog_params), - ]) - self.create_menu_section("Управление интерфейсом пользователя", [ - ("Изменение цветовой схемы, фона и логотипа", self.change_ui_settings), - ("Добавление подсказок в интерфейс", self.manage_ui_tips), - ]) - self.create_menu_section("Работа с базой знаний", [ - ("Добавление информации", knowledge_base.add_info), - ("Редактирование записей", knowledge_base.edit_records), - ("Удаление записей", knowledge_base.delete_records), - ("Просмотр базы знаний", knowledge_base.view_knowledge_base), - ("Генерация вопросов", knowledge_base.generate_questions), - ]) + + # Список разделов и их элементов + menu_sections = [ + ("Работа с базой данных", [ + ("Редактирование пользователей", db_management.edit_users), + ("Управление вопросами", db_management.manage_questions), + ("Просмотр таблиц", db_management.view_tables), + ]), + ("Управление игровым контентом", [ + ("Создание и настройка уровней", content.manage_levels), + ("Настройка параметров собаки", content.manage_dog_params), + ]), + ("Управление интерфейсом пользователя", [ + ("Добавление подсказок в интерфейс", self.manage_ui_tips), + ]), + ("Работа с базой знаний", [ + ("Добавление информации", knowledge_base.add_info), + ("Редактирование записей", knowledge_base.edit_records), + ("Удаление записей", knowledge_base.delete_records), + ("Просмотр базы знаний", knowledge_base.view_knowledge_base), + ("Генерация вопросов", knowledge_base.generate_questions), + ]), + ] + + # Определяем максимальную ширину текста для настройки ширины меню и кнопок + max_text_length = max( + len(title) for title, items in menu_sections + ) + max( + max(len(text) for text, _ in items) for _, items in menu_sections + ) + menu_width = max(300, max_text_length * 10) # Устанавливаем минимальную ширину + + # Обновляем ширину меню + self.menu_frame.config(width=menu_width) + + # Высота одной кнопки и отступов + button_height = 40 + button_spacing = 10 + section_spacing = 15 + + total_height = 0 + + for title, items in menu_sections: + # Заголовок раздела + section_label = tk.Label( + self.menu_frame, + text=title, + bg=MENU_COLOR, + fg=TEXT_COLOR, + font=("Comic Sans MS", 14, "bold"), + anchor="center" # Выравнивание по центру + ) + section_label.pack(fill="x", padx=10, pady=5) + total_height += button_height + section_spacing + + # Кнопки раздела + for text, command in items: + item_button = tk.Button( + self.menu_frame, + text=text, + bg=BUTTON_COLOR, + fg=TEXT_COLOR, + font=("Comic Sans MS", 12), + width=int(menu_width / 10) - 3, # Ширина кнопок зависит от ширины меню + height=1, + activebackground=BUTTON_COLOR, + activeforeground=TEXT_COLOR, + bd=0, + anchor="w", # Выравнивание текста по левому краю + command=lambda: command(self.main_frame) # Вызываем функцию и передаём фрейм + ) + item_button.pack(fill="x", padx=20, pady=5) + total_height += button_height + button_spacing + + # Подстройка высоты меню + self.menu_frame.config(height=total_height) def create_menu_section(self, title, items): section_label = tk.Label(self.menu_frame, text=title, bg=MENU_COLOR, fg=TEXT_COLOR, font=("Comic Sans MS", 14, "bold")) @@ -150,8 +218,14 @@ class AdminApp: tk.Label(frame, text="Здесь будут подсказки для интерфейса", bg=BACKGROUND_COLOR, fg=TEXT_COLOR, font=("Comic Sans MS", 16)).pack() def show_notifications(self, frame): - clear_frame(frame) - tk.Label(frame, text="Здесь будут уведомления", bg=BACKGROUND_COLOR, fg=TEXT_COLOR, font=("Comic Sans MS", 16)).pack() + clear_frame(frame) # Очищаем текущий экран + session = get_session() + notifications = session.query(Notifications).filter_by( + is_read=0).all() # Получаем все непрочитанные уведомления + for notification in notifications: + tk.Label(frame, text=notification.message, bg=BACKGROUND_COLOR, fg=TEXT_COLOR, + font=("Comic Sans MS", 16)).pack() + session.close() def show_security(self, frame): clear_frame(frame)