mirror of
https://github.com/skylanix/MamieHenriette.git
synced 2026-02-06 06:40:35 +01:00
Accueil restructuré (zones Discord/Twitch, stats, indicateur connexion) + Top 3 sanctions/modérateurs + BOT_STATUS dans config Flask
This commit is contained in:
@@ -33,7 +33,7 @@ def _set_sqlite_pragma(dbapi_connection, connection_record):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _tableExists(table_name:str, cursor:Cursor) -> bool:
|
def _tableExists(table_name: str, cursor: Cursor) -> bool:
|
||||||
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name=?", (table_name,))
|
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name=?", (table_name,))
|
||||||
return cursor.fetchone() is not None
|
return cursor.fetchone() is not None
|
||||||
|
|
||||||
@@ -72,7 +72,6 @@ def _doPostImportMigration(cursor:Cursor):
|
|||||||
_dropTable('game_bundle_old', cursor)
|
_dropTable('game_bundle_old', cursor)
|
||||||
|
|
||||||
if _tableExists('youtube_notification', cursor):
|
if _tableExists('youtube_notification', cursor):
|
||||||
logging.info("Migration de la table youtube_notification: ajout des colonnes d'embed")
|
|
||||||
embed_columns = [
|
embed_columns = [
|
||||||
('embed_title', 'VARCHAR(256)'),
|
('embed_title', 'VARCHAR(256)'),
|
||||||
('embed_description', 'VARCHAR(2000)'),
|
('embed_description', 'VARCHAR(2000)'),
|
||||||
@@ -81,7 +80,7 @@ def _doPostImportMigration(cursor:Cursor):
|
|||||||
('embed_author_name', 'VARCHAR(256)'),
|
('embed_author_name', 'VARCHAR(256)'),
|
||||||
('embed_author_icon', 'VARCHAR(512)'),
|
('embed_author_icon', 'VARCHAR(512)'),
|
||||||
('embed_thumbnail', 'BOOLEAN DEFAULT 1'),
|
('embed_thumbnail', 'BOOLEAN DEFAULT 1'),
|
||||||
('embed_image', 'BOOLEAN DEFAULT 1')
|
('embed_image', 'BOOLEAN DEFAULT 1'),
|
||||||
]
|
]
|
||||||
for col_name, col_type in embed_columns:
|
for col_name, col_type in embed_columns:
|
||||||
if not _tableHaveColumn('youtube_notification', col_name, cursor):
|
if not _tableHaveColumn('youtube_notification', col_name, cursor):
|
||||||
@@ -89,8 +88,7 @@ def _doPostImportMigration(cursor:Cursor):
|
|||||||
cursor.execute(f'ALTER TABLE youtube_notification ADD COLUMN {col_name} {col_type}')
|
cursor.execute(f'ALTER TABLE youtube_notification ADD COLUMN {col_name} {col_type}')
|
||||||
logging.info(f"Colonne {col_name} ajoutée à youtube_notification")
|
logging.info(f"Colonne {col_name} ajoutée à youtube_notification")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Impossible d'ajouter la colonne {col_name}: {e}")
|
logging.warning(f"Colonne youtube_notification.{col_name}: {e}")
|
||||||
raise
|
|
||||||
|
|
||||||
with webapp.app_context():
|
with webapp.app_context():
|
||||||
with open('database/schema.sql', 'r') as f:
|
with open('database/schema.sql', 'r') as f:
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ class AntiCheatCache(db.Model):
|
|||||||
notes = db.Column(db.String(1024))
|
notes = db.Column(db.String(1024))
|
||||||
updated_at = db.Column(db.DateTime)
|
updated_at = db.Column(db.DateTime)
|
||||||
|
|
||||||
|
|
||||||
class YouTubeNotification(db.Model):
|
class YouTubeNotification(db.Model):
|
||||||
__tablename__ = 'youtube_notification'
|
__tablename__ = 'youtube_notification'
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import discord
|
|||||||
import logging
|
import logging
|
||||||
import random
|
import random
|
||||||
|
|
||||||
|
from webapp import webapp
|
||||||
from database import db
|
from database import db
|
||||||
from database.helpers import ConfigurationHelper
|
from database.helpers import ConfigurationHelper
|
||||||
from database.models import Configuration, Humeur, Commande
|
from database.models import Configuration, Humeur, Commande
|
||||||
@@ -28,6 +29,8 @@ from protondb import searhProtonDb
|
|||||||
class DiscordBot(discord.Client):
|
class DiscordBot(discord.Client):
|
||||||
async def on_ready(self):
|
async def on_ready(self):
|
||||||
logging.info(f'Connecté en tant que {self.user} (ID: {self.user.id})')
|
logging.info(f'Connecté en tant que {self.user} (ID: {self.user.id})')
|
||||||
|
webapp.config["BOT_STATUS"]["discord_connected"] = True
|
||||||
|
webapp.config["BOT_STATUS"]["discord_guild_count"] = len(self.guilds)
|
||||||
for c in self.get_all_channels() :
|
for c in self.get_all_channels() :
|
||||||
logging.info(f'{c.id} {c.name}')
|
logging.info(f'{c.id} {c.name}')
|
||||||
|
|
||||||
@@ -38,6 +41,9 @@ class DiscordBot(discord.Client):
|
|||||||
self.loop.create_task(self.updateHumbleBundle())
|
self.loop.create_task(self.updateHumbleBundle())
|
||||||
self.loop.create_task(self.updateYouTube())
|
self.loop.create_task(self.updateYouTube())
|
||||||
|
|
||||||
|
async def on_disconnect(self):
|
||||||
|
webapp.config["BOT_STATUS"]["discord_connected"] = False
|
||||||
|
|
||||||
async def updateStatus(self):
|
async def updateStatus(self):
|
||||||
while not self.is_closed():
|
while not self.is_closed():
|
||||||
humeurs = Humeur.query.all()
|
humeurs = Humeur.query.all()
|
||||||
@@ -56,7 +62,6 @@ class DiscordBot(discord.Client):
|
|||||||
async def updateYouTube(self):
|
async def updateYouTube(self):
|
||||||
while not self.is_closed():
|
while not self.is_closed():
|
||||||
await checkYouTubeVideos()
|
await checkYouTubeVideos()
|
||||||
# Vérification toutes les 5 minutes (comme pour Twitch)
|
|
||||||
await asyncio.sleep(5*60)
|
await asyncio.sleep(5*60)
|
||||||
|
|
||||||
def getAllTextChannel(self) -> list[TextChannel]:
|
def getAllTextChannel(self) -> list[TextChannel]:
|
||||||
|
|||||||
@@ -14,8 +14,11 @@ USER_SCOPE = [AuthScope.CHAT_READ, AuthScope.CHAT_EDIT]
|
|||||||
|
|
||||||
async def _onReady(ready_event: EventData):
|
async def _onReady(ready_event: EventData):
|
||||||
logging.info('Bot Twitch prêt')
|
logging.info('Bot Twitch prêt')
|
||||||
|
channel = ConfigurationHelper().getValue('twitch_channel')
|
||||||
|
webapp.config["BOT_STATUS"]["twitch_connected"] = True
|
||||||
|
webapp.config["BOT_STATUS"]["twitch_channel_name"] = channel
|
||||||
with webapp.app_context():
|
with webapp.app_context():
|
||||||
await ready_event.chat.join_room(ConfigurationHelper().getValue('twitch_channel'))
|
await ready_event.chat.join_room(channel)
|
||||||
asyncio.get_event_loop().create_task(twitchBot._checkOnlineStreamers())
|
asyncio.get_event_loop().create_task(twitchBot._checkOnlineStreamers())
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,4 +2,12 @@ from flask import Flask
|
|||||||
|
|
||||||
webapp = Flask(__name__)
|
webapp = Flask(__name__)
|
||||||
|
|
||||||
|
# État des bots (mis à jour par les bots, lu par le panneau)
|
||||||
|
webapp.config["BOT_STATUS"] = {
|
||||||
|
"discord_connected": False,
|
||||||
|
"discord_guild_count": 0,
|
||||||
|
"twitch_connected": False,
|
||||||
|
"twitch_channel_name": None,
|
||||||
|
}
|
||||||
|
|
||||||
from webapp import commandes, configurations, index, humeurs, protondb, live_alert, twitch_auth, moderation, youtube
|
from webapp import commandes, configurations, index, humeurs, protondb, live_alert, twitch_auth, moderation, youtube
|
||||||
|
|||||||
@@ -1,6 +1,16 @@
|
|||||||
from flask import render_template
|
from flask import render_template
|
||||||
from webapp import webapp
|
from webapp import webapp
|
||||||
|
from database.models import ModerationEvent
|
||||||
|
|
||||||
@webapp.route("/")
|
@webapp.route("/")
|
||||||
def index():
|
def index():
|
||||||
return render_template("index.html")
|
status = webapp.config["BOT_STATUS"]
|
||||||
|
sanctions_count = ModerationEvent.query.count()
|
||||||
|
return render_template(
|
||||||
|
"index.html",
|
||||||
|
discord_connected=status["discord_connected"],
|
||||||
|
discord_guild_count=status["discord_guild_count"],
|
||||||
|
sanctions_count=sanctions_count,
|
||||||
|
twitch_connected=status["twitch_connected"],
|
||||||
|
twitch_channel_name=status["twitch_channel_name"],
|
||||||
|
)
|
||||||
|
|||||||
@@ -3,16 +3,58 @@ from webapp import webapp
|
|||||||
from database import db
|
from database import db
|
||||||
from database.models import ModerationEvent
|
from database.models import ModerationEvent
|
||||||
|
|
||||||
|
def _top_sanctioned():
|
||||||
|
return (
|
||||||
|
db.session.query(
|
||||||
|
ModerationEvent.discord_id,
|
||||||
|
db.func.max(ModerationEvent.username).label("username"),
|
||||||
|
db.func.count(ModerationEvent.id).label("count"),
|
||||||
|
)
|
||||||
|
.group_by(ModerationEvent.discord_id)
|
||||||
|
.order_by(db.func.count(ModerationEvent.id).desc())
|
||||||
|
.limit(3)
|
||||||
|
.all()
|
||||||
|
)
|
||||||
|
|
||||||
|
def _top_moderators():
|
||||||
|
return (
|
||||||
|
db.session.query(
|
||||||
|
ModerationEvent.staff_id,
|
||||||
|
db.func.max(ModerationEvent.staff_name).label("staff_name"),
|
||||||
|
db.func.count(ModerationEvent.id).label("count"),
|
||||||
|
)
|
||||||
|
.group_by(ModerationEvent.staff_id)
|
||||||
|
.order_by(db.func.count(ModerationEvent.id).desc())
|
||||||
|
.limit(3)
|
||||||
|
.all()
|
||||||
|
)
|
||||||
|
|
||||||
@webapp.route("/moderation")
|
@webapp.route("/moderation")
|
||||||
def moderation():
|
def moderation():
|
||||||
events = ModerationEvent.query.order_by(ModerationEvent.created_at.desc()).all()
|
events = ModerationEvent.query.order_by(ModerationEvent.created_at.desc()).all()
|
||||||
return render_template("moderation.html", events=events, event=None)
|
top_sanctioned = _top_sanctioned()
|
||||||
|
top_moderators = _top_moderators()
|
||||||
|
return render_template(
|
||||||
|
"moderation.html",
|
||||||
|
events=events,
|
||||||
|
event=None,
|
||||||
|
top_sanctioned=top_sanctioned,
|
||||||
|
top_moderators=top_moderators,
|
||||||
|
)
|
||||||
|
|
||||||
@webapp.route("/moderation/edit/<int:event_id>")
|
@webapp.route("/moderation/edit/<int:event_id>")
|
||||||
def open_edit_moderation_event(event_id):
|
def open_edit_moderation_event(event_id):
|
||||||
event = ModerationEvent.query.get_or_404(event_id)
|
event = ModerationEvent.query.get_or_404(event_id)
|
||||||
events = ModerationEvent.query.order_by(ModerationEvent.created_at.desc()).all()
|
events = ModerationEvent.query.order_by(ModerationEvent.created_at.desc()).all()
|
||||||
return render_template("moderation.html", events=events, event=event)
|
top_sanctioned = _top_sanctioned()
|
||||||
|
top_moderators = _top_moderators()
|
||||||
|
return render_template(
|
||||||
|
"moderation.html",
|
||||||
|
events=events,
|
||||||
|
event=event,
|
||||||
|
top_sanctioned=top_sanctioned,
|
||||||
|
top_moderators=top_moderators,
|
||||||
|
)
|
||||||
|
|
||||||
@webapp.route("/moderation/update/<int:event_id>", methods=['POST'])
|
@webapp.route("/moderation/update/<int:event_id>", methods=['POST'])
|
||||||
def update_moderation_event(event_id):
|
def update_moderation_event(event_id):
|
||||||
|
|||||||
@@ -1,7 +1,97 @@
|
|||||||
{% extends "template.html" %}
|
{% extends "template.html" %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>Bienvenue sur l'interface d'administration de Mamie.</h1>
|
<div class="text-center py-10">
|
||||||
<p>Nous devons définir ce que nous souhaitons afficher sur la page d'accueil. Peut-être l'historique des dernières
|
<h1 class="text-3xl sm:text-4xl font-bold text-slate-800 dark:text-white mb-3">
|
||||||
modifications ? de la modération ?</p>
|
Panneau d'administration
|
||||||
|
</h1>
|
||||||
|
<p class="text-base text-slate-600 dark:text-slate-400 max-w-xl mx-auto">
|
||||||
|
Gérez les fonctionnalités de votre bot Discord et Twitch depuis cette interface.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# Zone Discord #}
|
||||||
|
<div class="bg-white dark:bg-slate-800 rounded-xl border border-slate-200 dark:border-slate-700 overflow-hidden mb-8">
|
||||||
|
<div class="p-4 sm:p-6 border-b border-slate-200 dark:border-slate-700 flex flex-wrap items-center justify-between gap-4">
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<span class="inline-flex items-center justify-center w-3 h-3 rounded-full {% if discord_connected %}bg-emerald-500 ring-4 ring-emerald-500/30{% else %}bg-slate-400 ring-4 ring-slate-400/30{% endif %}" title="{% if discord_connected %}Bot Discord connecté{% else %}Bot Discord déconnecté{% endif %}"></span>
|
||||||
|
<h2 class="text-xl font-semibold text-slate-800 dark:text-white">Discord</h2>
|
||||||
|
</div>
|
||||||
|
<span class="text-sm text-slate-500 dark:text-slate-400">
|
||||||
|
{% if discord_connected %}Connecté{% else %}Déconnecté{% endif %}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="p-4 sm:p-6 grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||||
|
<div class="rounded-lg bg-slate-50 dark:bg-slate-700/50 p-4 border border-slate-200 dark:border-slate-600">
|
||||||
|
<p class="text-sm font-medium text-slate-500 dark:text-slate-400">Serveurs connectés</p>
|
||||||
|
<p class="text-2xl font-bold text-slate-800 dark:text-white mt-1">{{ discord_guild_count }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="rounded-lg bg-slate-50 dark:bg-slate-700/50 p-4 border border-slate-200 dark:border-slate-600">
|
||||||
|
<p class="text-sm font-medium text-slate-500 dark:text-slate-400">Sanctions enregistrées</p>
|
||||||
|
<p class="text-2xl font-bold text-slate-800 dark:text-white mt-1">{{ sanctions_count }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="rounded-lg bg-slate-50 dark:bg-slate-700/50 p-4 border border-slate-200 dark:border-slate-600 sm:col-span-2 lg:col-span-2 flex items-center justify-center">
|
||||||
|
<div class="flex flex-wrap gap-3 justify-center">
|
||||||
|
<a href="/live-alert" class="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-slate-200 dark:bg-slate-600 text-slate-800 dark:text-white text-sm font-medium hover:bg-slate-300 dark:hover:bg-slate-500 transition-colors">Alertes Live</a>
|
||||||
|
<a href="/youtube" class="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-slate-200 dark:bg-slate-600 text-slate-800 dark:text-white text-sm font-medium hover:bg-slate-300 dark:hover:bg-slate-500 transition-colors">Notification YouTube</a>
|
||||||
|
<a href="/humeurs" class="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-slate-200 dark:bg-slate-600 text-slate-800 dark:text-white text-sm font-medium hover:bg-slate-300 dark:hover:bg-slate-500 transition-colors">Humeurs</a>
|
||||||
|
<a href="/protondb" class="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-slate-200 dark:bg-slate-600 text-slate-800 dark:text-white text-sm font-medium hover:bg-slate-300 dark:hover:bg-slate-500 transition-colors">ProtonDB</a>
|
||||||
|
<a href="/commandes" class="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-slate-200 dark:bg-slate-600 text-slate-800 dark:text-white text-sm font-medium hover:bg-slate-300 dark:hover:bg-slate-500 transition-colors">Commandes</a>
|
||||||
|
<a href="/moderation" class="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-slate-200 dark:bg-slate-600 text-slate-800 dark:text-white text-sm font-medium hover:bg-slate-300 dark:hover:bg-slate-500 transition-colors">Modération</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# Zone Twitch #}
|
||||||
|
<div class="bg-white dark:bg-slate-800 rounded-xl border border-slate-200 dark:border-slate-700 overflow-hidden mb-8">
|
||||||
|
<div class="p-4 sm:p-6 border-b border-slate-200 dark:border-slate-700 flex flex-wrap items-center justify-between gap-4">
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<span class="inline-flex items-center justify-center w-3 h-3 rounded-full {% if twitch_connected %}bg-emerald-500 ring-4 ring-emerald-500/30{% else %}bg-slate-400 ring-4 ring-slate-400/30{% endif %}" title="{% if twitch_connected %}Bot Twitch connecté{% else %}Bot Twitch déconnecté{% endif %}"></span>
|
||||||
|
<h2 class="text-xl font-semibold text-slate-800 dark:text-white">Twitch</h2>
|
||||||
|
</div>
|
||||||
|
<span class="text-sm text-slate-500 dark:text-slate-400">
|
||||||
|
{% if twitch_connected %}Connecté{% else %}Déconnecté{% endif %}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="p-4 sm:p-6 grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||||
|
<div class="rounded-lg bg-slate-50 dark:bg-slate-700/50 p-4 border border-slate-200 dark:border-slate-600">
|
||||||
|
<p class="text-sm font-medium text-slate-500 dark:text-slate-400">Canal connecté</p>
|
||||||
|
<p class="text-2xl font-bold text-slate-800 dark:text-white mt-1">{% if twitch_channel_name %}{{ twitch_channel_name }}{% else %}—{% endif %}</p>
|
||||||
|
</div>
|
||||||
|
<div class="rounded-lg bg-slate-50 dark:bg-slate-700/50 p-4 border border-slate-200 dark:border-slate-600">
|
||||||
|
<p class="text-sm font-medium text-slate-500 dark:text-slate-400">Sanctions</p>
|
||||||
|
<p class="text-2xl font-bold text-slate-800 dark:text-white mt-1">—</p>
|
||||||
|
<p class="text-xs text-slate-500 dark:text-slate-400 mt-1">À venir</p>
|
||||||
|
</div>
|
||||||
|
<div class="rounded-lg bg-slate-50 dark:bg-slate-700/50 p-4 border border-slate-200 dark:border-slate-600 sm:col-span-2 lg:col-span-2 flex items-center justify-center">
|
||||||
|
<p class="text-sm text-slate-500 dark:text-slate-400">Intégrations du bot Twitch à venir.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-white dark:bg-slate-800 rounded-lg p-6 border border-slate-200 dark:border-slate-700">
|
||||||
|
<div class="flex items-start gap-4">
|
||||||
|
<div class="flex-shrink-0 w-10 h-10 rounded-lg bg-slate-100 dark:bg-slate-700 flex items-center justify-center">
|
||||||
|
<svg class="w-5 h-5 text-slate-600 dark:text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 class="font-medium text-slate-800 dark:text-white mb-2">À propos</h3>
|
||||||
|
<p class="text-sm text-slate-600 dark:text-slate-400 mb-4">
|
||||||
|
Mamie Henriette est un bot open source pour Discord et Twitch, développé par la communauté.
|
||||||
|
Cette interface vous permet de configurer et gérer toutes les fonctionnalités.
|
||||||
|
</p>
|
||||||
|
<div class="flex flex-wrap gap-3">
|
||||||
|
<a href="https://github.com/skylanix/MamieHenriette" target="_blank" class="inline-flex items-center gap-2 px-3 py-1.5 bg-slate-800 dark:bg-slate-700 text-white rounded text-sm hover:bg-slate-700 dark:hover:bg-slate-600 transition-colors">
|
||||||
|
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24"><path fill-rule="evenodd" d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z" clip-rule="evenodd"></path></svg>
|
||||||
|
GitHub
|
||||||
|
</a>
|
||||||
|
<a href="https://discord.com/invite/UwAPqMJnx3" target="_blank" class="inline-flex items-center gap-2 px-3 py-1.5 bg-slate-100 dark:bg-slate-700 text-slate-700 dark:text-slate-300 border border-slate-200 dark:border-slate-600 rounded text-sm hover:bg-slate-200 dark:hover:bg-slate-600 transition-colors">
|
||||||
|
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24"><path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028 14.09 14.09 0 0 0 1.226-1.994.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z"></path></svg>
|
||||||
|
Discord
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -1,110 +1,215 @@
|
|||||||
{% extends "template.html" %}
|
{% extends "template.html" %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>Modération Discord</h1>
|
<div class="mb-6">
|
||||||
|
<h1 class="text-2xl font-semibold text-slate-800 dark:text-white mb-1">Modération</h1>
|
||||||
|
<p class="text-sm text-slate-600 dark:text-slate-400">
|
||||||
|
Historique des actions de modération sur le serveur Discord.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<p>
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
|
||||||
Historique des actions de modération effectuées sur le serveur Discord.
|
<div class="bg-white dark:bg-slate-800 rounded-lg border border-slate-200 dark:border-slate-700 overflow-hidden">
|
||||||
|
<div class="px-5 py-4 border-b border-slate-200 dark:border-slate-700">
|
||||||
|
<h2 class="text-lg font-medium text-slate-800 dark:text-white">Top 3 sanctions</h2>
|
||||||
|
<p class="text-xs text-slate-500 dark:text-slate-400 mt-0.5">Utilisateurs les plus sanctionnés</p>
|
||||||
|
</div>
|
||||||
|
<div class="divide-y divide-slate-200 dark:divide-slate-700">
|
||||||
|
{% for row in top_sanctioned %}
|
||||||
|
<div class="px-5 py-3 flex items-center justify-between gap-3">
|
||||||
|
<div class="flex items-center gap-3 min-w-0">
|
||||||
|
<span class="flex-shrink-0 w-7 h-7 rounded-full bg-slate-200 dark:bg-slate-600 flex items-center justify-center text-sm font-bold text-slate-700 dark:text-slate-300">{{ loop.index }}</span>
|
||||||
|
<div class="min-w-0">
|
||||||
|
<span class="block text-sm font-medium text-slate-800 dark:text-white truncate">{{ row.username or '—' }}</span>
|
||||||
|
<span class="block text-xs text-slate-500 dark:text-slate-400 font-mono truncate">{{ row.discord_id }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span class="flex-shrink-0 text-sm font-semibold text-slate-600 dark:text-slate-300">{{ row.count }} sanction{{ 's' if row.count > 1 else '' }}</span>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="px-5 py-6 text-center text-sm text-slate-500 dark:text-slate-400">Aucune sanction enregistrée</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bg-white dark:bg-slate-800 rounded-lg border border-slate-200 dark:border-slate-700 overflow-hidden">
|
||||||
|
<div class="px-5 py-4 border-b border-slate-200 dark:border-slate-700">
|
||||||
|
<h2 class="text-lg font-medium text-slate-800 dark:text-white">Top 3 modérateurs</h2>
|
||||||
|
<p class="text-xs text-slate-500 dark:text-slate-400 mt-0.5">Staff ayant effectué le plus d'actions</p>
|
||||||
|
</div>
|
||||||
|
<div class="divide-y divide-slate-200 dark:divide-slate-700">
|
||||||
|
{% for row in top_moderators %}
|
||||||
|
<div class="px-5 py-3 flex items-center justify-between gap-3">
|
||||||
|
<div class="flex items-center gap-3 min-w-0">
|
||||||
|
<span class="flex-shrink-0 w-7 h-7 rounded-full bg-slate-200 dark:bg-slate-600 flex items-center justify-center text-sm font-bold text-slate-700 dark:text-slate-300">{{ loop.index }}</span>
|
||||||
|
<div class="min-w-0">
|
||||||
|
<span class="block text-sm font-medium text-slate-800 dark:text-white truncate">{{ row.staff_name or '—' }}</span>
|
||||||
|
<span class="block text-xs text-slate-500 dark:text-slate-400 font-mono truncate">{{ row.staff_id }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span class="flex-shrink-0 text-sm font-semibold text-slate-600 dark:text-slate-300">{{ row.count }} action{{ 's' if row.count > 1 else '' }}</span>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="px-5 py-6 text-center text-sm text-slate-500 dark:text-slate-400">Aucune action enregistrée</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
Le bot enregistre automatiquement les avertissements, exclusions et bannissements.
|
<div class="bg-white dark:bg-slate-800 rounded-lg border border-slate-200 dark:border-slate-700 overflow-hidden mb-6">
|
||||||
|
<details class="group">
|
||||||
<table>
|
<summary class="flex items-center justify-between px-5 py-4 cursor-pointer hover:bg-slate-50 dark:hover:bg-slate-700/30 transition-colors">
|
||||||
<thead>
|
<span class="font-medium text-slate-800 dark:text-white">Commandes de modération disponibles</span>
|
||||||
<tr>
|
<svg class="w-5 h-5 text-slate-400 group-open:rotate-180 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path></svg>
|
||||||
<th>Commande</th>
|
</summary>
|
||||||
<th>Description</th>
|
<div class="border-t border-slate-200 dark:border-slate-700">
|
||||||
</tr>
|
<div class="divide-y divide-slate-200 dark:divide-slate-700 text-sm">
|
||||||
</thead>
|
<div class="px-5 py-3 flex flex-col sm:flex-row sm:items-start gap-1 sm:gap-4">
|
||||||
<tbody>
|
<code class="text-slate-700 dark:text-slate-300 font-mono">!averto @user raison</code>
|
||||||
<tr>
|
<span class="text-slate-500 dark:text-slate-400">Avertit un utilisateur</span>
|
||||||
<td><strong>!averto @utilisateur raison</strong><br><small>Alias : !warn, !av, !avertissement</small></td>
|
</div>
|
||||||
<td>Avertit un utilisateur et enregistre l'avertissement dans la base de données</td>
|
<div class="px-5 py-3 flex flex-col sm:flex-row sm:items-start gap-1 sm:gap-4">
|
||||||
</tr>
|
<code class="text-slate-700 dark:text-slate-300 font-mono">!delaverto id</code>
|
||||||
<tr>
|
<span class="text-slate-500 dark:text-slate-400">Retire un avertissement</span>
|
||||||
<td><strong>!delaverto id</strong><br><small>Alias : !removewarn, !delwarn</small></td>
|
</div>
|
||||||
<td>Retire un avertissement en utilisant son numéro d'ID</td>
|
<div class="px-5 py-3 flex flex-col sm:flex-row sm:items-start gap-1 sm:gap-4">
|
||||||
</tr>
|
<code class="text-slate-700 dark:text-slate-300 font-mono">!warnings [@user]</code>
|
||||||
<tr>
|
<span class="text-slate-500 dark:text-slate-400">Liste les événements de modération</span>
|
||||||
<td><strong>!warnings</strong> ou <strong>!warnings @utilisateur</strong><br><small>Alias : !listevent, !listwarn</small></td>
|
</div>
|
||||||
<td>Affiche la liste des événements de modération (tous ou pour un utilisateur spécifique)</td>
|
<div class="px-5 py-3 flex flex-col sm:flex-row sm:items-start gap-1 sm:gap-4">
|
||||||
</tr>
|
<code class="text-slate-700 dark:text-slate-300 font-mono">!inspect @user</code>
|
||||||
<tr>
|
<span class="text-slate-500 dark:text-slate-400">Informations sur un utilisateur</span>
|
||||||
<td><strong>!inspect @utilisateur</strong> ou <strong>!inspect id</strong></td>
|
</div>
|
||||||
<td>Affiche des informations détaillées sur un utilisateur : création du compte, date d'arrivée, historique de modération</td>
|
<div class="px-5 py-3 flex flex-col sm:flex-row sm:items-start gap-1 sm:gap-4">
|
||||||
</tr>
|
<code class="text-slate-700 dark:text-slate-300 font-mono">!kick @user raison</code>
|
||||||
<tr>
|
<span class="text-slate-500 dark:text-slate-400">Expulse un utilisateur</span>
|
||||||
<td><strong>!kick @utilisateur raison</strong></td>
|
</div>
|
||||||
<td>Expulse un utilisateur du serveur</td>
|
<div class="px-5 py-3 flex flex-col sm:flex-row sm:items-start gap-1 sm:gap-4">
|
||||||
</tr>
|
<code class="text-slate-700 dark:text-slate-300 font-mono">!ban @user raison</code>
|
||||||
<tr>
|
<span class="text-slate-500 dark:text-slate-400">Bannit un utilisateur</span>
|
||||||
<td><strong>!ban @utilisateur raison</strong></td>
|
</div>
|
||||||
<td>Bannit définitivement un utilisateur du serveur</td>
|
<div class="px-5 py-3 flex flex-col sm:flex-row sm:items-start gap-1 sm:gap-4">
|
||||||
</tr>
|
<code class="text-slate-700 dark:text-slate-300 font-mono">!unban id</code>
|
||||||
<tr>
|
<span class="text-slate-500 dark:text-slate-400">Révoque un bannissement</span>
|
||||||
<td><strong>!unban discord_id</strong> ou <strong>!unban #sanction_id raison</strong></td>
|
</div>
|
||||||
<td>Révoque le bannissement d'un utilisateur et lui envoie une invitation</td>
|
<div class="px-5 py-3 flex flex-col sm:flex-row sm:items-start gap-1 sm:gap-4">
|
||||||
</tr>
|
<code class="text-slate-700 dark:text-slate-300 font-mono">!banlist</code>
|
||||||
<tr>
|
<span class="text-slate-500 dark:text-slate-400">Liste des utilisateurs bannis</span>
|
||||||
<td><strong>!banlist</strong></td>
|
</div>
|
||||||
<td>Affiche la liste des utilisateurs actuellement bannis du serveur</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
</details>
|
||||||
<td><strong>!aide</strong><br><small>Alias : !help</small></td>
|
</div>
|
||||||
<td>Affiche l'aide avec toutes les commandes disponibles</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{% if not event %}
|
{% if not event %}
|
||||||
<h2>Événements de modération</h2>
|
<div class="bg-white dark:bg-slate-800 rounded-lg border border-slate-200 dark:border-slate-700 overflow-hidden mb-6">
|
||||||
<table class="moderation">
|
<div class="px-5 py-4 border-b border-slate-200 dark:border-slate-700">
|
||||||
|
<h2 class="text-lg font-medium text-slate-800 dark:text-white">Événements de modération</h2>
|
||||||
|
</div>
|
||||||
|
<div class="overflow-x-auto">
|
||||||
|
<table class="w-full">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr class="bg-slate-50 dark:bg-slate-700/50 border-b border-slate-200 dark:border-slate-700">
|
||||||
<th>Type</th>
|
<th class="px-4 py-3 text-left text-xs font-medium text-slate-500 dark:text-slate-400 uppercase">Type</th>
|
||||||
<th>Utilisateur</th>
|
<th class="px-4 py-3 text-left text-xs font-medium text-slate-500 dark:text-slate-400 uppercase">Utilisateur</th>
|
||||||
<th>Discord ID</th>
|
<th class="px-4 py-3 text-left text-xs font-medium text-slate-500 dark:text-slate-400 uppercase">Date</th>
|
||||||
<th>Date & Heure</th>
|
<th class="px-4 py-3 text-left text-xs font-medium text-slate-500 dark:text-slate-400 uppercase">Raison</th>
|
||||||
<th>Raison</th>
|
<th class="px-4 py-3 text-left text-xs font-medium text-slate-500 dark:text-slate-400 uppercase">Staff</th>
|
||||||
<th>Staff</th>
|
<th class="px-4 py-3 text-right text-xs font-medium text-slate-500 dark:text-slate-400 uppercase">Actions</th>
|
||||||
<th>#</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody class="divide-y divide-slate-200 dark:divide-slate-700">
|
||||||
{% for mod_event in events %}
|
{% for mod_event in events %}
|
||||||
|
<tr class="hover:bg-slate-50 dark:hover:bg-slate-700/30 transition-colors">
|
||||||
|
<td class="px-4 py-3">
|
||||||
|
{% if mod_event.type == 'ban' %}
|
||||||
|
<span class="text-xs font-medium text-red-600 dark:text-red-400">Ban</span>
|
||||||
|
{% elif mod_event.type == 'kick' %}
|
||||||
|
<span class="text-xs font-medium text-orange-600 dark:text-orange-400">Kick</span>
|
||||||
|
{% elif mod_event.type == 'warn' or mod_event.type == 'warning' %}
|
||||||
|
<span class="text-xs font-medium text-yellow-600 dark:text-yellow-400">Warn</span>
|
||||||
|
{% elif mod_event.type == 'unban' %}
|
||||||
|
<span class="text-xs font-medium text-green-600 dark:text-green-400">Unban</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="text-xs font-medium text-slate-600 dark:text-slate-400">{{ mod_event.type }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-3">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<span class="text-sm font-medium text-slate-800 dark:text-white">{{ mod_event.username }}</span>
|
||||||
|
<span class="text-xs text-slate-500 dark:text-slate-400 font-mono">{{ mod_event.discord_id }}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-3 text-sm text-slate-600 dark:text-slate-400 whitespace-nowrap">
|
||||||
|
{{ mod_event.created_at.strftime('%d/%m/%Y %H:%M') if mod_event.created_at else 'N/A' }}
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-3 text-sm text-slate-600 dark:text-slate-400 max-w-xs">
|
||||||
|
<div class="line-clamp-2">{{ mod_event.reason }}</div>
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-3 text-sm text-slate-600 dark:text-slate-400">
|
||||||
|
{{ mod_event.staff_name }}
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-3">
|
||||||
|
<div class="flex items-center justify-end gap-2">
|
||||||
|
<a href="{{ url_for('open_edit_moderation_event', event_id = mod_event.id) }}" class="text-sm text-slate-500 hover:text-slate-700 dark:hover:text-slate-300 transition-colors">
|
||||||
|
Modifier
|
||||||
|
</a>
|
||||||
|
<a href="{{ url_for('delete_moderation_event', event_id = mod_event.id) }}" onclick="return confirm('Êtes-vous sûr de vouloir supprimer cet événement ?')" class="text-sm text-slate-500 hover:text-red-600 dark:hover:text-red-400 transition-colors">
|
||||||
|
Supprimer
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% else %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ mod_event.type }}</td>
|
<td colspan="6" class="px-4 py-8 text-center text-sm text-slate-500 dark:text-slate-400">
|
||||||
<td>{{ mod_event.username }}</td>
|
Aucun événement de modération
|
||||||
<td>{{ mod_event.discord_id }}</td>
|
|
||||||
<td>{{ mod_event.created_at.strftime('%d/%m/%Y %H:%M') if mod_event.created_at else 'N/A' }}</td>
|
|
||||||
<td>{{ mod_event.reason }}</td>
|
|
||||||
<td>{{ mod_event.staff_name }}</td>
|
|
||||||
<td>
|
|
||||||
<a href="{{ url_for('open_edit_moderation_event', event_id = mod_event.id) }}" class="icon">✐</a>
|
|
||||||
<a href="{{ url_for('delete_moderation_event', event_id = mod_event.id) }}" onclick="return confirm('Êtes-vous sûr de vouloir supprimer cet événement ?')" class="icon">🗑</a>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if event %}
|
{% if event %}
|
||||||
<h2>Editer un événement</h2>
|
<div class="bg-white dark:bg-slate-800 rounded-lg border border-slate-200 dark:border-slate-700 p-5">
|
||||||
<form action="{{ url_for('update_moderation_event', event_id = event.id) }}" method="POST">
|
<h2 class="text-lg font-medium text-slate-800 dark:text-white mb-5">Modifier l'événement</h2>
|
||||||
<label for="type">Type</label>
|
|
||||||
<input name="type" type="text" value="{{ event.type }}" disabled />
|
|
||||||
<label for="username">Utilisateur</label>
|
|
||||||
<input name="username" type="text" value="{{ event.username }}" disabled />
|
|
||||||
<label for="discord_id">Discord ID</label>
|
|
||||||
<input name="discord_id" type="text" value="{{ event.discord_id }}" disabled />
|
|
||||||
<label for="reason">Raison</label>
|
|
||||||
<input name="reason" type="text" value="{{ event.reason }}" required="required" />
|
|
||||||
<label for="staff_name">Staff</label>
|
|
||||||
<input name="staff_name" type="text" value="{{ event.staff_name }}" disabled />
|
|
||||||
<input type="Submit" value="Modifier">
|
|
||||||
<a href="{{ url_for('moderation') }}">Annuler</a>
|
|
||||||
</form>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
|
<form action="{{ url_for('update_moderation_event', event_id = event.id) }}" method="POST" class="space-y-6">
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">Type</label>
|
||||||
|
<input type="text" value="{{ event.type }}" disabled class="w-full px-3 py-2 bg-slate-100 dark:bg-slate-700 border border-slate-300 dark:border-slate-600 rounded-lg text-sm text-slate-500 dark:text-slate-400 cursor-not-allowed">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">Staff</label>
|
||||||
|
<input type="text" value="{{ event.staff_name }}" disabled class="w-full px-3 py-2 bg-slate-100 dark:bg-slate-700 border border-slate-300 dark:border-slate-600 rounded-lg text-sm text-slate-500 dark:text-slate-400 cursor-not-allowed">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">Utilisateur</label>
|
||||||
|
<input type="text" value="{{ event.username }}" disabled class="w-full px-3 py-2 bg-slate-100 dark:bg-slate-700 border border-slate-300 dark:border-slate-600 rounded-lg text-sm text-slate-500 dark:text-slate-400 cursor-not-allowed">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">Discord ID</label>
|
||||||
|
<input type="text" value="{{ event.discord_id }}" disabled class="w-full px-3 py-2 bg-slate-100 dark:bg-slate-700 border border-slate-300 dark:border-slate-600 rounded-lg text-sm text-slate-500 dark:text-slate-400 cursor-not-allowed font-mono">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="reason" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">Raison</label>
|
||||||
|
<input type="text" name="reason" id="reason" value="{{ event.reason }}" required class="w-full px-3 py-2 bg-slate-50 dark:bg-slate-700 border border-slate-300 dark:border-slate-600 rounded-lg text-sm text-slate-900 dark:text-white focus:ring-2 focus:ring-slate-500 focus:border-transparent transition-all">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center justify-end gap-3">
|
||||||
|
<a href="{{ url_for('moderation') }}" class="px-4 py-2 text-slate-700 dark:text-slate-300 text-sm font-medium rounded-lg hover:bg-slate-100 dark:hover:bg-slate-700 transition-colors">
|
||||||
|
Annuler
|
||||||
|
</a>
|
||||||
|
<button type="submit" class="px-4 py-2 bg-slate-800 hover:bg-slate-700 dark:bg-slate-700 dark:hover:bg-slate-600 text-white text-sm font-medium rounded-lg transition-colors">
|
||||||
|
Enregistrer
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -1,40 +1,249 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html color-mode="user">
|
<html lang="fr" class="h-full">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<meta name="mobile-web-app-capable" content="yes" />
|
<meta name="mobile-web-app-capable" content="yes">
|
||||||
<title>Mamie Henriette</title>
|
<title>Mamie Henriette</title>
|
||||||
<link rel="stylesheet" href="/static/css/mvp.css" />
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
<link rel="stylesheet" href="/static/css/style.css" />
|
<script>
|
||||||
|
tailwind.config = {
|
||||||
|
darkMode: 'class',
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
primary: {
|
||||||
|
50: '#f8fafc',
|
||||||
|
100: '#f1f5f9',
|
||||||
|
200: '#e2e8f0',
|
||||||
|
300: '#cbd5e1',
|
||||||
|
400: '#94a3b8',
|
||||||
|
500: '#64748b',
|
||||||
|
600: '#475569',
|
||||||
|
700: '#334155',
|
||||||
|
800: '#1e293b',
|
||||||
|
900: '#0f172a',
|
||||||
|
},
|
||||||
|
accent: {
|
||||||
|
50: '#f0fdf4',
|
||||||
|
100: '#dcfce7',
|
||||||
|
200: '#bbf7d0',
|
||||||
|
300: '#86efac',
|
||||||
|
400: '#4ade80',
|
||||||
|
500: '#22c55e',
|
||||||
|
600: '#16a34a',
|
||||||
|
700: '#15803d',
|
||||||
|
800: '#166534',
|
||||||
|
900: '#14532d',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
<link rel="icon" href="/static/ico/favicon.ico" type="image/x-icon">
|
<link rel="icon" href="/static/ico/favicon.ico" type="image/x-icon">
|
||||||
<link rel="shortcut icon" href="/static/ico/favicon.ico" type="image/x-icon">
|
<link rel="shortcut icon" href="/static/ico/favicon.ico" type="image/x-icon">
|
||||||
|
<style>
|
||||||
|
/* Animations personnalisées */
|
||||||
|
.fade-in {
|
||||||
|
animation: fadeIn 0.3s ease-in-out;
|
||||||
|
}
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from { opacity: 0; transform: translateY(-10px); }
|
||||||
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
}
|
||||||
|
/* Scrollbar personnalisée */
|
||||||
|
::-webkit-scrollbar { width: 8px; height: 8px; }
|
||||||
|
::-webkit-scrollbar-track { background: transparent; }
|
||||||
|
::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 4px; }
|
||||||
|
::-webkit-scrollbar-thumb:hover { background: #94a3b8; }
|
||||||
|
.dark ::-webkit-scrollbar-thumb { background: #475569; }
|
||||||
|
.dark ::-webkit-scrollbar-thumb:hover { background: #64748b; }
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body class="h-full bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100 transition-colors duration-200">
|
||||||
<header>
|
<!-- Navbar -->
|
||||||
<nav>
|
<nav class="fixed top-0 left-0 right-0 z-50 bg-white dark:bg-gray-800 shadow-md border-b border-gray-200 dark:border-gray-700">
|
||||||
<a href="/"><img src="/static/ico/favicon.ico"></a>
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
<ul>
|
<div class="flex items-center justify-between h-16">
|
||||||
<li><a href="/live-alert">Alerte live</a></li>
|
<!-- Logo -->
|
||||||
<li><a href="/youtube">YouTube</a></li>
|
<a href="/" class="flex items-center gap-3 group">
|
||||||
<li><a href="/commandes">Commandes</a></li>
|
<img src="/static/ico/favicon.ico" alt="Mamie Henriette" class="w-10 h-10 rounded-full ring-2 ring-slate-200 dark:ring-slate-600 group-hover:ring-slate-300 dark:group-hover:ring-slate-500 transition-all">
|
||||||
<li><a href="/humeurs">Humeurs</a></li>
|
<span class="font-bold text-xl text-slate-800 dark:text-white hidden sm:block">Mamie Henriette</span>
|
||||||
<li><a href="/moderation">Modération</a></li>
|
</a>
|
||||||
<li><a href="/protondb">ProtonDB</a></li>
|
|
||||||
<li><a href="/configurations">Configurations</a></li>
|
<!-- Navigation Desktop -->
|
||||||
</ul>
|
<div class="hidden md:flex items-center gap-1">
|
||||||
|
<!-- Discord (sous-menus) -->
|
||||||
|
<div class="relative group">
|
||||||
|
<button type="button" class="px-4 py-2 rounded-lg text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-primary-600 dark:hover:text-primary-400 transition-all flex items-center gap-1.5">
|
||||||
|
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24"><path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028 14.09 14.09 0 0 0 1.226-1.994.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z"/></svg>
|
||||||
|
Discord
|
||||||
|
<svg class="w-4 h-4 transition-transform group-hover:rotate-180" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path></svg>
|
||||||
|
</button>
|
||||||
|
<div class="absolute left-0 top-full pt-1 opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200">
|
||||||
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg border border-gray-200 dark:border-gray-700 py-1 min-w-[200px]">
|
||||||
|
<a href="/humeurs" class="flex items-center gap-2 px-4 py-2.5 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors">
|
||||||
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14.828 14.828a4 4 0 01-5.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
|
||||||
|
Humeur
|
||||||
|
</a>
|
||||||
|
<a href="/live-alert" class="flex items-center gap-2 px-4 py-2.5 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors">
|
||||||
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg>
|
||||||
|
Notification Twitch
|
||||||
|
</a>
|
||||||
|
<a href="/youtube" class="flex items-center gap-2 px-4 py-2.5 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors">
|
||||||
|
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24"><path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"/></svg>
|
||||||
|
Notification YouTube
|
||||||
|
</a>
|
||||||
|
<a href="/protondb" class="flex items-center gap-2 px-4 py-2.5 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors">
|
||||||
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 4a2 2 0 114 0v1a1 1 0 001 1h3a1 1 0 011 1v3a1 1 0 01-1 1h-1a2 2 0 100 4h1a1 1 0 011 1v3a1 1 0 01-1 1h-3a1 1 0 01-1-1v-1a2 2 0 10-4 0v1a1 1 0 01-1 1H7a1 1 0 01-1-1v-3a1 1 0 00-1-1H4a2 2 0 110-4h1a1 1 0 001-1V7a1 1 0 011-1h3a1 1 0 001-1V4z"></path></svg>
|
||||||
|
ProtonDB
|
||||||
|
</a>
|
||||||
|
<div class="border-t border-gray-200 dark:border-gray-700 my-1"></div>
|
||||||
|
<a href="/commandes" class="flex items-center gap-2 px-4 py-2.5 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors">
|
||||||
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path></svg>
|
||||||
|
Commandes
|
||||||
|
</a>
|
||||||
|
<a href="/moderation" class="flex items-center gap-2 px-4 py-2.5 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors">
|
||||||
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"></path></svg>
|
||||||
|
Modération
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Twitch (futur bot) -->
|
||||||
|
<div class="relative group">
|
||||||
|
<button type="button" class="px-4 py-2 rounded-lg text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-primary-600 dark:hover:text-primary-400 transition-all flex items-center gap-1.5">
|
||||||
|
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24"><path d="M11.571 4.714h1.715v5.143H11.57l-.002-5.143zm3.43 0H16.714v5.143H15V4.714zM6 0L1.714 4.286v15.428h5.143V24l4.286-4.286h3.428L22.286 12V0H6zm14.571 11.143l-3.428 3.428h-3.429l-3 3v-3H6.857V1.714h13.714v9.429z"/></svg>
|
||||||
|
Twitch
|
||||||
|
<svg class="w-4 h-4 transition-transform group-hover:rotate-180" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path></svg>
|
||||||
|
</button>
|
||||||
|
<div class="absolute left-0 top-full pt-1 opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200">
|
||||||
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg border border-gray-200 dark:border-gray-700 py-1 min-w-[200px]">
|
||||||
|
<a href="/live-alert" class="flex items-center gap-2 px-4 py-2.5 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors">
|
||||||
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg>
|
||||||
|
Alerte live
|
||||||
|
</a>
|
||||||
|
<span class="flex items-center gap-2 px-4 py-2.5 text-sm text-gray-400 dark:text-gray-500 italic">
|
||||||
|
Bot Twitch — à venir
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Configuration locale -->
|
||||||
|
<a href="/configurations" class="px-4 py-2 rounded-lg text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-primary-600 dark:hover:text-primary-400 transition-all flex items-center gap-2">
|
||||||
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path></svg>
|
||||||
|
Configuration
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Actions -->
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<!-- Dark Mode Toggle -->
|
||||||
|
<button onclick="toggleDarkMode()" class="p-2 rounded-lg text-gray-500 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-primary-600 dark:hover:text-primary-400 transition-all" title="Mode sombre">
|
||||||
|
<svg class="w-5 h-5 hidden dark:block" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"></path></svg>
|
||||||
|
<svg class="w-5 h-5 block dark:hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"></path></svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Mobile Menu Button -->
|
||||||
|
<button onclick="toggleMobileMenu()" class="md:hidden p-2 rounded-lg text-gray-500 hover:bg-gray-100 dark:hover:bg-gray-700 transition-all">
|
||||||
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path></svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Mobile Menu -->
|
||||||
|
<div id="mobile-menu" class="hidden md:hidden border-t border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800">
|
||||||
|
<div class="px-4 py-3 space-y-1">
|
||||||
|
<a href="/live-alert" class="flex items-center gap-3 px-4 py-3 rounded-lg text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 transition-all">
|
||||||
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg>
|
||||||
|
Alerte live
|
||||||
|
</a>
|
||||||
|
<a href="/youtube" class="flex items-center gap-3 px-4 py-3 rounded-lg text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 transition-all">
|
||||||
|
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24"><path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"/></svg>
|
||||||
|
YouTube
|
||||||
|
</a>
|
||||||
|
<a href="/commandes" class="flex items-center gap-3 px-4 py-3 rounded-lg text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 transition-all">
|
||||||
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path></svg>
|
||||||
|
Commandes
|
||||||
|
</a>
|
||||||
|
<a href="/humeurs" class="flex items-center gap-3 px-4 py-3 rounded-lg text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 transition-all">
|
||||||
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14.828 14.828a4 4 0 01-5.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
|
||||||
|
Humeurs
|
||||||
|
</a>
|
||||||
|
<a href="/moderation" class="flex items-center gap-3 px-4 py-3 rounded-lg text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 transition-all">
|
||||||
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"></path></svg>
|
||||||
|
Modération
|
||||||
|
</a>
|
||||||
|
<a href="/protondb" class="flex items-center gap-3 px-4 py-3 rounded-lg text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 transition-all">
|
||||||
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 4a2 2 0 114 0v1a1 1 0 001 1h3a1 1 0 011 1v3a1 1 0 01-1 1h-1a2 2 0 100 4h1a1 1 0 011 1v3a1 1 0 01-1 1h-3a1 1 0 01-1-1v-1a2 2 0 10-4 0v1a1 1 0 01-1 1H7a1 1 0 01-1-1v-3a1 1 0 00-1-1H4a2 2 0 110-4h1a1 1 0 001-1V7a1 1 0 011-1h3a1 1 0 001-1V4z"></path></svg>
|
||||||
|
ProtonDB
|
||||||
|
</a>
|
||||||
|
<a href="/configurations" class="flex items-center gap-3 px-4 py-3 rounded-lg text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 transition-all">
|
||||||
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path></svg>
|
||||||
|
Configurations
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
|
||||||
<main>
|
<!-- Main Content -->
|
||||||
|
<main class="pt-20 pb-12 min-h-screen">
|
||||||
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div class="fade-in">
|
||||||
{% block content %}{% endblock %}
|
{% block content %}{% endblock %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
<footer>
|
|
||||||
<hr>
|
<!-- Footer -->
|
||||||
<p><a href="https://github.com/skylanix/MamieHenriette" target="_blank">MamieHenriette</a> créé par la communauté <a href="https://discord.com/invite/UwAPqMJnx3" target="_blank">Discord</a> de <a href="https://www.youtube.com/@513v3" target="_blank">573v3</a> - Projet open source sous licence <a href="https://www.gnu.org/licenses/agpl-3.0.html" target="_blank">AGPLv3</a></p>
|
<footer class="bg-white dark:bg-gray-800 border-t border-gray-200 dark:border-gray-700">
|
||||||
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
||||||
|
<div class="flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||||
|
<div class="flex items-center gap-2 text-sm text-gray-600 dark:text-gray-400">
|
||||||
|
<img src="/static/ico/favicon.ico" alt="" class="w-6 h-6 rounded-full">
|
||||||
|
<span>Mamie Henriette</span>
|
||||||
|
</div>
|
||||||
|
<p class="text-sm text-gray-500 dark:text-gray-400 text-center">
|
||||||
|
Créé par la communauté
|
||||||
|
<a href="https://discord.com/invite/UwAPqMJnx3" target="_blank" class="text-primary-600 dark:text-primary-400 hover:underline">Discord</a>
|
||||||
|
de
|
||||||
|
<a href="https://www.youtube.com/@513v3" target="_blank" class="text-primary-600 dark:text-primary-400 hover:underline">573v3</a>
|
||||||
|
</p>
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<a href="https://github.com/skylanix/MamieHenriette" target="_blank" class="text-gray-500 hover:text-gray-700 dark:hover:text-gray-300 transition-colors">
|
||||||
|
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24"><path fill-rule="evenodd" d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z" clip-rule="evenodd"></path></svg>
|
||||||
|
</a>
|
||||||
|
<span class="text-xs text-gray-400 dark:text-gray-500">AGPLv3</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Dark mode
|
||||||
|
function toggleDarkMode() {
|
||||||
|
document.documentElement.classList.toggle('dark');
|
||||||
|
localStorage.setItem('darkMode', document.documentElement.classList.contains('dark'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize dark mode from preference
|
||||||
|
if (localStorage.getItem('darkMode') === 'true' ||
|
||||||
|
(!localStorage.getItem('darkMode') && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
||||||
|
document.documentElement.classList.add('dark');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mobile menu
|
||||||
|
function toggleMobileMenu() {
|
||||||
|
document.getElementById('mobile-menu').classList.toggle('hidden');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
Reference in New Issue
Block a user