Panneau d'administration
Comment fonctionne le panneau d'administration — gestion des utilisateurs, abonnements, contenu et analyses du tableau de bord.
Panneau d'administration
ScaleRocket inclut un panneau d'administration complet (apps/ops) construit avec Vite + React. Il vous offre un tableau de bord privé pour gérer les utilisateurs, suivre les abonnements, éditer le contenu du blog et consulter les analyses.
Comment fonctionne l'application Ops
Le panneau d'administration est une application Vite séparée qui tourne sur le port 5174. Il partage des packages avec le tableau de bord principal (packages/ui, packages/types, packages/config) mais possède ses propres routes et pages.
apps/ops/
├── src/
│ ├── routes/
│ │ ├── __root.tsx # Root layout with sidebar
│ │ ├── index.tsx # Dashboard stats
│ │ ├── users/
│ │ │ ├── index.tsx # Users list
│ │ │ └── $userId.tsx # User detail
│ │ ├── subscriptions/
│ │ │ └── index.tsx # Subscriptions list
│ │ └── blog/
│ │ ├── index.tsx # Blog posts list
│ │ └── editor.tsx # Blog post editor
│ ├── lib/
│ │ ├── supabase.ts # Supabase client
│ │ └── api.ts # API utilities
│ └── main.tsx
├── .env.example
└── package.jsonListe blanche des administrateurs
L'accès au panneau d'administration est restreint par une liste blanche d'emails. Seuls les emails dans la liste blanche peuvent se connecter.
Configurer la liste blanche
Définissez les emails d'administration dans votre environnement :
# apps/ops/.env.local
VITE_SUPABASE_URL=https://your-project.supabase.co
VITE_SUPABASE_ANON_KEY=eyJ...
VITE_ADMIN_EMAILS=you@yourdomain.com,cofounder@yourdomain.comLa route racine vérifie la liste blanche au chargement :
// apps/ops/src/routes/__root.tsx
const adminEmails = import.meta.env.VITE_ADMIN_EMAILS?.split(",") || [];
export const Route = createRootRoute({
beforeLoad: async () => {
const { data: { user } } = await supabase.auth.getUser();
if (!user || !adminEmails.includes(user.email)) {
throw redirect({ to: "/unauthorized" });
}
},
});Note : Pour une sécurité renforcée, vous pouvez également appliquer l'accès administrateur au niveau de la base de données avec des politiques RLS qui vérifient une table
admins.
Statistiques du tableau de bord
La page d'accueil du panneau d'administration affiche les métriques clés en un coup d'oeil :
- Total utilisateurs — nombre de tous les utilisateurs inscrits
- Abonnements actifs — nombre par plan
- Revenu mensuel — calculé à partir des abonnements actifs
- Nouvelles inscriptions — utilisateurs inscrits dans les 7 derniers jours
- Utilisation des crédits — total des crédits consommés ce mois-ci
// apps/ops/src/routes/index.tsx
import { supabase } from "../lib/supabase";
async function getDashboardStats() {
const [
{ count: totalUsers },
{ count: activeSubscriptions },
{ data: recentSignups },
] = await Promise.all([
supabase.from("profiles").select("*", { count: "exact", head: true }),
supabase
.from("subscriptions")
.select("*", { count: "exact", head: true })
.eq("status", "active"),
supabase
.from("profiles")
.select("id, email, created_at")
.gte("created_at", new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString())
.order("created_at", { ascending: false }),
]);
return { totalUsers, activeSubscriptions, recentSignups };
}Gestion des utilisateurs
La page des utilisateurs liste tous les utilisateurs inscrits avec recherche et pagination :
- Voir les détails de l'utilisateur — email, date d'inscription, plan, solde de crédits
- Voir l'abonnement — plan actuel, période de facturation, prochain renouvellement
- Voir les crédits — solde actuel, allocation mensuelle, historique d'utilisation
Page de détail de l'utilisateur
Cliquez sur n'importe quel utilisateur pour voir son profil complet :
// apps/ops/src/routes/users/$userId.tsx
async function getUserDetail(userId: string) {
const [
{ data: profile },
{ data: subscription },
{ data: credits },
] = await Promise.all([
supabase.from("profiles").select("*").eq("id", userId).single(),
supabase.from("subscriptions").select("*").eq("user_id", userId).single(),
supabase.from("credits").select("*").eq("user_id", userId).single(),
]);
return { profile, subscription, credits };
}Note : Le panneau d'administration utilise la clé
service_rolevia les Edge Functions pour les opérations qui doivent contourner le RLS (comme consulter les données d'autres utilisateurs). Les requêtes Supabase directes depuis l'application ops utilisent des politiques RLS limitées aux utilisateurs admin.
Gestion des abonnements
La page des abonnements affiche tous les abonnements actifs, annulés et en retard de paiement :
- Filtrer par statut (active, cancelled, past_due, trialing)
- Voir les détails de l'abonnement (plan, montant, cycle de facturation)
- Lien vers le client dans le Stripe Dashboard
// Fetch subscriptions with user info
const { data: subscriptions } = await supabase
.from("subscriptions")
.select(`
*,
profiles:user_id (email, full_name)
`)
.order("created_at", { ascending: false });Éditeur de blog
Le panneau d'administration inclut un éditeur de blog pour publier du contenu sur le site marketing :
Lister les articles de blog
const { data: posts } = await supabase
.from("blog_posts")
.select("id, title, slug, status, published_at, created_at")
.order("created_at", { ascending: false });Créer/Modifier un article
L'éditeur de blog supporte :
- Titre et slug (généré automatiquement à partir du titre)
- Contenu en Markdown
- Statut — brouillon ou publié
- Image de couverture téléversée (stockée dans Supabase Storage)
- SEO — meta title et meta description
// Save a blog post
async function savePost(post: BlogPost) {
const { data, error } = await supabase
.from("blog_posts")
.upsert({
id: post.id,
title: post.title,
slug: post.slug,
content: post.content,
status: post.status,
cover_image: post.coverImage,
meta_title: post.metaTitle,
meta_description: post.metaDescription,
published_at: post.status === "published" ? new Date().toISOString() : null,
})
.select()
.single();
if (error) throw error;
return data;
}Lancer le panneau d'administration
Démarrez toutes les applications depuis la racine :
pnpm devOu démarrez uniquement le panneau d'administration :
pnpm --filter ops devVisitez http://localhost:5174 et connectez-vous avec un email de la liste blanche admin.
Considérations de sécurité
- La liste blanche admin est une première couche de défense, pas la seule
- Utilisez des politiques RLS avec une table
adminspour une protection au niveau de la base de données - N'exposez jamais les clés
service_roleau client - Envisagez d'ajouter un journal d'audit pour les actions d'administration
- En production, restreignez le panneau d'administration à un sous-domaine séparé (ex.
admin.yourdomain.com)
Prochaines étapes
- Déployer toutes les applications — y compris le panneau d'administration
- Configurer le système de crédits — gérer depuis le panneau d'administration
- Configurer Stripe — abonnements affichés dans l'admin
Fini ? Marquez cette page comme terminée.