feat: replace initial-data with full *.lafabriquedesgenies.be HTTPS services & direct URL editor

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
klemensandCursor committed 2026-10-22 00:23:54 +02:00
1 parent 91a57285b9
commit 205337f5c8
8 files changed
+870 -322

No files matched your search

+402 -3
View File
@@ -1,4 +1,403 @@
{
"version": "1.0.0",
"note": "Fichier de persistance locale pour le portail Home. Synchronisé avec src/data/initial-data.ts."
}
"version": "3.0.0",
"dockTileIds": [
"nextcloud",
"dockge",
"gitea",
"jellyfin",
"open-webui",
"vaultwarden"
],
"quickNote": "Bienvenue sur Home OS (*.lafabriquedesgenies.be). Cliquez sur « Éditer » pour ajuster n'importe quelle URL ou sous-domaine à la volée.",
"categories": [
{
"id": "infrastructure",
"name": "Infrastructure & Serveur",
"subtitle": "Administration système, conteneurs Docker, sauvegardes & réseau",
"icon": "Server",
"tabs": [
{
"id": "infra-main",
"label": "Services Serveur",
"tiles": [
{
"id": "dockge",
"title": "Dockge",
"description": "Gestionnaire Docker Compose",
"url": "https://dockge.lafabriquedesgenies.be",
"icon": "Terminal",
"size": "2x1",
"badge": "Docker",
"accentColor": "emerald",
"openBehavior": "external"
},
{
"id": "nginx-proxy-manager",
"title": "Nginx Proxy Manager",
"description": "Reverse Proxy & Certificats SSL",
"url": "https://proxy.lafabriquedesgenies.be",
"icon": "Shield",
"size": "1x1",
"badge": "Proxy SSL",
"accentColor": "blue",
"openBehavior": "external"
},
{
"id": "duplicati",
"title": "Duplicati",
"description": "Sauvegardes du serveur",
"url": "https://backup.lafabriquedesgenies.be",
"icon": "Archive",
"size": "1x1",
"badge": "Backup",
"accentColor": "sky",
"openBehavior": "external"
},
{
"id": "speedtest",
"title": "Speedtest",
"description": "Surveillance débit réseau",
"url": "https://speedtest.lafabriquedesgenies.be",
"icon": "Activity",
"size": "1x1",
"badge": "Réseau",
"accentColor": "cyan",
"openBehavior": "external"
},
{
"id": "adminer",
"title": "Adminer",
"description": "Gestionnaire bases SQL",
"url": "https://db.lafabriquedesgenies.be",
"icon": "Database",
"size": "1x1",
"badge": "SQL",
"accentColor": "amber",
"openBehavior": "external"
},
{
"id": "adguard-home",
"title": "AdGuard Home",
"description": "Filtrage DNS & Antipub",
"url": "https://adguard.lafabriquedesgenies.be",
"icon": "ShieldCheck",
"size": "1x1",
"badge": "DNS",
"accentColor": "teal",
"openBehavior": "external"
},
{
"id": "watchtower",
"title": "Watchtower",
"description": "Mises à jour automatiques",
"url": "https://dockge.lafabriquedesgenies.be",
"icon": "RefreshCw",
"size": "1x1",
"badge": "Daemon Auto",
"accentColor": "violet",
"openBehavior": "external"
}
]
}
]
},
{
"id": "private-security",
"name": "Privé & Sécurité",
"subtitle": "Cloud personnel, mots de passe, documents, notes & domotique",
"icon": "ShieldCheck",
"tabs": [
{
"id": "private-main",
"label": "Espace Privé",
"tiles": [
{
"id": "nextcloud",
"title": "Nextcloud",
"description": "Fichiers & Drive Cloud",
"url": "https://nc.lafabriquedesgenies.be",
"icon": "Cloud",
"size": "2x1",
"badge": "Cloud",
"accentColor": "sky",
"openBehavior": "external"
},
{
"id": "vaultwarden",
"title": "Vaultwarden",
"description": "Gestionnaire de mots de passe",
"url": "https://vault.lafabriquedesgenies.be",
"icon": "Key",
"size": "1x1",
"badge": "Coffre-fort",
"accentColor": "amber",
"openBehavior": "external"
},
{
"id": "paperless-ngx",
"title": "Paperless-ngx",
"description": "GED & Archivage intelligent",
"url": "https://paperless.lafabriquedesgenies.be",
"icon": "FileText",
"size": "1x1",
"badge": "GED",
"accentColor": "emerald",
"openBehavior": "external"
},
{
"id": "wikijs",
"title": "Wiki.js",
"description": "Base de connaissances & notes",
"url": "https://wiki.lafabriquedesgenies.be",
"icon": "BookOpen",
"size": "1x1",
"badge": "Wiki",
"accentColor": "indigo",
"openBehavior": "external"
},
{
"id": "stirling-pdf",
"title": "Stirling PDF",
"description": "Boîte à outils PDF complète",
"url": "https://pdf.lafabriquedesgenies.be",
"icon": "FileCode",
"size": "1x1",
"badge": "PDF",
"accentColor": "rose",
"openBehavior": "external"
},
{
"id": "home-assistant",
"title": "Home Assistant",
"description": "Domotique de la maison",
"url": "https://hass.lafabriquedesgenies.be",
"icon": "Home",
"size": "2x1",
"badge": "Domotique",
"accentColor": "cyan",
"openBehavior": "external"
}
]
}
]
},
{
"id": "multimedia",
"name": "Multimédia & Médias",
"subtitle": "Streaming vidéo, livres audio, musique & téléchargements",
"icon": "Film",
"tabs": [
{
"id": "media-main",
"label": "Streaming & Téléchargements",
"tiles": [
{
"id": "jellyfin",
"title": "Jellyfin",
"description": "Streaming Films & Séries",
"url": "https://jellyfin.lafabriquedesgenies.be",
"icon": "Film",
"size": "2x1",
"badge": "Streaming",
"accentColor": "purple",
"openBehavior": "external"
},
{
"id": "audiobookshelf",
"title": "Audiobookshelf",
"description": "Livres audio & Podcasts",
"url": "https://audiobookshelf.lafabriquedesgenies.be",
"icon": "Headphones",
"size": "1x1",
"badge": "Audio",
"accentColor": "amber",
"openBehavior": "external"
},
{
"id": "sonarr",
"title": "Sonarr",
"description": "Suivi & gestion des séries",
"url": "https://sonarr.lafabriquedesgenies.be",
"icon": "Tv",
"size": "1x1",
"badge": "Séries",
"accentColor": "sky",
"openBehavior": "external"
},
{
"id": "radarr",
"title": "Radarr",
"description": "Suivi & gestion des films",
"url": "https://radarr.lafabriquedesgenies.be",
"icon": "Video",
"size": "1x1",
"badge": "Films",
"accentColor": "amber",
"openBehavior": "external"
},
{
"id": "qbittorrent",
"title": "qBittorrent",
"description": "Téléchargements VPN (Gluetun)",
"url": "https://torrent.lafabriquedesgenies.be",
"icon": "DownloadCloud",
"size": "1x1",
"badge": "VPN Gluetun",
"accentColor": "blue",
"openBehavior": "external"
},
{
"id": "deemix",
"title": "Deemix",
"description": "Téléchargement Musique",
"url": "https://deemix.lafabriquedesgenies.be",
"icon": "Music",
"size": "1x1",
"badge": "Musique",
"accentColor": "pink",
"openBehavior": "external"
},
{
"id": "sabnzbd",
"title": "SABnzbd",
"description": "Client Usenet",
"url": "https://sabnzbd.lafabriquedesgenies.be",
"icon": "HardDriveDownload",
"size": "1x1",
"badge": "Usenet",
"accentColor": "emerald",
"openBehavior": "external"
}
]
}
]
},
{
"id": "projects-work",
"name": "Projets & Travail",
"subtitle": "Forge Git, applications La Fabrique des Génies & outils métiers",
"icon": "Briefcase",
"tabs": [
{
"id": "work-main",
"label": "Applications & Projets",
"tiles": [
{
"id": "gitea",
"title": "Gitea",
"description": "Dépôts de code Git",
"url": "https://git.lafabriquedesgenies.be",
"icon": "GitBranch",
"size": "2x1",
"badge": "Git",
"accentColor": "emerald",
"openBehavior": "external"
},
{
"id": "wordpress-lafabrique",
"title": "WordPress La Fabrique",
"description": "Site officiel La Fabrique",
"url": "https://lafabriquedesgenies.be",
"icon": "Globe",
"size": "1x1",
"badge": "Site Web",
"accentColor": "blue",
"openBehavior": "external"
},
{
"id": "teacher-app",
"title": "Teacher App",
"description": "Plateforme pédagogique",
"url": "https://teacher.lafabriquedesgenies.be",
"icon": "GraduationCap",
"size": "1x1",
"badge": "Pédagogie",
"accentColor": "indigo",
"openBehavior": "external"
},
{
"id": "creaprof",
"title": "CreaProf",
"description": "Outil création enseignant",
"url": "https://creaprof.lafabriquedesgenies.be",
"icon": "PenTool",
"size": "1x1",
"badge": "Création",
"accentColor": "violet",
"openBehavior": "external"
},
{
"id": "ubicraft",
"title": "Ubicraft",
"description": "Application métier",
"url": "https://ubicraft.lafabriquedesgenies.be",
"icon": "LayoutGrid",
"size": "1x1",
"badge": "Métier",
"accentColor": "cyan",
"openBehavior": "external"
},
{
"id": "lma-v2",
"title": "LMA V2",
"description": "Plateforme Last Minute Activities",
"url": "https://lma.lafabriquedesgenies.be",
"icon": "Briefcase",
"size": "1x1",
"badge": "LMA",
"accentColor": "amber",
"openBehavior": "external"
},
{
"id": "coo-app",
"title": "Coo App",
"description": "Application de gestion",
"url": "https://coo.lafabriquedesgenies.be",
"icon": "Layers",
"size": "1x1",
"badge": "Gestion",
"accentColor": "teal",
"openBehavior": "external"
}
]
}
]
},
{
"id": "ai",
"name": "Intelligence Artificielle",
"subtitle": "Modèles LLM auto-hébergés & Assistant Google Gemini intégré",
"icon": "Sparkles",
"tabs": [
{
"id": "ai-main",
"label": "Assistants IA",
"tiles": [
{
"id": "open-webui",
"title": "Open WebUI",
"description": "Interface modèles LLM",
"url": "https://ai.lafabriquedesgenies.be",
"icon": "Bot",
"size": "2x1",
"badge": "LLM",
"accentColor": "emerald",
"openBehavior": "external"
},
{
"id": "google-gemini",
"title": "Google Gemini",
"description": "Assistant IA intégré",
"url": "#gemini",
"icon": "Sparkles",
"size": "2x1",
"badge": "Omnibox IA",
"accentColor": "blue",
"openBehavior": "external"
}
]
}
]
}
]
}
+11 -3
View File
@@ -12,7 +12,11 @@ async function readOrInitData(): Promise<DashboardData> {
await fs.mkdir(DATA_DIR, { recursive: true });
const raw = await fs.readFile(DATA_FILE, "utf-8");
const parsed = JSON.parse(raw);
if (parsed && Array.isArray(parsed.categories)) {
if (
parsed &&
Array.isArray(parsed.categories) &&
parsed.version === initialDashboardData.version
) {
return parsed as DashboardData;
}
await fs.writeFile(
@@ -46,9 +50,13 @@ export async function POST(request: Request) {
{ status: 400 }
);
}
const payload: DashboardData = {
...body,
version: initialDashboardData.version,
};
await fs.mkdir(DATA_DIR, { recursive: true });
await fs.writeFile(DATA_FILE, JSON.stringify(body, null, 2), "utf-8");
return NextResponse.json({ ok: true, data: body });
await fs.writeFile(DATA_FILE, JSON.stringify(payload, null, 2), "utf-8");
return NextResponse.json({ ok: true, data: payload });
} catch (error) {
return NextResponse.json(
{ error: "Impossible de sauvegarder data.json", details: String(error) },
+63 -43
View File
@@ -12,12 +12,16 @@ import {
Layers,
ArrowLeft,
ArrowRight,
Sparkles,
} from "lucide-react";
interface BentoGridProps {
tiles: ShortcutTile[];
isEditMode: boolean;
onLaunchTile: (tile: ShortcutTile, forceMode?: "workspace" | "popup" | "external") => void;
onLaunchTile: (
tile: ShortcutTile,
forceMode?: "workspace" | "popup" | "external"
) => void;
onAddTile: () => void;
onEditTile: (tile: ShortcutTile) => void;
onDeleteTile: (tileId: string) => void;
@@ -91,6 +95,7 @@ const accentStyles: Record<
};
function formatHostname(url: string) {
if (!url || url === "#gemini") return "Assistant intégré";
try {
const parsed = new URL(url);
return parsed.host;
@@ -120,19 +125,22 @@ export function BentoGrid({
const style =
accentStyles[tile.accentColor || "indigo"] || accentStyles.indigo;
const behavior = tile.openBehavior || "workspace";
const behavior = tile.openBehavior || "external";
const isGeminiWidget = tile.url === "#gemini";
return (
<div
key={tile.id}
onClick={() => {
if (!isEditMode) {
if (isEditMode) {
onEditTile(tile);
} else {
onLaunchTile(tile);
}
}}
className={`group relative flex flex-col justify-between overflow-hidden rounded-2xl border border-white/10 bg-zinc-900/60 p-4 sm:p-5 backdrop-blur-md transition-all duration-200 select-none ${spanClass} ${
isEditMode
? "border-dashed border-amber-500/40 bg-zinc-900/80"
? "cursor-pointer border-dashed border-amber-500/40 bg-zinc-900/80 hover:border-amber-400"
: `cursor-pointer ${style.borderHover} hover:-translate-y-0.5 hover:bg-zinc-900/90 hover:shadow-xl hover:shadow-black/40`
}`}
>
@@ -175,10 +183,11 @@ export function BentoGrid({
<button
type="button"
onClick={() => onEditTile(tile)}
title="Modifier la tuile"
className="rounded p-1 text-amber-300 hover:bg-amber-500/20"
title="Modifier l'URL ou les détails"
className="flex items-center gap-1 rounded bg-amber-500/15 px-1.5 py-1 text-[10px] font-medium text-amber-300 hover:bg-amber-500/25"
>
<Pencil className="size-3.5" />
<Pencil className="size-3" />
<span>URL</span>
</button>
<button
type="button"
@@ -203,41 +212,52 @@ export function BentoGrid({
</Badge>
)}
{/* Quick override buttons on hover */}
<div className="flex items-center gap-0.5 rounded-lg border border-white/8 bg-zinc-950/70 p-0.5 opacity-80 sm:opacity-0 transition-opacity group-hover:opacity-100">
<button
type="button"
onClick={() => onLaunchTile(tile, "workspace")}
title="Ouvrir dans un onglet intégré à Home"
className={`rounded p-1 transition-colors ${
behavior === "workspace"
? "text-indigo-400 bg-indigo-500/15"
: "text-zinc-400 hover:text-white"
}`}
>
<Layers className="size-3" />
</button>
<button
type="button"
onClick={() => onLaunchTile(tile, "popup")}
title="Ouvrir en fenêtre App Chromium (idéal Google Docs/Drive)"
className={`rounded p-1 transition-colors ${
behavior === "popup"
? "text-cyan-400 bg-cyan-500/15"
: "text-zinc-400 hover:text-white"
}`}
>
<AppWindow className="size-3" />
</button>
<button
type="button"
onClick={() => onLaunchTile(tile, "external")}
title="Ouvrir dans un nouvel onglet navigateur"
className="rounded p-1 text-zinc-400 hover:text-white"
>
<ExternalLink className="size-3" />
</button>
</div>
{isGeminiWidget ? (
<span className="flex items-center gap-1 rounded-lg border border-blue-500/30 bg-blue-500/15 px-2 py-1 text-[10px] font-medium text-blue-300">
<Sparkles className="size-3" />
<span>Ouvrir IA</span>
</span>
) : (
/* Quick override buttons on hover */
<div className="flex items-center gap-0.5 rounded-lg border border-white/8 bg-zinc-950/70 p-0.5 opacity-80 sm:opacity-0 transition-opacity group-hover:opacity-100">
<button
type="button"
onClick={() => onLaunchTile(tile, "external")}
title="Ouvrir dans un nouvel onglet"
className={`rounded p-1 transition-colors ${
behavior === "external"
? "text-emerald-400 bg-emerald-500/15"
: "text-zinc-400 hover:text-white"
}`}
>
<ExternalLink className="size-3" />
</button>
<button
type="button"
onClick={() => onLaunchTile(tile, "workspace")}
title="Ouvrir dans un onglet intégré à Home"
className={`rounded p-1 transition-colors ${
behavior === "workspace"
? "text-indigo-400 bg-indigo-500/15"
: "text-zinc-400 hover:text-white"
}`}
>
<Layers className="size-3" />
</button>
<button
type="button"
onClick={() => onLaunchTile(tile, "popup")}
title="Ouvrir en fenêtre App dédiée"
className={`rounded p-1 transition-colors ${
behavior === "popup"
? "text-cyan-400 bg-cyan-500/15"
: "text-zinc-400 hover:text-white"
}`}
>
<AppWindow className="size-3" />
</button>
</div>
)}
</div>
)}
</div>
@@ -248,7 +268,7 @@ export function BentoGrid({
<h3 className="text-base font-semibold tracking-tight text-zinc-100 group-hover:text-white truncate">
{tile.title}
</h3>
<span className="font-mono text-[10px] text-zinc-500 truncate max-w-[130px] shrink-0">
<span className="font-mono text-[10px] text-zinc-500 truncate max-w-[175px] shrink-0">
{formatHostname(tile.url)}
</span>
</div>
+10 -2
View File
@@ -32,6 +32,7 @@ export function DashboardShell({ initialData }: DashboardShellProps) {
>({});
const [searchQuery, setSearchQuery] = useState("");
const [isEditMode, setIsEditMode] = useState(false);
const [geminiTriggerCount, setGeminiTriggerCount] = useState(0);
// OS Window Manager state ("bento" or window.id)
const [openWindows, setOpenWindows] = useState<OpenWindowTab[]>([]);
@@ -122,7 +123,13 @@ export function DashboardShell({ initialData }: DashboardShellProps) {
tile: ShortcutTile,
forceMode?: "workspace" | "popup" | "external"
) => {
const mode = forceMode || tile.openBehavior || "workspace";
if (tile.url === "#gemini") {
setActiveViewId("bento");
setGeminiTriggerCount((c) => c + 1);
return;
}
const mode = forceMode || tile.openBehavior || "external";
if (mode === "external") {
window.open(tile.url, "_blank", "noopener,noreferrer");
@@ -134,7 +141,7 @@ export function DashboardShell({ initialData }: DashboardShellProps) {
return;
}
// Default: "workspace" -> Open or focus an in-OS tab
// "workspace" -> Open or focus an in-OS tab
const existing = openWindows.find((w) => w.tileId === tile.id);
if (existing) {
setActiveViewId(existing.id);
@@ -398,6 +405,7 @@ export function DashboardShell({ initialData }: DashboardShellProps) {
searchQuery={searchQuery}
onSearchChange={setSearchQuery}
onOpenCustomUrl={handleOpenCustomUrl}
geminiTriggerCount={geminiTriggerCount}
/>
<main className="flex-1 overflow-y-auto px-4 py-5 pb-24 sm:px-6 md:pb-8">
+21 -6
View File
@@ -1,6 +1,6 @@
"use client";
import { useState } from "react";
import { useState, useRef, useEffect } from "react";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import {
@@ -18,6 +18,7 @@ interface HeaderOmniboxProps {
searchQuery: string;
onSearchChange: (q: string) => void;
onOpenCustomUrl: (url: string, title?: string) => void;
geminiTriggerCount?: number;
}
export function HeaderOmnibox({
@@ -26,12 +27,23 @@ export function HeaderOmnibox({
searchQuery,
onSearchChange,
onOpenCustomUrl,
geminiTriggerCount = 0,
}: HeaderOmniboxProps) {
const [mode, setMode] = useState<"search" | "gemini" | "url">("search");
const [geminiPrompt, setGeminiPrompt] = useState("");
const [geminiLoading, setGeminiLoading] = useState(false);
const [geminiAnswer, setGeminiAnswer] = useState<string | null>(null);
const [customUrlInput, setCustomUrlInput] = useState("");
const geminiInputRef = useRef<HTMLInputElement | null>(null);
useEffect(() => {
if (geminiTriggerCount > 0) {
setMode("gemini");
setTimeout(() => {
geminiInputRef.current?.focus();
}, 50);
}
}, [geminiTriggerCount]);
const handleGeminiSubmit = async (e: React.FormEvent) => {
e.preventDefault();
@@ -65,7 +77,6 @@ export function HeaderOmnibox({
if (raw.includes(".") && !raw.includes(" ")) {
finalUrl = `https://${raw}`;
} else {
// DuckDuckGo / Wikipedia embed-friendly search or direct URL
finalUrl = `https://fr.wikipedia.org/w/index.php?search=${encodeURIComponent(raw)}`;
}
}
@@ -122,7 +133,10 @@ export function HeaderOmnibox({
<button
type="button"
onClick={() => setMode("gemini")}
onClick={() => {
setMode("gemini");
setTimeout(() => geminiInputRef.current?.focus(), 50);
}}
className={`flex items-center gap-1 rounded-lg px-2.5 py-1.5 text-xs font-medium transition-all ${
mode === "gemini"
? "bg-gradient-to-r from-indigo-500 to-blue-500 text-white shadow-xs"
@@ -138,7 +152,7 @@ export function HeaderOmnibox({
<Input
value={searchQuery}
onChange={(e) => onSearchChange(e.target.value)}
placeholder="Rechercher Nextcloud, Docs, Drive, Gitea..."
placeholder="Rechercher un service (*.lafabriquedesgenies.be)..."
className="h-8 border-0 bg-transparent text-sm text-zinc-100 placeholder:text-zinc-500 focus-visible:ring-0 dark:bg-transparent"
/>
)}
@@ -151,7 +165,7 @@ export function HeaderOmnibox({
<Input
value={customUrlInput}
onChange={(e) => setCustomUrlInput(e.target.value)}
placeholder="Entrer une URL (ex: cloud.lafabriquedesgenies.be) à ouvrir en onglet..."
placeholder="Entrer une URL (ex: nc.lafabriquedesgenies.be) à ouvrir..."
className="h-8 border-0 bg-transparent text-sm text-zinc-100 placeholder:text-zinc-500 focus-visible:ring-0 dark:bg-transparent"
/>
<Button
@@ -170,9 +184,10 @@ export function HeaderOmnibox({
className="flex flex-1 items-center"
>
<Input
ref={geminiInputRef}
value={geminiPrompt}
onChange={(e) => setGeminiPrompt(e.target.value)}
placeholder="Demander à Gemini (résumer un cours, traduire, formule Excel)..."
placeholder="Poser une question à Google Gemini, traduire, générer une idée..."
className="h-8 border-0 bg-transparent text-sm text-zinc-100 placeholder:text-zinc-500 focus-visible:ring-0 dark:bg-transparent"
/>
<Button
+26
View File
@@ -5,12 +5,21 @@ import {
Wrench,
Sparkles,
Container,
Shield,
ShieldCheck,
Archive,
Activity,
Cloud,
Key,
KeyRound,
Radar,
Film,
Headphones,
Tv,
Video,
DownloadCloud,
Music,
HardDriveDownload,
Image,
RefreshCw,
Network,
@@ -20,6 +29,8 @@ import {
Bookmark,
Wallet,
FileText,
FileCode,
Home,
GitBranch,
Code2,
BookOpen,
@@ -36,11 +47,13 @@ import {
Folder,
Database,
Layers,
LayoutGrid,
ExternalLink,
HardDrive,
Calendar,
GraduationCap,
AppWindow,
Lock,
type LucideIcon,
} from "lucide-react";
@@ -51,12 +64,21 @@ export const iconRegistry: Record<string, LucideIcon> = {
Wrench,
Sparkles,
Container,
Shield,
ShieldCheck,
Archive,
Activity,
Cloud,
Key,
KeyRound,
Radar,
Film,
Headphones,
Tv,
Video,
DownloadCloud,
Music,
HardDriveDownload,
Image,
RefreshCw,
Network,
@@ -66,6 +88,8 @@ export const iconRegistry: Record<string, LucideIcon> = {
Bookmark,
Wallet,
FileText,
FileCode,
Home,
GitBranch,
Code2,
BookOpen,
@@ -82,11 +106,13 @@ export const iconRegistry: Record<string, LucideIcon> = {
Folder,
Database,
Layers,
LayoutGrid,
ExternalLink,
HardDrive,
Calendar,
GraduationCap,
AppWindow,
Lock,
};
export const availableIconNames = Object.keys(iconRegistry);
@@ -53,7 +53,7 @@ export function TileEditorModal({
initialTile?.accentColor || "indigo"
);
const [openBehavior, setOpenBehavior] = useState<OpenBehavior>(
initialTile?.openBehavior || "workspace"
initialTile?.openBehavior || "external"
);
const handleSubmit = (e: React.FormEvent) => {
@@ -116,15 +116,16 @@ export function TileEditorModal({
</div>
<div className="space-y-1.5">
<label className="text-xs font-medium text-zinc-300">
Adresse URL complète
<label className="text-xs font-semibold text-amber-300">
Adresse URL complète (Sous-domaine HTTPS)
</label>
<Input
value={url}
onChange={(e) => setUrl(e.target.value)}
placeholder="https://cloud.lafabriquedesgenies.be"
placeholder="https://service.lafabriquedesgenies.be"
required
className="border-white/10 bg-zinc-900 font-mono text-xs text-zinc-100"
autoFocus={Boolean(initialTile)}
className="border-amber-500/40 bg-zinc-900 font-mono text-xs text-zinc-100 focus-visible:ring-amber-500/30"
/>
</div>
+331 -260
View File
@@ -1,295 +1,366 @@
import { DashboardData } from "@/types/dashboard";
export const initialDashboardData: DashboardData = {
version: "2.0.0",
version: "3.0.0",
dockTileIds: [
"nextcloud-files",
"nextcloud-office",
"google-docs",
"google-drive",
"gitea-server",
"nextcloud",
"dockge",
"gitea",
"jellyfin",
"open-webui",
"vaultwarden",
],
quickNote:
"Bienvenue sur Home OS. Utilisez F11 sur CachyOS (Chromium) ou ajoutez l'application à l'écran d'accueil sur votre Galaxy S20 FE.",
"Bienvenue sur Home OS (*.lafabriquedesgenies.be). Cliquez sur « Éditer » pour ajuster n'importe quelle URL ou sous-domaine à la volée.",
categories: [
{
id: "work",
name: "Bureautique & École",
subtitle: "Nextcloud Office, Google Docs, Drive & Cours",
icon: "Briefcase",
tabs: [
{
id: "cloud-office",
label: "Documents & Suites",
tiles: [
{
id: "nextcloud-files",
title: "Nextcloud Hub",
description: "Tous mes fichiers, dossiers personnels et synchronisation",
url: "https://cloud.lafabriquedesgenies.be",
icon: "Cloud",
size: "2x1",
badge: "Fichiers",
accentColor: "sky",
openBehavior: "workspace",
},
{
id: "nextcloud-office",
title: "Nextcloud Office",
description: "Traitement de texte (Word), Tableur (Excel) & Présentations",
url: "https://cloud.lafabriquedesgenies.be/apps/richdocuments",
icon: "FileSpreadsheet",
size: "2x1",
badge: "Word / Excel",
accentColor: "emerald",
openBehavior: "workspace",
},
{
id: "google-docs",
title: "Google Docs",
description: "Travaux d'école, rapports et rédaction collaborative",
url: "https://docs.google.com",
icon: "FileText",
size: "2x1",
badge: "École • Docs",
accentColor: "blue",
openBehavior: "popup",
},
{
id: "google-drive",
title: "Google Drive",
description: "Stockage scolaire, cours partagés et Google Sheets",
url: "https://drive.google.com",
icon: "HardDrive",
size: "1x1",
badge: "École • Drive",
accentColor: "amber",
openBehavior: "popup",
},
{
id: "nextcloud-notes",
title: "Notes & Aide-mémoire",
description: "Prise de notes rapide en Markdown sur Nextcloud",
url: "https://cloud.lafabriquedesgenies.be/apps/notes",
icon: "NotebookPen",
size: "1x1",
badge: "Markdown",
accentColor: "violet",
openBehavior: "workspace",
},
],
},
{
id: "dev-git",
label: "Code & Projets",
tiles: [
{
id: "gitea-server",
title: "Gitea — La Fabrique",
description: "Dépôts Git personnels (klemens/home) et CI",
url: "https://git.lafabriquedesgenies.be/klemens/home",
icon: "GitBranch",
size: "2x1",
badge: "Git Serveur",
accentColor: "emerald",
openBehavior: "workspace",
},
{
id: "github",
title: "GitHub",
description: "Projets open-source et dépôts externes",
url: "https://github.com",
icon: "Code2",
size: "1x1",
badge: "Externe",
accentColor: "slate",
openBehavior: "popup",
},
{
id: "excalidraw",
title: "Excalidraw",
description: "Schémas d'architecture, croquis et diagrammes de cours",
url: "https://excalidraw.com",
icon: "PenTool",
size: "1x1",
badge: "Tableau blanc",
accentColor: "indigo",
openBehavior: "workspace",
},
],
},
],
},
{
id: "server",
name: "Mon Serveur",
subtitle: "Infrastructure Docker (Core i3 / 8 Go RAM) & Réseau",
id: "infrastructure",
name: "Infrastructure & Serveur",
subtitle: "Administration système, conteneurs Docker, sauvegardes & réseau",
icon: "Server",
tabs: [
{
id: "containers",
label: "Conteneurs & Infra",
id: "infra-main",
label: "Services Serveur",
tiles: [
{
id: "dockge",
title: "Dockge",
description: "Gestionnaire de stacks Docker Compose ultra-léger",
url: "http://localhost:5001",
icon: "Container",
description: "Gestionnaire Docker Compose",
url: "https://dockge.lafabriquedesgenies.be",
icon: "Terminal",
size: "2x1",
badge: "Docker",
accentColor: "emerald",
openBehavior: "workspace",
openBehavior: "external",
},
{
id: "nginx-proxy",
title: "Nginx Reverse Proxy",
description: "Gestion des domaines *.lafabriquedesgenies.be et SSL",
url: "http://localhost:81",
icon: "ShieldCheck",
id: "nginx-proxy-manager",
title: "Nginx Proxy Manager",
description: "Reverse Proxy & Certificats SSL",
url: "https://proxy.lafabriquedesgenies.be",
icon: "Shield",
size: "1x1",
badge: "Proxy SSL",
accentColor: "blue",
openBehavior: "workspace",
openBehavior: "external",
},
{
id: "beszel",
title: "Monitoring i3",
description: "Charge CPU, mémoire 8 Go et stockage disque",
url: "http://localhost:8090",
id: "duplicati",
title: "Duplicati",
description: "Sauvegardes du serveur",
url: "https://backup.lafabriquedesgenies.be",
icon: "Archive",
size: "1x1",
badge: "Backup",
accentColor: "sky",
openBehavior: "external",
},
{
id: "speedtest",
title: "Speedtest",
description: "Surveillance débit réseau",
url: "https://speedtest.lafabriquedesgenies.be",
icon: "Activity",
size: "1x1",
badge: "Système",
badge: "Réseau",
accentColor: "cyan",
openBehavior: "external",
},
{
id: "adminer",
title: "Adminer",
description: "Gestionnaire bases SQL",
url: "https://db.lafabriquedesgenies.be",
icon: "Database",
size: "1x1",
badge: "SQL",
accentColor: "amber",
openBehavior: "external",
},
{
id: "adguard-home",
title: "AdGuard Home",
description: "Filtrage DNS & Antipub",
url: "https://adguard.lafabriquedesgenies.be",
icon: "ShieldCheck",
size: "1x1",
badge: "DNS",
accentColor: "teal",
openBehavior: "external",
},
{
id: "watchtower",
title: "Watchtower",
description: "Mises à jour automatiques",
url: "https://dockge.lafabriquedesgenies.be",
icon: "RefreshCw",
size: "1x1",
badge: "Daemon Auto",
accentColor: "violet",
openBehavior: "workspace",
openBehavior: "external",
},
],
},
],
},
{
id: "private-security",
name: "Privé & Sécurité",
subtitle: "Cloud personnel, mots de passe, documents, notes & domotique",
icon: "ShieldCheck",
tabs: [
{
id: "private-main",
label: "Espace Privé",
tiles: [
{
id: "nextcloud",
title: "Nextcloud",
description: "Fichiers & Drive Cloud",
url: "https://nc.lafabriquedesgenies.be",
icon: "Cloud",
size: "2x1",
badge: "Cloud",
accentColor: "sky",
openBehavior: "external",
},
{
id: "vaultwarden",
title: "Vaultwarden",
description: "Gestionnaire de mots de passe auto-hébergé",
url: "http://localhost:8222",
icon: "KeyRound",
description: "Gestionnaire de mots de passe",
url: "https://vault.lafabriquedesgenies.be",
icon: "Key",
size: "1x1",
badge: "Sécurité",
badge: "Coffre-fort",
accentColor: "amber",
openBehavior: "workspace",
openBehavior: "external",
},
{
id: "adguard",
title: "AdGuard DNS",
description: "Filtrage publicitaire et résolution DNS locale",
url: "http://localhost:3000",
icon: "Radar",
id: "paperless-ngx",
title: "Paperless-ngx",
description: "GED & Archivage intelligent",
url: "https://paperless.lafabriquedesgenies.be",
icon: "FileText",
size: "1x1",
badge: "DNS",
accentColor: "teal",
openBehavior: "workspace",
},
],
},
],
},
{
id: "private",
name: "Privé",
subtitle: "Quotidien, Mails, Agenda & Médias",
icon: "User",
tabs: [
{
id: "daily",
label: "Quotidien",
tiles: [
{
id: "nextcloud-calendar",
title: "Agenda & Tâches",
description: "Planning des cours, échéances et rendez-vous",
url: "https://cloud.lafabriquedesgenies.be/apps/calendar",
icon: "Calendar",
size: "2x1",
badge: "Planning",
accentColor: "sky",
openBehavior: "workspace",
badge: "GED",
accentColor: "emerald",
openBehavior: "external",
},
{
id: "webmail",
title: "Messagerie",
description: "Boîte mail personnelle et scolaire",
url: "https://mail.proton.me",
icon: "Mail",
id: "wikijs",
title: "Wiki.js",
description: "Base de connaissances & notes",
url: "https://wiki.lafabriquedesgenies.be",
icon: "BookOpen",
size: "1x1",
badge: "Emails",
accentColor: "violet",
openBehavior: "popup",
},
{
id: "freshrss",
title: "Lecteur RSS",
description: "Veille technologique et actualités sans pub",
url: "http://localhost:8085",
icon: "Rss",
size: "1x1",
badge: "Flux",
accentColor: "amber",
openBehavior: "workspace",
},
{
id: "jellyfin",
title: "Médiathèque",
description: "Films, séries et musique en streaming local",
url: "http://localhost:8096",
icon: "Film",
size: "2x1",
badge: "Média",
accentColor: "purple",
openBehavior: "workspace",
},
],
},
],
},
{
id: "tools",
name: "Outils",
subtitle: "Utilitaires rapides PDF, Réseau & Partage S20 FE <-> CachyOS",
icon: "Wrench",
tabs: [
{
id: "utilities",
label: "Boîte à outils",
tiles: [
{
id: "pairdrop",
title: "PairDrop (CachyOS ↔ S20 FE)",
description: "Transfert instantané de fichiers entre le PC et le Galaxy S20 FE",
url: "https://pairdrop.net",
icon: "Share2",
size: "2x1",
badge: "AirDrop Local",
accentColor: "cyan",
openBehavior: "workspace",
badge: "Wiki",
accentColor: "indigo",
openBehavior: "external",
},
{
id: "stirling-pdf",
title: "Stirling PDF",
description: "Convertir, fusionner et signer des PDF pour l'école",
url: "http://localhost:8083",
icon: "FileText",
description: "Boîte à outils PDF complète",
url: "https://pdf.lafabriquedesgenies.be",
icon: "FileCode",
size: "1x1",
badge: "PDF",
accentColor: "rose",
openBehavior: "workspace",
openBehavior: "external",
},
{
id: "it-tools",
title: "IT-Tools",
description: "Convertisseurs, générateurs de tokens, JSON et réseau",
url: "https://it-tools.tech",
icon: "Cpu",
id: "home-assistant",
title: "Home Assistant",
description: "Domotique de la maison",
url: "https://hass.lafabriquedesgenies.be",
icon: "Home",
size: "2x1",
badge: "Domotique",
accentColor: "cyan",
openBehavior: "external",
},
],
},
],
},
{
id: "multimedia",
name: "Multimédia & Médias",
subtitle: "Streaming vidéo, livres audio, musique & téléchargements",
icon: "Film",
tabs: [
{
id: "media-main",
label: "Streaming & Téléchargements",
tiles: [
{
id: "jellyfin",
title: "Jellyfin",
description: "Streaming Films & Séries",
url: "https://jellyfin.lafabriquedesgenies.be",
icon: "Film",
size: "2x1",
badge: "Streaming",
accentColor: "purple",
openBehavior: "external",
},
{
id: "audiobookshelf",
title: "Audiobookshelf",
description: "Livres audio & Podcasts",
url: "https://audiobookshelf.lafabriquedesgenies.be",
icon: "Headphones",
size: "1x1",
badge: "DevTools",
badge: "Audio",
accentColor: "amber",
openBehavior: "external",
},
{
id: "sonarr",
title: "Sonarr",
description: "Suivi & gestion des séries",
url: "https://sonarr.lafabriquedesgenies.be",
icon: "Tv",
size: "1x1",
badge: "Séries",
accentColor: "sky",
openBehavior: "external",
},
{
id: "radarr",
title: "Radarr",
description: "Suivi & gestion des films",
url: "https://radarr.lafabriquedesgenies.be",
icon: "Video",
size: "1x1",
badge: "Films",
accentColor: "amber",
openBehavior: "external",
},
{
id: "qbittorrent",
title: "qBittorrent",
description: "Téléchargements VPN (Gluetun)",
url: "https://torrent.lafabriquedesgenies.be",
icon: "DownloadCloud",
size: "1x1",
badge: "VPN Gluetun",
accentColor: "blue",
openBehavior: "external",
},
{
id: "deemix",
title: "Deemix",
description: "Téléchargement Musique",
url: "https://deemix.lafabriquedesgenies.be",
icon: "Music",
size: "1x1",
badge: "Musique",
accentColor: "pink",
openBehavior: "external",
},
{
id: "sabnzbd",
title: "SABnzbd",
description: "Client Usenet",
url: "https://sabnzbd.lafabriquedesgenies.be",
icon: "HardDriveDownload",
size: "1x1",
badge: "Usenet",
accentColor: "emerald",
openBehavior: "external",
},
],
},
],
},
{
id: "projects-work",
name: "Projets & Travail",
subtitle: "Forge Git, applications La Fabrique des Génies & outils métiers",
icon: "Briefcase",
tabs: [
{
id: "work-main",
label: "Applications & Projets",
tiles: [
{
id: "gitea",
title: "Gitea",
description: "Dépôts de code Git",
url: "https://git.lafabriquedesgenies.be",
icon: "GitBranch",
size: "2x1",
badge: "Git",
accentColor: "emerald",
openBehavior: "external",
},
{
id: "wordpress-lafabrique",
title: "WordPress La Fabrique",
description: "Site officiel La Fabrique",
url: "https://lafabriquedesgenies.be",
icon: "Globe",
size: "1x1",
badge: "Site Web",
accentColor: "blue",
openBehavior: "external",
},
{
id: "teacher-app",
title: "Teacher App",
description: "Plateforme pédagogique",
url: "https://teacher.lafabriquedesgenies.be",
icon: "GraduationCap",
size: "1x1",
badge: "Pédagogie",
accentColor: "indigo",
openBehavior: "external",
},
{
id: "creaprof",
title: "CreaProf",
description: "Outil création enseignant",
url: "https://creaprof.lafabriquedesgenies.be",
icon: "PenTool",
size: "1x1",
badge: "Création",
accentColor: "violet",
openBehavior: "external",
},
{
id: "ubicraft",
title: "Ubicraft",
description: "Application métier",
url: "https://ubicraft.lafabriquedesgenies.be",
icon: "LayoutGrid",
size: "1x1",
badge: "Métier",
accentColor: "cyan",
openBehavior: "external",
},
{
id: "lma-v2",
title: "LMA V2",
description: "Plateforme Last Minute Activities",
url: "https://lma.lafabriquedesgenies.be",
icon: "Briefcase",
size: "1x1",
badge: "LMA",
accentColor: "amber",
openBehavior: "external",
},
{
id: "coo-app",
title: "Coo App",
description: "Application de gestion",
url: "https://coo.lafabriquedesgenies.be",
icon: "Layers",
size: "1x1",
badge: "Gestion",
accentColor: "teal",
openBehavior: "workspace",
openBehavior: "external",
},
],
},
@@ -297,35 +368,35 @@ export const initialDashboardData: DashboardData = {
},
{
id: "ai",
name: "IA",
subtitle: "Assistant Gemini intégré & Outils de recherche",
name: "Intelligence Artificielle",
subtitle: "Modèles LLM auto-hébergés & Assistant Google Gemini intégré",
icon: "Sparkles",
tabs: [
{
id: "assistants",
label: "Studios IA",
id: "ai-main",
label: "Assistants IA",
tiles: [
{
id: "aistudio",
title: "Google AI Studio",
description: "Tests avancés Gemini et gestion de la clé API",
url: "https://aistudio.google.com",
icon: "Sparkles",
id: "open-webui",
title: "Open WebUI",
description: "Interface modèles LLM",
url: "https://ai.lafabriquedesgenies.be",
icon: "Bot",
size: "2x1",
badge: "Gemini",
accentColor: "blue",
openBehavior: "popup",
badge: "LLM",
accentColor: "emerald",
openBehavior: "external",
},
{
id: "wikipedia",
title: "Wikipédia FR",
description: "Encyclopédie libre consultable directement dans un onglet Home",
url: "https://fr.wikipedia.org",
icon: "BookOpen",
id: "google-gemini",
title: "Google Gemini",
description: "Assistant IA intégré",
url: "#gemini",
icon: "Sparkles",
size: "2x1",
badge: "Encyclopédie",
accentColor: "slate",
openBehavior: "workspace",
badge: "Omnibox IA",
accentColor: "blue",
openBehavior: "external",
},
],
},