From f2d7b1809619519c3794961f2c2d3c02f57ae407 Mon Sep 17 00:00:00 2001 From: Adam Date: Thu, 4 Dec 2025 00:31:58 +0100 Subject: [PATCH 1/2] fix: handle empty public folder in Docker build --- cdn-dashboard/Dockerfile | 5 ++++- cdn-dashboard/public/.gitkeep | 0 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 cdn-dashboard/public/.gitkeep diff --git a/cdn-dashboard/Dockerfile b/cdn-dashboard/Dockerfile index 15b43b8..107a2a5 100644 --- a/cdn-dashboard/Dockerfile +++ b/cdn-dashboard/Dockerfile @@ -27,9 +27,12 @@ RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 nextjs # Copy necessary files from builder -COPY --from=builder /app/public ./public +# Create public directory (may be empty) +RUN mkdir -p ./public COPY --from=builder /app/.next/standalone ./ COPY --from=builder /app/.next/static ./.next/static +# Copy public files if they exist (using wildcard to handle empty dir) +COPY --from=builder /app/public/. ./public/ # Set correct permissions RUN chown -R nextjs:nodejs /app diff --git a/cdn-dashboard/public/.gitkeep b/cdn-dashboard/public/.gitkeep new file mode 100644 index 0000000..e69de29 From 364489af6dade0423b19216f8031a2a780157a7a Mon Sep 17 00:00:00 2001 From: Adam Date: Thu, 4 Dec 2025 00:34:21 +0100 Subject: [PATCH 2/2] feat: enhance UI components with responsive design and improved layouts --- .../src/app/dashboard/files/page.tsx | 64 +++--- cdn-dashboard/src/app/dashboard/layout.tsx | 7 +- cdn-dashboard/src/app/dashboard/logs/page.tsx | 66 +++--- cdn-dashboard/src/app/dashboard/page.tsx | 26 +-- .../src/app/dashboard/tokens/page.tsx | 19 +- cdn-dashboard/src/components/mobile-nav.tsx | 215 ++++++++++++++++++ .../src/components/notifications.tsx | 6 +- cdn-dashboard/src/components/sidebar.tsx | 3 +- docker-compose.yml | 2 + 9 files changed, 316 insertions(+), 92 deletions(-) create mode 100644 cdn-dashboard/src/components/mobile-nav.tsx diff --git a/cdn-dashboard/src/app/dashboard/files/page.tsx b/cdn-dashboard/src/app/dashboard/files/page.tsx index 82fa3dc..7c69a2c 100644 --- a/cdn-dashboard/src/app/dashboard/files/page.tsx +++ b/cdn-dashboard/src/app/dashboard/files/page.tsx @@ -331,12 +331,12 @@ export default function FilesPage() { }; return ( - + {/* Header */} -
+
-

Fichiers

-

Gérez vos fichiers uploadés

+

Fichiers

+

Gérez vos fichiers uploadés

{filteredFiles.length > 0 && ( @@ -390,8 +390,8 @@ export default function FilesPage() { {/* Search & Filters */} - -
+ +
{/* Search */}
@@ -400,30 +400,31 @@ export default function FilesPage() { placeholder="Rechercher un fichier..." value={search} onChange={(e) => setSearch(e.target.value)} - className="w-full pl-10 pr-4 py-2.5 rounded-xl bg-zinc-800/50 border border-zinc-700/50 text-zinc-100 placeholder-zinc-600 focus:outline-none focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500/50 transition-all" + className="w-full pl-10 pr-4 py-2.5 rounded-xl bg-zinc-800/50 border border-zinc-700/50 text-zinc-100 placeholder-zinc-600 focus:outline-none focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500/50 transition-all text-sm sm:text-base" />
- {/* Type Filter */} -
- {FILE_TYPE_FILTERS.map((filter) => ( - - ))} -
+
+ {/* Type Filter */} +
+ {FILE_TYPE_FILTERS.map((filter) => ( + + ))} +
{/* View Toggle */} -
+
+
{/* Files */} - -

+ +

{filteredFiles.length} fichier(s)

-
- - Trier par: +
+ + Trier par:
) : ( /* Grid View */ -
+
{filteredFiles.map((file: CDNFile, index: number) => { const mimeType = file.mime_type || file.mimeType; const isImage = mimeType?.startsWith('image/'); diff --git a/cdn-dashboard/src/app/dashboard/layout.tsx b/cdn-dashboard/src/app/dashboard/layout.tsx index cdfd214..173a70c 100644 --- a/cdn-dashboard/src/app/dashboard/layout.tsx +++ b/cdn-dashboard/src/app/dashboard/layout.tsx @@ -3,6 +3,7 @@ import { useEffect, useState } from 'react'; import { useRouter, usePathname } from 'next/navigation'; import { Sidebar } from '@/components/sidebar'; +import { MobileNav } from '@/components/mobile-nav'; import { useAuthStore } from '@/lib/auth'; import { cn } from '@/lib/utils'; import { motion, AnimatePresence } from 'framer-motion'; @@ -79,6 +80,7 @@ export default function DashboardLayout({
+ -
+
{children}
diff --git a/cdn-dashboard/src/app/dashboard/logs/page.tsx b/cdn-dashboard/src/app/dashboard/logs/page.tsx index b238412..b208667 100644 --- a/cdn-dashboard/src/app/dashboard/logs/page.tsx +++ b/cdn-dashboard/src/app/dashboard/logs/page.tsx @@ -169,12 +169,12 @@ export default function LogsPage() { }, [data?.data]); return ( - + {/* Header */} -
+
-

Logs d'accès

-

Historique des téléchargements et accès

+

Logs d'accès

+

Historique des téléchargements et accès

{filteredLogs.length > 0 && ( @@ -236,55 +236,55 @@ export default function LogsPage() {
{/* Quick Stats */} -
+
- -
-
- + +
+
+
-

{stats.downloads}

-

Téléchargements

+

{stats.downloads}

+

Téléchargements

- -
-
- + +
+
+
-

{stats.uploads}

-

Uploads

+

{stats.uploads}

+

Uploads

- -
-
- + +
+
+
-

{stats.errors}

-

Erreurs

+

{stats.errors}

+

Erreurs

- -
-
- + +
+
+
-

{stats.uniqueIPs}

-

IPs uniques

+

{stats.uniqueIPs}

+

IPs uniques

@@ -293,17 +293,17 @@ export default function LogsPage() { {/* Search & Filters */} - -
+ +
{/* Search */}
setSearch(e.target.value)} - className="w-full pl-10 pr-4 py-2.5 rounded-xl bg-zinc-800/50 border border-zinc-700/50 text-zinc-100 placeholder-zinc-600 focus:outline-none focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500/50 transition-all" + className="w-full pl-10 pr-4 py-2.5 rounded-xl bg-zinc-800/50 border border-zinc-700/50 text-zinc-100 placeholder-zinc-600 focus:outline-none focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500/50 transition-all text-sm" />
diff --git a/cdn-dashboard/src/app/dashboard/page.tsx b/cdn-dashboard/src/app/dashboard/page.tsx index 8d1817b..55a4ce9 100644 --- a/cdn-dashboard/src/app/dashboard/page.tsx +++ b/cdn-dashboard/src/app/dashboard/page.tsx @@ -47,15 +47,15 @@ function StatsCard({ }) { return ( -
+
-
-

{title}

-
+
+

{title}

+
{subtitle && ( -

{subtitle}

+

{subtitle}

)} {trend && (
@@ -173,13 +173,13 @@ export default function DashboardPage() { initial={{ opacity: 0, y: -10 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: 0.1 }} - className="flex items-center justify-between" + className="flex flex-col sm:flex-row sm:items-center justify-between gap-4" >
-

Dashboard

-

Vue d'ensemble de votre CDN

+

Dashboard

+

Vue d'ensemble de votre CDN

-
+
{lastUpdate && ( - + {formatRelativeTime(lastUpdate.toISOString())} )} @@ -200,7 +200,7 @@ export default function DashboardPage() { {/* Stats Grid */} -
+
{/* Charts & Activity */} -
+
{/* Downloads Chart */}
diff --git a/cdn-dashboard/src/app/dashboard/tokens/page.tsx b/cdn-dashboard/src/app/dashboard/tokens/page.tsx index 8c8c4f3..bfeb2a8 100644 --- a/cdn-dashboard/src/app/dashboard/tokens/page.tsx +++ b/cdn-dashboard/src/app/dashboard/tokens/page.tsx @@ -115,31 +115,32 @@ export default function TokensPage() { }); return ( - + {/* Header */} -
+
-

Tokens d'accès

-

Gérez les tokens de téléchargement

+

Tokens d'accès

+

Gérez les tokens de téléchargement

{/* Search & Filters */} - +
setSearch(e.target.value)} - className="w-full pl-10 pr-4 py-2.5 rounded-xl bg-zinc-800/50 border border-zinc-700/50 text-zinc-100 placeholder-zinc-600 focus:outline-none focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500/50 transition-all" + className="w-full pl-10 pr-4 py-2.5 rounded-xl bg-zinc-800/50 border border-zinc-700/50 text-zinc-100 placeholder-zinc-600 focus:outline-none focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500/50 transition-all text-sm" />
@@ -149,7 +150,7 @@ export default function TokensPage() { {/* Tokens List */} -

+

{data?.pagination?.total || 0} token(s)

diff --git a/cdn-dashboard/src/components/mobile-nav.tsx b/cdn-dashboard/src/components/mobile-nav.tsx new file mode 100644 index 0000000..1d6fd78 --- /dev/null +++ b/cdn-dashboard/src/components/mobile-nav.tsx @@ -0,0 +1,215 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import Link from 'next/link'; +import { usePathname } from 'next/navigation'; +import { + LayoutDashboard, + Files, + Key, + Users, + Settings, + LogOut, + Shield, + Activity, + Menu, + X, + Sparkles, + Bell +} from 'lucide-react'; +import { cn } from '@/lib/utils'; +import { useAuthStore, useIsSuperadmin } from '@/lib/auth'; +import { motion, AnimatePresence } from 'framer-motion'; +import { useActivityStore } from '@/lib/notifications'; + +const navigation = [ + { name: 'Dashboard', href: '/dashboard', icon: LayoutDashboard }, + { name: 'Fichiers', href: '/dashboard/files', icon: Files }, + { name: 'Tokens', href: '/dashboard/tokens', icon: Key }, + { name: 'Logs', href: '/dashboard/logs', icon: Activity }, +]; + +const adminNavigation = [ + { name: 'Utilisateurs', href: '/dashboard/users', icon: Users }, + { name: 'Clés API', href: '/dashboard/api-keys', icon: Shield }, + { name: 'Paramètres', href: '/dashboard/settings', icon: Settings }, +]; + +export function MobileNav() { + const [isOpen, setIsOpen] = useState(false); + const pathname = usePathname(); + const { user, logout } = useAuthStore(); + const isSuperadmin = useIsSuperadmin(); + const { unreadCount } = useActivityStore(); + + // Close menu when route changes + useEffect(() => { + setIsOpen(false); + }, [pathname]); + + // Prevent scroll when menu is open + useEffect(() => { + if (isOpen) { + document.body.style.overflow = 'hidden'; + } else { + document.body.style.overflow = ''; + } + return () => { + document.body.style.overflow = ''; + }; + }, [isOpen]); + + const handleLogout = async () => { + await logout(); + window.location.href = '/login'; + }; + + return ( + <> + {/* Mobile Header */} +
+
+ {/* Logo */} + +
+ +
+
+

CDN

+
+ + + {/* Right actions */} +
+ {/* Notifications indicator */} + + + {/* Menu toggle */} + +
+
+
+ + {/* Mobile Menu Overlay */} + + {isOpen && ( + <> + {/* Backdrop */} + setIsOpen(false)} + className="lg:hidden fixed inset-0 z-40 bg-black/60 backdrop-blur-sm" + /> + + {/* Slide-out menu */} + + + + + )} + + + ); +} diff --git a/cdn-dashboard/src/components/notifications.tsx b/cdn-dashboard/src/components/notifications.tsx index 9c5729d..347f609 100644 --- a/cdn-dashboard/src/components/notifications.tsx +++ b/cdn-dashboard/src/components/notifications.tsx @@ -303,11 +303,11 @@ export function ActivityDropdown() { {/* Panel */} diff --git a/cdn-dashboard/src/components/sidebar.tsx b/cdn-dashboard/src/components/sidebar.tsx index a902ba2..579acd4 100644 --- a/cdn-dashboard/src/components/sidebar.tsx +++ b/cdn-dashboard/src/components/sidebar.tsx @@ -69,7 +69,8 @@ export function Sidebar() { 'fixed left-0 top-0 z-40 h-screen', 'bg-zinc-950/80 backdrop-blur-xl', 'border-r border-zinc-800/50', - 'flex flex-col' + 'flex flex-col', + 'hidden lg:flex' )} > {/* Logo Section */} diff --git a/docker-compose.yml b/docker-compose.yml index 3bc4947..46ffe10 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,6 +3,8 @@ services: build: ./services/postgres container_name: cdn-postgres restart: unless-stopped + ports: + - "5433:5432" environment: POSTGRES_DB: ${POSTGRES_DB:-cdn_db} POSTGRES_USER: ${POSTGRES_USER:-cdn_user}