", methods=['POST'])
+def api_revoke_invitation(invite_code):
+ """API pour révoquer une invitation"""
+ result = discord_bridge.revoke_invite(invite_code)
+ return jsonify(result)
+
diff --git a/webapp/static/css/mvp.css b/webapp/static/css/mvp.css
deleted file mode 100644
index 3c3d2b6..0000000
--- a/webapp/static/css/mvp.css
+++ /dev/null
@@ -1,603 +0,0 @@
-/* MVP.css v1.17.2 - https://github.com/andybrewer/mvp */
-
-:root {
- --active-brightness: 0.85;
- --border-radius: 5px;
- --box-shadow: 2px 2px 10px;
- --color-accent: #118bee15;
- --color-bg: #fff;
- --color-bg-secondary: #e9e9e9;
- --color-link: #118bee;
- --color-secondary: #920de9;
- --color-secondary-accent: #920de90b;
- --color-shadow: #f4f4f4;
- --color-table: #118bee;
- --color-text: #000;
- --color-text-secondary: #999;
- --color-scrollbar: #cacae8;
- --font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
- --hover-brightness: 1.2;
- --justify-important: center;
- --justify-normal: left;
- --line-height: 1.5;
- --width-card: 285px;
- --width-card-medium: 460px;
- --width-card-wide: 800px;
- --width-content: 1080px;
-}
-
-@media (prefers-color-scheme: dark) {
- :root[color-mode="user"] {
- --color-accent: #0097fc4f;
- --color-bg: #333;
- --color-bg-secondary: #555;
- --color-link: #0097fc;
- --color-secondary: #e20de9;
- --color-secondary-accent: #e20de94f;
- --color-shadow: #bbbbbb20;
- --color-table: #0097fc;
- --color-text: #f7f7f7;
- --color-text-secondary: #aaa;
- }
-}
-
-html {
- scroll-behavior: smooth;
-}
-
-@media (prefers-reduced-motion: reduce) {
- html {
- scroll-behavior: auto;
- }
-}
-
-/* Layout */
-article aside {
- background: var(--color-secondary-accent);
- border-left: 4px solid var(--color-secondary);
- padding: 0.01rem 0.8rem;
-}
-
-body {
- background: var(--color-bg);
- color: var(--color-text);
- font-family: var(--font-family);
- line-height: var(--line-height);
- margin: 0;
- overflow-x: hidden;
- padding: 0;
-}
-
-footer,
-header,
-main {
- margin: 0 auto;
- max-width: var(--width-content);
- /* padding: 3rem 1rem; */
- padding: 1rem 1rem;
-}
-
-hr {
- background-color: var(--color-bg-secondary);
- border: none;
- height: 1px;
- margin: 4rem 0;
- width: 100%;
-}
-
-section {
- display: flex;
- flex-wrap: wrap;
- justify-content: var(--justify-important);
-}
-
-section img,
-article img {
- max-width: 100%;
-}
-
-section pre {
- overflow: auto;
-}
-
-section aside {
- border: 1px solid var(--color-bg-secondary);
- border-radius: var(--border-radius);
- box-shadow: var(--box-shadow) var(--color-shadow);
- margin: 1rem;
- padding: 1.25rem;
- width: var(--width-card);
-}
-
-section aside:hover {
- box-shadow: var(--box-shadow) var(--color-bg-secondary);
-}
-
-[hidden] {
- display: none;
-}
-
-/* Headers */
-article header,
-div header,
-main header {
- padding-top: 0;
-}
-
-header {
- text-align: var(--justify-important);
-}
-
-header a b,
-header a em,
-header a i,
-header a strong {
- margin-left: 0.5rem;
- margin-right: 0.5rem;
-}
-
-/* header nav img {
- margin: 1rem 0;
-} */
-
-section header {
- padding-top: 0;
- width: 100%;
-}
-
-/* Nav */
-nav {
- align-items: center;
- display: flex;
- font-weight: bold;
- justify-content: space-between;
- /* margin-bottom: 7rem; */
-}
-
-nav ul {
- list-style: none;
- padding: 0;
-}
-
-nav ul li {
- display: inline-block;
- margin: 0 0.5rem;
- position: relative;
- text-align: left;
-}
-
-/* Nav Dropdown */
-nav ul li:hover ul {
- display: block;
-}
-
-nav ul li ul {
- background: var(--color-bg);
- border: 1px solid var(--color-bg-secondary);
- border-radius: var(--border-radius);
- box-shadow: var(--box-shadow) var(--color-shadow);
- display: none;
- height: auto;
- left: -2px;
- padding: 0.5rem 1rem;
- position: absolute;
- top: 1.7rem;
- white-space: nowrap;
- width: auto;
- z-index: 1;
-}
-
-nav ul li ul::before {
- /* fill gap above to make mousing over them easier */
- content: "";
- position: absolute;
- left: 0;
- right: 0;
- top: -0.5rem;
- height: 0.5rem;
-}
-
-nav ul li ul li,
-nav ul li ul li a {
- display: block;
-}
-
-/* Nav for Mobile */
-@media (max-width: 768px) {
- nav {
- flex-wrap: wrap;
- }
-
- nav ul li {
- width: calc(100% - 1em);
- }
-
- nav ul li ul {
- border: none;
- box-shadow: none;
- display: block;
- position: static;
- }
-}
-
-/* Typography */
-code,
-samp {
- background-color: var(--color-accent);
- border-radius: var(--border-radius);
- color: var(--color-text);
- display: inline-block;
- margin: 0 0.1rem;
- padding: 0 0.5rem;
-}
-
-details {
- margin: 1.3rem 0;
-}
-
-details summary {
- font-weight: bold;
- cursor: pointer;
-}
-
-h1,
-h2,
-h3,
-h4,
-h5,
-h6 {
- line-height: var(--line-height);
- text-wrap: balance;
-}
-
-mark {
- padding: 0.1rem;
-}
-
-ol li,
-ul li {
- padding: 0.2rem 0;
-}
-
-p {
- margin: 0.75rem 0;
- padding: 0;
- width: 100%;
-}
-
-pre {
- margin: 1rem 0;
- max-width: var(--width-card-wide);
- padding: 1rem 0;
-}
-
-pre code,
-pre samp {
- display: block;
- max-width: var(--width-card-wide);
- padding: 0.5rem 2rem;
- white-space: pre-wrap;
-}
-
-small {
- color: var(--color-text-secondary);
-}
-
-sup {
- background-color: var(--color-secondary);
- border-radius: var(--border-radius);
- color: var(--color-bg);
- font-size: xx-small;
- font-weight: bold;
- margin: 0.2rem;
- padding: 0.2rem 0.3rem;
- position: relative;
- top: -2px;
-}
-
-/* Links */
-a {
- color: var(--color-link);
- display: inline-block;
- font-weight: bold;
- text-decoration: underline;
-}
-
-a:hover {
- filter: brightness(var(--hover-brightness));
-}
-
-a:active {
- filter: brightness(var(--active-brightness));
-}
-
-a b,
-a em,
-a i,
-a strong,
-button,
-input[type="submit"] {
- border-radius: var(--border-radius);
- display: inline-block;
- font-size: medium;
- font-weight: bold;
- line-height: var(--line-height);
- margin: 0.5rem 0;
- padding: 1rem 2rem;
-}
-
-button,
-input[type="submit"] {
- font-family: var(--font-family);
-}
-
-button:hover,
-input[type="submit"]:hover {
- cursor: pointer;
- filter: brightness(var(--hover-brightness));
-}
-
-button:active,
-input[type="submit"]:active {
- filter: brightness(var(--active-brightness));
-}
-
-a b,
-a strong,
-button,
-input[type="submit"] {
- background-color: var(--color-link);
- border: 2px solid var(--color-link);
- color: var(--color-bg);
-}
-
-a em,
-a i {
- border: 2px solid var(--color-link);
- border-radius: var(--border-radius);
- color: var(--color-link);
- display: inline-block;
- padding: 1rem 2rem;
-}
-
-article aside a {
- color: var(--color-secondary);
-}
-
-/* Images */
-figure {
- margin: 0;
- padding: 0;
-}
-
-figure img {
- max-width: 100%;
-}
-
-figure figcaption {
- color: var(--color-text-secondary);
-}
-
-/* Forms */
-button:disabled,
-input:disabled {
- background: var(--color-bg-secondary);
- border-color: var(--color-bg-secondary);
- color: var(--color-text-secondary);
- cursor: not-allowed;
-}
-
-button[disabled]:hover,
-input[type="submit"][disabled]:hover {
- filter: none;
-}
-
-form {
- border: 1px solid var(--color-bg-secondary);
- border-radius: var(--border-radius);
- box-shadow: var(--box-shadow) var(--color-shadow);
- display: block;
- max-width: var(--width-card-wide);
- min-width: var(--width-card);
- padding: 1.5rem;
- text-align: var(--justify-normal);
-}
-
-form header {
- margin: 1.5rem 0;
- padding: 1.5rem 0;
-}
-
-input,
-label,
-select,
-textarea {
- display: block;
- font-size: inherit;
- max-width: var(--width-card-wide);
-}
-
-input[type="checkbox"],
-input[type="radio"] {
- display: inline-block;
-}
-
-input[type="checkbox"]+label,
-input[type="radio"]+label {
- display: inline-block;
- font-weight: normal;
- position: relative;
- top: 1px;
-}
-
-input[type="range"] {
- padding: 0.4rem 0;
-}
-
-input,
-select,
-textarea {
- border: 1px solid var(--color-bg-secondary);
- border-radius: var(--border-radius);
- margin-bottom: 1rem;
- padding: 0.4rem 0.8rem;
-}
-
-input[type="text"],
-input[type="password"],
-input[type="email"],
-textarea {
- width: calc(100% - 1.6rem);
-}
-
-input[readonly],
-textarea[readonly] {
- background-color: var(--color-bg-secondary);
-}
-
-label {
- font-weight: bold;
- margin-bottom: 0.2rem;
-}
-
-/* Popups */
-dialog {
- max-width: 90%;
- max-height: 85dvh;
- margin: auto;
- padding-block: 0;
- padding-inline: 20px;
- border: 1px solid var(--color-bg-secondary);
- border-radius: 0.5rem;
- overscroll-behavior: contain;
- scroll-behavior: smooth;
- scrollbar-width: none;
- /* Hide scrollbar for Firefox */
- -ms-overflow-style: none;
- /* Hide scrollbar for IE and Edge */
- scrollbar-color: transparent transparent;
- animation: bottom-to-top 0.25s ease-in-out forwards;
-}
-
-dialog::-webkit-scrollbar {
- width: 0;
- display: none;
-}
-
-dialog::-webkit-scrollbar-track {
- background: transparent;
-}
-
-dialog::-webkit-scrollbar-thumb {
- background-color: transparent;
-}
-
-@media (min-width: 650px) {
- dialog {
- max-width: 39rem;
- }
-}
-
-dialog::backdrop {
- background-color: rgba(0, 0, 0, 0.5);
-}
-
-@keyframes bottom-to-top {
- 0% {
- opacity: 0;
- transform: translateY(10%);
- }
-
- 100% {
- opacity: 1;
- transform: translateY(0);
- }
-}
-
-dialog hr {
- margin-block: 1rem;
-}
-
-/* Tables */
-table {
- border: 1px solid var(--color-bg-secondary);
- border-radius: var(--border-radius);
- border-spacing: 0;
- display: inline-block;
- max-width: 100%;
- overflow-x: auto;
- padding: 0;
- white-space: nowrap;
-}
-
-table td,
-table th,
-table tr {
- padding: 0.4rem 0.8rem;
- text-align: var(--justify-important);
-}
-
-table thead {
- background-color: var(--color-table);
- border-collapse: collapse;
- border-radius: var(--border-radius);
- color: var(--color-bg);
- margin: 0;
- padding: 0;
-}
-
-table thead tr:first-child th:first-child {
- border-top-left-radius: var(--border-radius);
-}
-
-table thead tr:first-child th:last-child {
- border-top-right-radius: var(--border-radius);
-}
-
-table thead th:first-child,
-table tr td:first-child {
- text-align: var(--justify-normal);
-}
-
-table tr:nth-child(even) {
- background-color: var(--color-accent);
-}
-
-/* Quotes */
-blockquote {
- display: block;
- font-size: x-large;
- line-height: var(--line-height);
- margin: 1rem auto;
- max-width: var(--width-card-medium);
- padding: 1.5rem 1rem;
- text-align: var(--justify-important);
-}
-
-blockquote footer {
- color: var(--color-text-secondary);
- display: block;
- font-size: small;
- line-height: var(--line-height);
- padding: 1.5rem 0;
-}
-
-/* Scrollbars */
-* {
- scrollbar-width: thin;
- scrollbar-color: var(--color-scrollbar) transparent;
-}
-
-*::-webkit-scrollbar {
- width: 5px;
- height: 5px;
-}
-
-*::-webkit-scrollbar-track {
- background: transparent;
-}
-
-*::-webkit-scrollbar-thumb {
- background-color: var(--color-scrollbar);
- border-radius: 10px;
-}
\ No newline at end of file
diff --git a/webapp/static/css/style.css b/webapp/static/css/style.css
index 25a3d21..37f6d21 100644
--- a/webapp/static/css/style.css
+++ b/webapp/static/css/style.css
@@ -1,19 +1,1401 @@
-header nav img {
- border-radius: 50%;
+/* === Variables & Thèmes === */
+:root {
+ /* Couleurs - Mode Clair */
+ --bg-primary: #fafafa;
+ --bg-secondary: #ffffff;
+ --bg-tertiary: #f0f0f0;
+ --text-primary: #1a1a2e;
+ --text-secondary: #4a4a5a;
+ --text-muted: #8a8a9a;
+ --accent: #2563eb;
+ --accent-hover: #1d4ed8;
+ --accent-soft: #2563eb15;
+ --border: #e2e2e8;
+ --shadow: rgba(0, 0, 0, 0.04);
+ --table-header: #1e293b;
+ --success: #059669;
+ --danger: #dc2626;
+
+ /* Espacements */
+ --space-xs: 0.25rem;
+ --space-sm: 0.5rem;
+ --space-md: 1rem;
+ --space-lg: 1.5rem;
+ --space-xl: 2rem;
+ --space-2xl: 3rem;
+
+ /* Typographie */
+ --font-sans: "DM Sans", -apple-system, BlinkMacSystemFont, sans-serif;
+ --font-mono: "JetBrains Mono", "Fira Code", monospace;
+ --text-sm: 0.875rem;
+ --text-base: 1rem;
+ --text-lg: 1.125rem;
+ --text-xl: 1.25rem;
+ --text-2xl: 1.5rem;
+ --line-height: 1.6;
+
+ /* Bordures */
+ --radius-sm: 6px;
+ --radius-md: 10px;
+ --radius-lg: 16px;
+ --radius-full: 9999px;
+
+ /* Largeurs */
+ --max-width: 1100px;
+}
+
+/* Mode Sombre automatique */
+@media (prefers-color-scheme: dark) {
+ :root {
+ --bg-primary: #0f0f14;
+ --bg-secondary: #18181f;
+ --bg-tertiary: #22222b;
+ --text-primary: #e8e8ed;
+ --text-secondary: #a8a8b8;
+ --text-muted: #68687a;
+ --accent: #3b82f6;
+ --accent-hover: #60a5fa;
+ --accent-soft: #3b82f620;
+ --border: #2a2a38;
+ --shadow: rgba(0, 0, 0, 0.3);
+ --table-header: #1e293b;
+ }
+}
+
+/* === Reset & Base === */
+*, *::before, *::after {
+ box-sizing: border-box;
+ margin: 0;
+ padding: 0;
+}
+
+html {
+ scroll-behavior: smooth;
+ font-size: 16px;
+}
+
+@media (prefers-reduced-motion: reduce) {
+ html { scroll-behavior: auto; }
+ *, *::before, *::after {
+ animation-duration: 0.01ms !important;
+ transition-duration: 0.01ms !important;
+ }
+}
+
+body {
+ font-family: var(--font-sans);
+ font-size: var(--text-base);
+ line-height: var(--line-height);
+ color: var(--text-primary);
+ background: var(--bg-primary);
+ min-height: 100vh;
+}
+
+/* === Layout === */
+header {
+ width: 100%;
+ padding: var(--space-sm) var(--space-lg);
+}
+
+main, footer {
+ width: 100%;
+ max-width: var(--max-width);
+ margin: 0 auto;
+ padding: var(--space-md) var(--space-lg);
+}
+
+main {
+ padding-top: var(--space-xl);
+ padding-bottom: var(--space-2xl);
+}
+
+/* === Navigation === */
+header {
+ background: var(--bg-secondary);
+ border-bottom: 1px solid var(--border);
+ position: sticky;
+ top: 0;
+ z-index: 100;
+}
+
+header nav {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: var(--space-lg);
+ padding: var(--space-sm) 0;
+ max-width: var(--max-width);
+ margin: 0 auto;
+}
+
+/* Logo */
+.nav-logo {
+ display: flex;
+ align-items: center;
+ gap: var(--space-sm);
+ text-decoration: none;
+ flex-shrink: 0;
+}
+
+.nav-logo img {
+ width: 48px;
+ height: 48px;
+ border-radius: var(--radius-full);
+ transition: transform 0.2s ease;
+}
+
+.nav-logo:hover img {
+ transform: scale(1.05);
+}
+
+.nav-logo span {
+ font-weight: 700;
+ font-size: var(--text-lg);
+ color: var(--text-primary);
+ letter-spacing: -0.02em;
+}
+
+/* Menu Toggle (hamburger) - TOUJOURS caché, sauf mobile */
+.nav-toggle {
+ position: absolute;
+ opacity: 0;
+ pointer-events: none;
+}
+
+.nav-toggle-label {
+ display: none;
+ visibility: hidden;
+}
+
+.nav-toggle-label span {
+ display: block;
+ width: 100%;
+ height: 2px;
+ background: var(--text-primary);
+ border-radius: 2px;
+ transition: all 0.3s ease;
+}
+
+/* Menu principal */
+.nav-menu {
+ display: flex;
+ align-items: center;
+ gap: var(--space-xs);
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
+
+.nav-menu > li {
+ position: relative;
+}
+
+.nav-menu > li > a {
+ display: flex;
+ align-items: center;
+ gap: var(--space-xs);
+ padding: var(--space-sm) var(--space-md);
+ color: var(--text-secondary);
+ text-decoration: none;
+ font-weight: 500;
+ font-size: var(--text-sm);
+ border-radius: var(--radius-sm);
+ transition: all 0.15s ease;
+ white-space: nowrap;
+}
+
+.nav-menu > li > a:hover {
+ color: var(--accent);
+ background: var(--accent-soft);
+}
+
+/* Indicateur de sous-menu */
+.nav-menu > li.has-submenu > a::after {
+ content: "";
+ width: 0;
+ height: 0;
+ border-left: 4px solid transparent;
+ border-right: 4px solid transparent;
+ border-top: 5px solid currentColor;
+ margin-left: var(--space-xs);
+ transition: transform 0.2s ease;
+}
+
+.nav-menu > li.has-submenu:hover > a::after {
+ transform: rotate(180deg);
+}
+
+/* Sous-menu */
+.submenu {
+ position: absolute;
+ top: 100%;
+ left: 0;
+ min-width: 200px;
+ background: var(--bg-secondary);
+ border: 1px solid var(--border);
+ border-radius: var(--radius-md);
+ padding: var(--space-sm);
+ list-style: none;
+ opacity: 0;
+ visibility: hidden;
+ transform: translateY(-8px);
+ transition: all 0.2s ease;
+ box-shadow: 0 10px 40px -10px var(--shadow);
+ z-index: 200;
+}
+
+.nav-menu > li.has-submenu:hover .submenu {
+ opacity: 1;
+ visibility: visible;
+ transform: translateY(0);
+}
+
+.submenu li a {
+ display: flex;
+ align-items: center;
+ gap: var(--space-sm);
+ padding: var(--space-sm) var(--space-md);
+ color: var(--text-secondary);
+ text-decoration: none;
+ font-size: var(--text-sm);
+ border-radius: var(--radius-sm);
+ transition: all 0.15s ease;
+}
+
+.submenu li a:hover {
+ color: var(--accent);
+ background: var(--accent-soft);
+}
+
+/* Nav Mobile */
+@media (max-width: 768px) {
+ .nav-toggle-label {
+ display: flex;
+ visibility: visible;
+ flex-direction: column;
+ justify-content: center;
+ gap: 5px;
+ width: 32px;
+ height: 32px;
+ cursor: pointer;
+ padding: 4px;
+ }
+
+ .nav-logo span {
+ display: none;
+ }
+
+ .nav-menu {
+ position: absolute;
+ top: 100%;
+ left: 0;
+ right: 0;
+ flex-direction: column;
+ align-items: stretch;
+ background: var(--bg-secondary);
+ border-bottom: 1px solid var(--border);
+ padding: var(--space-md);
+ gap: var(--space-xs);
+ opacity: 0;
+ visibility: hidden;
+ transform: translateY(-10px);
+ transition: all 0.2s ease;
+ }
+
+ .nav-toggle:checked ~ .nav-menu {
+ opacity: 1;
+ visibility: visible;
+ transform: translateY(0);
+ }
+
+ /* Animation hamburger -> X */
+ .nav-toggle:checked ~ .nav-toggle-label span:nth-child(1) {
+ transform: rotate(45deg) translate(5px, 5px);
+ }
+
+ .nav-toggle:checked ~ .nav-toggle-label span:nth-child(2) {
+ opacity: 0;
+ }
+
+ .nav-toggle:checked ~ .nav-toggle-label span:nth-child(3) {
+ transform: rotate(-45deg) translate(5px, -5px);
+ }
+
+ .nav-menu > li > a {
+ justify-content: space-between;
+ }
+
+ /* Sous-menu mobile */
+ .submenu {
+ position: static;
+ opacity: 1;
+ visibility: visible;
+ transform: none;
+ box-shadow: none;
+ border: none;
+ background: var(--bg-tertiary);
+ margin-top: var(--space-xs);
+ padding: var(--space-xs);
+ display: none;
+ }
+
+ .nav-menu > li.has-submenu:hover .submenu,
+ .nav-menu > li.has-submenu:focus-within .submenu {
+ display: block;
+ }
+}
+
+/* === Typographie === */
+h1, h2, h3, h4, h5, h6 {
+ font-weight: 600;
+ line-height: 1.3;
+ color: var(--text-primary);
+}
+
+h1 { font-size: var(--text-2xl); margin-bottom: var(--space-md); }
+h2 { font-size: var(--text-xl); margin-bottom: var(--space-md); }
+h3 { font-size: var(--text-lg); margin-bottom: var(--space-sm); }
+
+p {
+ color: var(--text-secondary);
+ margin-bottom: var(--space-md);
+}
+
+small {
+ font-size: var(--text-sm);
+ color: var(--text-muted);
+}
+
+/* === Liens === */
+a {
+ color: var(--accent);
+ text-decoration: none;
+ transition: color 0.15s ease;
+}
+
+a:hover {
+ color: var(--accent-hover);
+}
+
+/* === Boutons === */
+button,
+input[type="submit"],
+.btn {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: var(--space-sm);
+ padding: var(--space-sm) var(--space-lg);
+ font-family: var(--font-sans);
+ font-size: var(--text-sm);
+ font-weight: 500;
+ color: #fff;
+ background: var(--accent);
+ border: none;
+ border-radius: var(--radius-sm);
+ cursor: pointer;
+ transition: all 0.15s ease;
+}
+
+button:hover,
+input[type="submit"]:hover,
+.btn:hover {
+ background: var(--accent-hover);
+ transform: translateY(-1px);
+}
+
+button:active,
+input[type="submit"]:active {
+ transform: translateY(0);
+}
+
+button:disabled,
+input[type="submit"]:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+ transform: none;
+}
+
+.btn-secondary {
+ color: var(--text-primary);
+ background: var(--bg-tertiary);
+ border: 1px solid var(--border);
+}
+
+.btn-secondary:hover {
+ background: var(--border);
+}
+
+.btn-danger {
+ background: var(--danger);
+}
+
+.btn-danger:hover {
+ background: #b91c1c;
+}
+
+/* === Formulaires === */
+form {
+ background: var(--bg-secondary);
+ border: 1px solid var(--border);
+ border-radius: var(--radius-md);
+ padding: var(--space-lg);
+ max-width: 600px;
+}
+
+label {
+ display: block;
+ font-size: var(--text-sm);
+ font-weight: 500;
+ color: var(--text-primary);
+ margin-bottom: var(--space-xs);
+}
+
+input[type="text"],
+input[type="password"],
+input[type="email"],
+input[type="url"],
+input[type="number"],
+select,
+textarea {
+ width: 100%;
+ padding: var(--space-sm) var(--space-md);
+ font-family: var(--font-sans);
+ font-size: var(--text-base);
+ color: var(--text-primary);
+ background: var(--bg-tertiary);
+ border: 1px solid var(--border);
+ border-radius: var(--radius-sm);
+ margin-bottom: var(--space-md);
+ transition: border-color 0.15s ease, box-shadow 0.15s ease;
+}
+
+input:focus,
+select:focus,
+textarea:focus {
+ outline: none;
+ border-color: var(--accent);
+ box-shadow: 0 0 0 3px var(--accent-soft);
+}
+
+input[type="checkbox"],
+input[type="radio"] {
+ width: var(--space-md);
+ height: var(--space-md);
+ accent-color: var(--accent);
+ margin-right: var(--space-sm);
+}
+
+input[type="checkbox"] + label,
+input[type="radio"] + label {
+ display: inline;
+ font-weight: normal;
+ cursor: pointer;
+}
+
+textarea {
+ min-height: 120px;
+ resize: vertical;
+}
+
+input[readonly],
+textarea[readonly] {
+ background: var(--bg-primary);
+ color: var(--text-muted);
+}
+
+/* === Tableaux === */
+table {
+ width: 100%;
+ border-collapse: collapse;
+ background: var(--bg-secondary);
+ border-radius: var(--radius-md);
+ overflow: hidden;
+ border: 1px solid var(--border);
+}
+
+table thead {
+ background: var(--table-header);
+}
+
+table th {
+ padding: var(--space-md);
+ font-size: var(--text-sm);
+ font-weight: 600;
+ color: #fff;
+ text-align: left;
}
-table th,
table td {
+ padding: var(--space-md);
+ font-size: var(--text-sm);
+ color: var(--text-secondary);
+ border-bottom: 1px solid var(--border);
text-align: left;
vertical-align: top;
- overflow: hidden;
- white-space: normal;
+}
+
+table tbody tr:last-child td {
+ border-bottom: none;
+}
+
+table tbody tr:hover {
+ background: var(--accent-soft);
}
table.live-alert tr td:last-child {
white-space: nowrap;
}
+/* === Code === */
+code, pre {
+ font-family: var(--font-mono);
+ font-size: var(--text-sm);
+}
+
+code {
+ padding: var(--space-xs) var(--space-sm);
+ background: var(--accent-soft);
+ color: var(--accent);
+ border-radius: var(--radius-sm);
+}
+
+pre {
+ background: var(--bg-tertiary);
+ border: 1px solid var(--border);
+ border-radius: var(--radius-md);
+ padding: var(--space-md);
+ overflow-x: auto;
+}
+
+pre code {
+ background: none;
+ padding: 0;
+ color: var(--text-primary);
+}
+
+/* === Cards & Sections === */
+.card,
+section aside {
+ background: var(--bg-secondary);
+ border: 1px solid var(--border);
+ border-radius: var(--radius-md);
+ padding: var(--space-lg);
+}
+
+article aside {
+ background: var(--accent-soft);
+ border-left: 3px solid var(--accent);
+ padding: var(--space-md);
+ border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
+ margin: var(--space-md) 0;
+}
+
+/* Info Box */
+.info-box {
+ background: var(--accent-soft);
+ border: 1px solid var(--accent);
+ border-radius: var(--radius-md);
+ padding: var(--space-lg);
+ margin-bottom: var(--space-lg);
+}
+
+.info-box h3 {
+ color: var(--accent);
+ margin-bottom: var(--space-sm);
+}
+
+.info-box p {
+ margin-bottom: var(--space-sm);
+}
+
+.info-box p:last-child {
+ margin-bottom: 0;
+}
+
+.info-box ul {
+ margin: var(--space-sm) 0;
+ padding-left: var(--space-lg);
+}
+
+.info-box li {
+ margin-bottom: var(--space-xs);
+ color: var(--text-secondary);
+}
+
+.info-box em {
+ color: var(--text-muted);
+ font-style: italic;
+}
+
+/* === HR === */
+hr {
+ border: none;
+ height: 1px;
+ background: var(--border);
+ margin: var(--space-xl) 0;
+}
+
+/* === Footer === */
+footer {
+ padding-top: 0;
+}
+
+footer p {
+ font-size: var(--text-sm);
+ color: var(--text-muted);
+ text-align: center;
+}
+
+footer a {
+ font-weight: 500;
+}
+
+/* === Icônes === */
a.icon {
text-decoration: none;
-}
\ No newline at end of file
+ display: inline-flex;
+ align-items: center;
+ gap: var(--space-xs);
+}
+
+/* === Dialog/Modal === */
+dialog {
+ max-width: min(90vw, 500px);
+ background: var(--bg-secondary);
+ border: 1px solid var(--border);
+ border-radius: var(--radius-lg);
+ padding: var(--space-lg);
+ box-shadow: 0 25px 50px -12px var(--shadow);
+}
+
+dialog::backdrop {
+ background: rgba(0, 0, 0, 0.5);
+ backdrop-filter: blur(4px);
+}
+
+/* === Scrollbar === */
+::-webkit-scrollbar {
+ width: 8px;
+ height: 8px;
+}
+
+::-webkit-scrollbar-track {
+ background: transparent;
+}
+
+::-webkit-scrollbar-thumb {
+ background: var(--border);
+ border-radius: var(--radius-full);
+}
+
+::-webkit-scrollbar-thumb:hover {
+ background: var(--text-muted);
+}
+
+* {
+ scrollbar-width: thin;
+ scrollbar-color: var(--border) transparent;
+}
+
+/* === Utilitaires === */
+.text-center { text-align: center; }
+.text-muted { color: var(--text-muted); }
+.mt-md { margin-top: var(--space-md); }
+.mb-md { margin-bottom: var(--space-md); }
+.flex { display: flex; }
+.gap-sm { gap: var(--space-sm); }
+.gap-md { gap: var(--space-md); }
+
+/* === Dashboard === */
+.dashboard-section {
+ margin-bottom: var(--space-2xl);
+}
+
+.dashboard-section h2 {
+ margin-bottom: var(--space-lg);
+ font-size: var(--text-lg);
+ color: var(--text-primary);
+}
+
+.stats-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+ gap: var(--space-md);
+}
+
+.stat-card {
+ display: flex;
+ align-items: center;
+ gap: var(--space-md);
+ background: var(--bg-secondary);
+ border: 1px solid var(--border);
+ border-radius: var(--radius-md);
+ padding: var(--space-lg);
+ position: relative;
+ transition: all 0.2s ease;
+}
+
+.stat-card:hover {
+ border-color: var(--accent);
+ transform: translateY(-2px);
+}
+
+.stat-card.online {
+ border-left: 3px solid var(--success);
+}
+
+.stat-card.offline {
+ border-left: 3px solid var(--danger);
+}
+
+.stat-icon {
+ font-size: 1.75rem;
+ line-height: 1;
+}
+
+.stat-content {
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-xs);
+ flex: 1;
+}
+
+.stat-label {
+ font-size: var(--text-sm);
+ color: var(--text-muted);
+ font-weight: 500;
+}
+
+.stat-value {
+ font-size: var(--text-base);
+ font-weight: 600;
+ color: var(--text-primary);
+}
+
+.stat-value.big {
+ font-size: var(--text-2xl);
+}
+
+.stat-sub {
+ font-size: var(--text-sm);
+ color: var(--text-secondary);
+}
+
+.stat-status {
+ width: 10px;
+ height: 10px;
+ border-radius: var(--radius-full);
+ flex-shrink: 0;
+}
+
+.stat-status.online {
+ background: var(--success);
+ box-shadow: 0 0 8px var(--success);
+}
+
+.stat-status.offline {
+ background: var(--danger);
+}
+
+/* Cogs Grid */
+.cogs-grid {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--space-sm);
+}
+
+.cog-item {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--space-sm);
+ padding: var(--space-sm) var(--space-md);
+ background: var(--bg-secondary);
+ border: 1px solid var(--border);
+ border-radius: var(--radius-full);
+ font-size: var(--text-sm);
+ transition: all 0.15s ease;
+}
+
+.cog-item.enabled {
+ border-color: var(--success);
+ background: rgba(5, 150, 105, 0.1);
+}
+
+.cog-item.disabled {
+ opacity: 0.6;
+}
+
+.cog-status {
+ font-size: var(--text-sm);
+}
+
+.cog-name {
+ color: var(--text-primary);
+ font-weight: 500;
+}
+
+.last-update {
+ font-size: var(--text-sm);
+ color: var(--text-muted);
+ margin-top: var(--space-xl);
+ text-align: right;
+}
+
+/* Announce Form Inline */
+.announce-form-inline {
+ max-width: 100%;
+}
+
+.announce-row {
+ display: flex;
+ gap: var(--space-sm);
+ align-items: stretch;
+}
+
+.announce-row input[type="text"] {
+ flex: 1;
+ min-width: 200px;
+ padding: var(--space-sm) var(--space-md);
+ font-family: var(--font-sans);
+ font-size: var(--text-base);
+ color: var(--text-primary);
+ background: var(--bg-tertiary);
+ border: 1px solid var(--border);
+ border-radius: var(--radius-sm);
+ margin-bottom: 0;
+}
+
+.announce-row input[type="text"]:focus {
+ outline: none;
+ border-color: var(--accent);
+ box-shadow: 0 0 0 3px var(--accent-soft);
+}
+
+.announce-row select {
+ width: auto;
+ min-width: 180px;
+ padding: var(--space-sm) var(--space-md);
+ font-family: var(--font-sans);
+ font-size: var(--text-sm);
+ color: var(--text-primary);
+ background: var(--bg-tertiary);
+ border: 1px solid var(--border);
+ border-radius: var(--radius-sm);
+ cursor: pointer;
+}
+
+.announce-row select:focus {
+ outline: none;
+ border-color: var(--accent);
+ box-shadow: 0 0 0 3px var(--accent-soft);
+}
+
+.announce-row button {
+ flex-shrink: 0;
+ white-space: nowrap;
+}
+
+.announce-result {
+ margin-top: var(--space-md);
+ padding: var(--space-sm) var(--space-md);
+ border-radius: var(--radius-sm);
+ font-size: var(--text-sm);
+ font-weight: 500;
+}
+
+.announce-result.success {
+ background: rgba(5, 150, 105, 0.1);
+ border: 1px solid var(--success);
+ color: var(--success);
+}
+
+.announce-result.error {
+ background: rgba(220, 38, 38, 0.1);
+ border: 1px solid var(--danger);
+ color: var(--danger);
+}
+
+@media (max-width: 640px) {
+ .announce-row {
+ flex-direction: column;
+ }
+
+ .announce-row input[type="text"],
+ .announce-row select {
+ width: 100%;
+ min-width: unset;
+ }
+}
+
+/* === Moderation Insights === */
+.moderation-insights {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
+ gap: var(--space-lg);
+ margin-bottom: var(--space-2xl);
+}
+
+.insight-card {
+ background: var(--bg-secondary);
+ border: 1px solid var(--border);
+ border-radius: var(--radius-md);
+ padding: var(--space-lg);
+}
+
+.insight-card h3 {
+ font-size: var(--text-base);
+ margin-bottom: var(--space-md);
+ color: var(--text-primary);
+}
+
+/* Leaderboard */
+.leaderboard {
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-sm);
+}
+
+.leaderboard-item {
+ display: flex;
+ align-items: center;
+ gap: var(--space-md);
+ padding: var(--space-sm) var(--space-md);
+ background: var(--bg-tertiary);
+ border-radius: var(--radius-sm);
+ transition: all 0.15s ease;
+}
+
+.leaderboard-item:hover {
+ background: var(--accent-soft);
+}
+
+.leaderboard-item.warning:hover {
+ background: rgba(220, 38, 38, 0.1);
+}
+
+.leaderboard-item .rank {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 24px;
+ height: 24px;
+ background: var(--accent);
+ color: #fff;
+ border-radius: var(--radius-full);
+ font-size: var(--text-sm);
+ font-weight: 600;
+ flex-shrink: 0;
+}
+
+.leaderboard-item:first-child .rank {
+ background: linear-gradient(135deg, #fbbf24, #f59e0b);
+}
+
+.leaderboard-item:nth-child(2) .rank {
+ background: linear-gradient(135deg, #94a3b8, #64748b);
+}
+
+.leaderboard-item:nth-child(3) .rank {
+ background: linear-gradient(135deg, #d97706, #b45309);
+}
+
+.leaderboard-item.warning .rank {
+ background: var(--danger);
+}
+
+.leaderboard-item .name {
+ flex: 1;
+ font-weight: 500;
+ color: var(--text-primary);
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.leaderboard-item .count {
+ font-size: var(--text-sm);
+ color: var(--text-muted);
+ flex-shrink: 0;
+}
+
+/* Type Badges */
+.type-badges {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--space-sm);
+}
+
+.type-badge {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--space-sm);
+ padding: var(--space-sm) var(--space-md);
+ background: var(--bg-tertiary);
+ border-radius: var(--radius-full);
+ border: 1px solid var(--border);
+}
+
+.type-badge .type-name {
+ font-size: var(--text-sm);
+ font-weight: 500;
+ color: var(--text-primary);
+}
+
+.type-badge .type-count {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 24px;
+ height: 24px;
+ padding: 0 var(--space-sm);
+ background: var(--accent);
+ color: #fff;
+ border-radius: var(--radius-full);
+ font-size: var(--text-sm);
+ font-weight: 600;
+}
+
+.type-badge.warn .type-count,
+.type-badge.warning .type-count,
+.type-badge.avertissement .type-count {
+ background: #f59e0b;
+}
+
+.type-badge.kick .type-count,
+.type-badge.expulsion .type-count {
+ background: #f97316;
+}
+
+.type-badge.ban .type-count,
+.type-badge.bannissement .type-count {
+ background: #dc2626;
+}
+
+.type-badge.unban .type-count {
+ background: #059669;
+}
+
+/* Commands Section (Collapsible) */
+.commands-section {
+ background: var(--bg-secondary);
+ border: 1px solid var(--border);
+ border-radius: var(--radius-md);
+ margin-bottom: var(--space-2xl);
+ overflow: hidden;
+}
+
+.commands-section summary {
+ display: flex;
+ align-items: center;
+ gap: var(--space-sm);
+ padding: var(--space-lg);
+ font-weight: 600;
+ color: var(--text-primary);
+ cursor: pointer;
+ user-select: none;
+ transition: background 0.15s ease;
+}
+
+.commands-section summary:hover {
+ background: var(--accent-soft);
+}
+
+.commands-section summary::-webkit-details-marker {
+ display: none;
+}
+
+.commands-section summary::after {
+ content: "";
+ margin-left: auto;
+ width: 0;
+ height: 0;
+ border-left: 5px solid transparent;
+ border-right: 5px solid transparent;
+ border-top: 6px solid var(--text-muted);
+ transition: transform 0.2s ease;
+}
+
+.commands-section[open] summary::after {
+ transform: rotate(180deg);
+}
+
+.summary-icon {
+ font-size: var(--text-lg);
+}
+
+.commands-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
+ gap: var(--space-md);
+ padding: 0 var(--space-lg) var(--space-lg);
+}
+
+.command-card {
+ background: var(--bg-tertiary);
+ border-radius: var(--radius-sm);
+ padding: var(--space-md);
+ transition: all 0.15s ease;
+}
+
+.command-card:hover {
+ background: var(--accent-soft);
+ transform: translateY(-2px);
+}
+
+.command-card .command-header {
+ margin-bottom: var(--space-sm);
+}
+
+.command-card .command-header code {
+ font-size: var(--text-sm);
+ font-weight: 600;
+}
+
+.command-card p {
+ font-size: var(--text-sm);
+ color: var(--text-secondary);
+ margin-bottom: var(--space-xs);
+}
+
+.command-card small {
+ font-size: 0.75rem;
+ color: var(--text-muted);
+}
+
+/* Event Type Badge in Table */
+.event-type {
+ display: inline-block;
+ padding: var(--space-xs) var(--space-sm);
+ border-radius: var(--radius-sm);
+ font-size: var(--text-sm);
+ font-weight: 500;
+ background: var(--bg-tertiary);
+ color: var(--text-primary);
+}
+
+.event-type.warn,
+.event-type.warning,
+.event-type.avertissement {
+ background: rgba(245, 158, 11, 0.15);
+ color: #f59e0b;
+}
+
+.event-type.kick,
+.event-type.expulsion {
+ background: rgba(249, 115, 22, 0.15);
+ color: #f97316;
+}
+
+.event-type.ban,
+.event-type.bannissement {
+ background: rgba(220, 38, 38, 0.15);
+ color: #dc2626;
+}
+
+.event-type.unban {
+ background: rgba(5, 150, 105, 0.15);
+ color: #059669;
+}
+
+/* Empty State */
+.empty-state {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: var(--space-2xl);
+ background: var(--bg-secondary);
+ border: 1px dashed var(--border);
+ border-radius: var(--radius-md);
+ text-align: center;
+}
+
+.empty-state .empty-icon {
+ font-size: 3rem;
+ margin-bottom: var(--space-md);
+}
+
+.empty-state p {
+ font-size: var(--text-lg);
+ font-weight: 500;
+ color: var(--text-primary);
+ margin-bottom: var(--space-xs);
+}
+
+.empty-state small {
+ color: var(--text-muted);
+}
+
+/* === Moderation Navigation === */
+.moderation-nav {
+ display: flex;
+ gap: var(--space-sm);
+ margin-bottom: var(--space-lg);
+ padding: var(--space-sm);
+ background: var(--bg-secondary);
+ border: 1px solid var(--border);
+ border-radius: var(--radius-md);
+}
+
+.moderation-nav .nav-btn {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--space-sm);
+ padding: var(--space-sm) var(--space-lg);
+ font-size: var(--text-sm);
+ font-weight: 500;
+ color: var(--text-secondary);
+ background: transparent;
+ border: 1px solid transparent;
+ border-radius: var(--radius-sm);
+ text-decoration: none;
+ transition: all 0.15s ease;
+}
+
+.moderation-nav .nav-btn:hover {
+ color: var(--accent);
+ background: var(--accent-soft);
+}
+
+.moderation-nav .nav-btn.active {
+ color: var(--accent);
+ background: var(--accent-soft);
+ border-color: var(--accent);
+}
+
+/* === Invitations Section === */
+.invites-actions {
+ display: flex;
+ align-items: center;
+ gap: var(--space-lg);
+ margin-bottom: var(--space-lg);
+ flex-wrap: wrap;
+}
+
+.checkbox-label {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--space-sm);
+ cursor: pointer;
+ font-size: var(--text-sm);
+ color: var(--text-secondary);
+}
+
+.checkbox-label input[type="checkbox"] {
+ margin: 0;
+}
+
+/* Invitation Badges */
+.badge {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ padding: var(--space-xs) var(--space-sm);
+ font-size: 0.75rem;
+ font-weight: 600;
+ border-radius: var(--radius-sm);
+ text-transform: uppercase;
+ letter-spacing: 0.02em;
+}
+
+.badge.active {
+ background: rgba(5, 150, 105, 0.15);
+ color: #059669;
+}
+
+.badge.permanent {
+ background: rgba(59, 130, 246, 0.15);
+ color: #3b82f6;
+}
+
+.badge.revoked {
+ background: rgba(220, 38, 38, 0.15);
+ color: #dc2626;
+}
+
+.badge.expired {
+ background: rgba(107, 114, 128, 0.15);
+ color: #6b7280;
+}
+
+.badge.used {
+ background: rgba(245, 158, 11, 0.15);
+ color: #f59e0b;
+}
+
+/* Invites Table Styles */
+.invites-table tr.revoked td {
+ opacity: 0.6;
+}
+
+.invites-table tr.expired td {
+ opacity: 0.8;
+}
+
+/* Flash Messages */
+.flash-messages {
+ margin-bottom: var(--space-lg);
+}
+
+.flash-message {
+ display: flex;
+ align-items: center;
+ gap: var(--space-sm);
+ padding: var(--space-md);
+ border-radius: var(--radius-sm);
+ margin-bottom: var(--space-sm);
+ font-size: var(--text-sm);
+ font-weight: 500;
+}
+
+.flash-message.success {
+ background: rgba(5, 150, 105, 0.15);
+ border: 1px solid var(--success);
+ color: var(--success);
+}
+
+.flash-message.error {
+ background: rgba(220, 38, 38, 0.15);
+ border: 1px solid var(--danger);
+ color: var(--danger);
+}
+
+/* === Responsive === */
+@media (max-width: 640px) {
+ :root {
+ --text-2xl: 1.375rem;
+ --text-xl: 1.125rem;
+ }
+
+ header, main, footer {
+ padding-left: var(--space-md);
+ padding-right: var(--space-md);
+ }
+
+ table {
+ display: block;
+ overflow-x: auto;
+ }
+
+ .stats-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .moderation-insights {
+ grid-template-columns: 1fr;
+ }
+
+ .commands-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .moderation-nav {
+ flex-direction: column;
+ }
+
+ .invites-actions {
+ flex-direction: column;
+ align-items: flex-start;
+ }
+}
diff --git a/webapp/templates/configurations.html b/webapp/templates/configurations.html
index 40cf0bf..cbc3d78 100644
--- a/webapp/templates/configurations.html
+++ b/webapp/templates/configurations.html
@@ -5,6 +5,17 @@
Configurez les tokens Discord, les notifications Humble Bundle et l'API Twitch.
Discord
+
+{% with messages = get_flashed_messages(with_categories=true) %}
+ {% if messages %}
+ {% for category, message in messages %}
+
+ {{ message }}
+
+ {% endfor %}
+ {% endif %}
+{% endwith %}
+
+
+⚠️ Gestion des serveurs
+
+
+
{% endblock %}
\ No newline at end of file
diff --git a/webapp/templates/freegames.html b/webapp/templates/freegames.html
new file mode 100644
index 0000000..1daccd7
--- /dev/null
+++ b/webapp/templates/freegames.html
@@ -0,0 +1,381 @@
+{% extends "template.html" %}
+
+{% block content %}
+🎮 Jeux Gratuits
+Gérez les notifications de jeux gratuits via le flux RSS LootScraper. Activez le module, choisissez les sources à suivre et décidez quels jeux partager avec votre communauté.
+
+Configuration
+
+
+Jeux disponibles {{pending_count}} en attente
+
+
+ 🔄 Rafraîchir le flux RSS
+
+
+
+ Filtrer par source :
+
+ Toutes les sources
+ {% for key, name in sources.items() %}
+ {{name}}
+ {% endfor %}
+
+
+ Filtrer par statut :
+
+ Tous
+ En attente
+ Notifiés
+
+
+
+
+ {% for game in games %}
+
+
+
+ {% if game.description %}
+
{{game.description[:150]}}{% if game.description|length > 150 %}...{% endif %}
+ {% endif %}
+
+
+
+
+ {% if not game.notified %}
+ 📢 Notifier
+ ✓ Marquer comme notifié
+ {% else %}
+ ↩️ Réinitialiser
+ {% endif %}
+ 🗑️ Supprimer
+
+
+ {% else %}
+
Aucun jeu gratuit trouvé. Cliquez sur "Rafraîchir le flux RSS" pour récupérer les dernières offres.
+ {% endfor %}
+
+
+
+
+
+{% endblock %}
+
diff --git a/webapp/templates/humeurs.html b/webapp/templates/humeurs.html
index a060ccf..80d5761 100644
--- a/webapp/templates/humeurs.html
+++ b/webapp/templates/humeurs.html
@@ -3,6 +3,18 @@
{% block content %}
Humeurs de Mamie
Définissez les statuts Discord qui changeront automatiquement toutes les 10 minutes pour donner de la personnalité à votre bot.
+
+
+
📝 Variables disponibles
+
Vous pouvez utiliser ces variables dans vos humeurs, elles seront remplacées automatiquement :
+
+ {servers} — Nombre de serveurs gérés par le bot
+ {members} — Nombre total de membres sur tous les serveurs
+ {channels} — Nombre total de salons sur tous les serveurs
+
+
Exemple : "Je surveille {members} personnes sur {servers} serveurs 👀"
+
+
diff --git a/webapp/templates/index.html b/webapp/templates/index.html
index eb248da..760bdef 100644
--- a/webapp/templates/index.html
+++ b/webapp/templates/index.html
@@ -1,7 +1,161 @@
{% extends "template.html" %}
{% block content %}
-Bienvenue sur l'interface d'administration de Mamie.
-Nous devons définir ce que nous souhaitons afficher sur la page d'accueil. Peut-être l'historique des dernières
- modifications ? de la modération ?
-{% endblock %}
\ No newline at end of file
+Tableau de bord
+Bienvenue sur l'interface d'administration de Mamie Henriette.
+
+
+
+ 🔌 Services
+
+
+
🤖
+
+ Discord
+ {{ 'Connecté' if stats.discord_connected else 'Déconnecté' }}
+ {% if stats.discord_connected and stats.discord_bot_name %}
+ {{ stats.discord_bot_name }}
+ {% endif %}
+
+
+
+
+
+
📺
+
+ Twitch
+ {{ 'Connecté' if stats.twitch_connected else 'Déconnecté' }}
+ {% if stats.twitch_connected and stats.twitch_channel %}
+ #{{ stats.twitch_channel }}
+ {% endif %}
+
+
+
+
+
+
+
+{% if stats.discord_connected %}
+
+ 📊 Statistiques Discord
+
+
+
🏠
+
+ Serveurs
+ {{ stats.discord_guilds }}
+
+
+
+
+
👥
+
+ Membres
+ {{ stats.discord_members }}
+
+
+
+
+
💬
+
+ Salons
+ {{ stats.discord_channels }}
+
+
+
+
+
+
+
+ 📢 Envoyer une annonce
+
+
+{% endif %}
+
+
+
+ ⚡ Fonctionnalités
+
+ {% for cog_name, enabled in stats.cogs_enabled.items() %}
+
+ {{ '✅' if enabled else '❌' }}
+ {{ cog_name }}
+
+ {% else %}
+
Aucune information disponible. Le bot Discord n'est peut-être pas encore connecté.
+ {% endfor %}
+
+
+
+{% if stats.last_update %}
+Dernière mise à jour : {{ stats.last_update.strftime('%d/%m/%Y à %H:%M:%S') }}
+{% endif %}
+
+
+{% endblock %}
diff --git a/webapp/templates/moderation.html b/webapp/templates/moderation.html
index fc1e316..7b5c65c 100644
--- a/webapp/templates/moderation.html
+++ b/webapp/templates/moderation.html
@@ -3,61 +3,175 @@
{% block content %}
Modération Discord
-
- Historique des actions de modération effectuées sur le serveur Discord.
+
Historique des actions de modération effectuées sur le serveur Discord. Le bot enregistre automatiquement les avertissements, exclusions et bannissements.
- Le bot enregistre automatiquement les avertissements, exclusions et bannissements.
+
+
-
-
-
- Commande
- Description
-
-
-
-
- !averto @utilisateur raison Alias : !warn, !av, !avertissement
- Avertit un utilisateur et enregistre l'avertissement dans la base de données
-
-
- !delaverto id Alias : !removewarn, !delwarn
- Retire un avertissement en utilisant son numéro d'ID
-
-
- !warnings ou !warnings @utilisateur Alias : !listevent, !listwarn
- Affiche la liste des événements de modération (tous ou pour un utilisateur spécifique)
-
-
- !inspect @utilisateur ou !inspect id
- Affiche des informations détaillées sur un utilisateur : création du compte, date d'arrivée, historique de modération
-
-
- !kick @utilisateur raison
- Expulse un utilisateur du serveur
-
-
- !ban @utilisateur raison
- Bannit définitivement un utilisateur du serveur
-
-
- !unban discord_id ou !unban #sanction_id raison
- Révoque le bannissement d'un utilisateur et lui envoie une invitation
-
-
- !banlist
- Affiche la liste des utilisateurs actuellement bannis du serveur
-
-
- !aide Alias : !help
- Affiche l'aide avec toutes les commandes disponibles
-
-
-
-
+
+
+
📊 Statistiques
+
+
+
📋
+
+ Total des sanctions
+ {{ stats.total }}
+
+
+
+
⏰
+
+ Dernières 24h
+ {{ stats.recent_24h }}
+
+
+
+
📅
+
+ 7 derniers jours
+ {{ stats.recent_7d }}
+
+
+
+
📆
+
+ 30 derniers jours
+ {{ stats.recent_30d }}
+
+
+
+
+
+
+
+
+
🛡️ Top Modérateurs
+ {% if stats.top_moderators %}
+
+ {% for mod_name, count in stats.top_moderators %}
+
+ {{ loop.index }}
+ {{ mod_name }}
+ {{ count }} sanction{{ 's' if count > 1 else '' }}
+
+ {% endfor %}
+
+ {% else %}
+
Aucune donnée disponible
+ {% endif %}
+
+
+
+
⚖️ Types de sanctions
+ {% if stats.type_counts %}
+
+ {% for type_name, count in stats.type_counts.items() %}
+
+ {{ type_name }}
+ {{ count }}
+
+ {% endfor %}
+
+ {% else %}
+
Aucune donnée disponible
+ {% endif %}
+
+
+
+
⚠️ Utilisateurs les plus sanctionnés
+ {% if stats.top_sanctioned %}
+
+ {% for username, count in stats.top_sanctioned %}
+
+ {{ loop.index }}
+ {{ username }}
+ {{ count }} sanction{{ 's' if count > 1 else '' }}
+
+ {% endfor %}
+
+ {% else %}
+
Aucune donnée disponible
+ {% endif %}
+
+
+
+
+
+
+ 📖
+ Commandes de modération disponibles
+
+
+
+
+ !averto @utilisateur raison
+
+
Avertit un utilisateur et enregistre l'avertissement
+
Alias : !warn, !av, !avertissement
+
+
+
+ !delaverto id
+
+
Retire un avertissement par son ID
+
Alias : !removewarn, !delwarn
+
+
+
+ !warnings [@utilisateur]
+
+
Liste les événements de modération
+
Alias : !listevent, !listwarn
+
+
+
+ !inspect @utilisateur
+
+
Informations détaillées sur un utilisateur
+
Aussi : !inspect id
+
+
+
+ !kick @utilisateur raison
+
+
Expulse un utilisateur du serveur
+
+
+
+ !ban @utilisateur raison
+
+
Bannit définitivement un utilisateur
+
+
+
+ !unban discord_id
+
+
Révoque un bannissement
+
Aussi : !unban #sanction_id raison
+
+
+
+ !banlist
+
+
Liste des utilisateurs bannis
+
+
+
+ !aide
+
+
Affiche toutes les commandes
+
Alias : !help
+
+
+
{% if not event %}
-Événements de modération
+📜 Historique des événements
+{% if events %}
@@ -73,24 +187,31 @@
{% for mod_event in events %}
- {{ mod_event.type }}
+ {{ mod_event.type }}
{{ mod_event.username }}
- {{ mod_event.discord_id }}
+ {{ mod_event.discord_id }}
{{ mod_event.created_at.strftime('%d/%m/%Y %H:%M') if mod_event.created_at else 'N/A' }}
{{ mod_event.reason }}
{{ mod_event.staff_name }}
- ✐
- 🗑
+ ✐
+ 🗑
{% endfor %}
+{% else %}
+
+
🎉
+
Aucun événement de modération enregistré
+
Le serveur est sage !
+
+{% endif %}
{% endif %}
{% if event %}
-Editer un événement
+✏️ Editer un événement
{% endif %}
+{% if show_invites %}
+
+🔗 Invitations Discord
+
+
+
+{% if invite_stats %}
+
+
+
📊 Statistiques des invitations
+
+
+
🔗
+
+ Invitations actives
+ {{ invite_stats.total }}
+
+
+
+
👥
+
+ Utilisations totales
+ {{ invite_stats.total_uses }}
+
+
+
+
♾️
+
+ Permanentes
+ {{ invite_stats.permanent }}
+
+
+
+
⏳
+
+ Temporaires
+ {{ invite_stats.temporary }}
+
+
+
+
+
+{% if invite_stats.top_inviters %}
+
+
+
🏆 Top inviteurs
+
+ {% for inviter_name, uses in invite_stats.top_inviters %}
+
+ {{ loop.index }}
+ {{ inviter_name }}
+ {{ uses }} membre{{ 's' if uses > 1 else '' }} invité{{ 's' if uses > 1 else '' }}
+
+ {% endfor %}
+
+
+
+{% endif %}
+{% endif %}
+
+
+{% if invites %}
+
+
+
+ Code
+ Canal
+ Créateur
+ Utilisations
+ Max utilisations
+ Expiration
+ Créée le
+ Statut
+ #
+
+
+
+ {% for invite in invites %}
+
+ {{ invite.code }}
+ {{ invite.channel_name or 'N/A' }}
+ {{ invite.inviter_name or 'Inconnu' }}
+ {{ invite.uses or 0 }}
+ {{ invite.max_uses if invite.max_uses > 0 else '∞' }}
+
+ {% if invite.max_age == 0 %}
+ Jamais
+ {% elif invite.expires_at %}
+ {{ invite.expires_at.strftime('%d/%m/%Y %H:%M') }}
+ {% else %}
+ N/A
+ {% endif %}
+
+ {{ invite.created_at.strftime('%d/%m/%Y %H:%M') if invite.created_at else 'N/A' }}
+
+ {% if invite.revoked %}
+ Révoquée
+ {% elif invite.is_expired %}
+ Expirée
+ {% elif invite.max_uses > 0 and invite.uses >= invite.max_uses %}
+ Max atteint
+ {% else %}
+ Active
+ {% endif %}
+
+
+ {% if not invite.revoked %}
+ 🗑
+ {% endif %}
+
+
+ {% endfor %}
+
+
+{% else %}
+
+
🔗
+
Aucune invitation trouvée
+
Cliquez sur "Synchroniser les invitations" pour récupérer les invitations depuis Discord
+
+{% endif %}
+
+
+{% endif %}
+
{% endblock %}
diff --git a/webapp/templates/template.html b/webapp/templates/template.html
index e36f520..a606a2b 100644
--- a/webapp/templates/template.html
+++ b/webapp/templates/template.html
@@ -7,7 +7,9 @@
Mamie Henriette
-
+
+
+
@@ -16,18 +18,56 @@
-
-
+ {% with messages = get_flashed_messages(with_categories=true) %}
+ {% if messages %}
+
+ {% for category, message in messages %}
+
{{ message }}
+ {% endfor %}
+
+ {% endif %}
+ {% endwith %}
{% block content %}{% endblock %}