homelab_automation/docs/database/database_schema_explorer.jsx
Bruno Charest 984d06a223
Some checks failed
Tests / Backend Tests (Python) (3.10) (push) Has been cancelled
Tests / Backend Tests (Python) (3.11) (push) Has been cancelled
Tests / Backend Tests (Python) (3.12) (push) Has been cancelled
Tests / Frontend Tests (JS) (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / All Tests Passed (push) Has been cancelled
feat: Implement comprehensive database schema with new models, CRUD operations, and documentation for host metrics, Docker management, and terminal sessions, while removing old test files.
2026-03-05 10:16:13 -05:00

656 lines
43 KiB
JavaScript

import React, { useState, useMemo } from "react";
const SCHEMA = {
alerts: {
group: "Notifications",
description: "Alertes système générées pour les utilisateurs",
columns: [
{ name: "id", type: "INTEGER", pk: true, fk: null, nullable: false, default: null },
{ name: "user_id", type: "INTEGER", pk: false, fk: "users.id", nullable: true, default: null },
{ name: "category", type: "VARCHAR(50)", pk: false, fk: null, nullable: false, default: null },
{ name: "level", type: "VARCHAR(20)", pk: false, fk: null, nullable: true, default: null },
{ name: "title", type: "VARCHAR(255)", pk: false, fk: null, nullable: true, default: null },
{ name: "message", type: "TEXT", pk: false, fk: null, nullable: false, default: null },
{ name: "source", type: "VARCHAR(50)", pk: false, fk: null, nullable: true, default: null },
{ name: "details", type: "JSON", pk: false, fk: null, nullable: true, default: null },
{ name: "read_at", type: "DATETIME", pk: false, fk: null, nullable: true, default: null },
{ name: "created_at", type: "DATETIME", pk: false, fk: null, nullable: false, default: "now()" },
]
},
app_settings: {
group: "Système",
description: "Paramètres globaux de l'application",
columns: [
{ name: "key", type: "VARCHAR(100)", pk: true, fk: null, nullable: false, default: null },
{ name: "value", type: "TEXT", pk: false, fk: null, nullable: true, default: null },
{ name: "created_at", type: "DATETIME", pk: false, fk: null, nullable: false, default: "now()" },
{ name: "updated_at", type: "DATETIME", pk: false, fk: null, nullable: false, default: "now()" },
]
},
bootstrap_status: {
group: "Hôtes",
description: "État du bootstrapping Ansible par hôte",
columns: [
{ name: "id", type: "INTEGER", pk: true, fk: null, nullable: false, default: null },
{ name: "host_id", type: "VARCHAR(50)", pk: false, fk: "hosts.id", nullable: false, default: null },
{ name: "status", type: "VARCHAR(50)", pk: false, fk: null, nullable: false, default: null },
{ name: "automation_user", type: "VARCHAR(50)", pk: false, fk: null, nullable: true, default: null },
{ name: "last_attempt", type: "DATETIME", pk: false, fk: null, nullable: true, default: null },
{ name: "error_message", type: "TEXT", pk: false, fk: null, nullable: true, default: null },
{ name: "created_at", type: "DATETIME", pk: false, fk: null, nullable: false, default: "now()" },
]
},
container_customizations: {
group: "Docker",
description: "Personnalisations visuelles de conteneurs par utilisateur",
columns: [
{ name: "id", type: "INTEGER", pk: true, fk: null, nullable: false, default: null },
{ name: "user_id", type: "INTEGER", pk: false, fk: "users.id", nullable: true, default: null },
{ name: "host_id", type: "VARCHAR(50)", pk: false, fk: "hosts.id", nullable: false, default: null },
{ name: "container_id", type: "VARCHAR(64)", pk: false, fk: null, nullable: false, default: null },
{ name: "icon_key", type: "VARCHAR(100)", pk: false, fk: null, nullable: true, default: null },
{ name: "icon_color", type: "VARCHAR(20)", pk: false, fk: null, nullable: true, default: null },
{ name: "bg_color", type: "VARCHAR(20)", pk: false, fk: null, nullable: true, default: null },
{ name: "created_at", type: "DATETIME", pk: false, fk: null, nullable: false, default: "now()" },
{ name: "updated_at", type: "DATETIME", pk: false, fk: null, nullable: false, default: "now()" },
]
},
docker_alerts: {
group: "Docker",
description: "Alertes liées aux conteneurs Docker",
columns: [
{ name: "id", type: "INTEGER", pk: true, fk: null, nullable: false, default: null },
{ name: "host_id", type: "VARCHAR(50)", pk: false, fk: "hosts.id", nullable: false, default: null },
{ name: "container_name", type: "VARCHAR(255)", pk: false, fk: null, nullable: false, default: null },
{ name: "severity", type: "VARCHAR(20)", pk: false, fk: null, nullable: false, default: "warning" },
{ name: "state", type: "VARCHAR(20)", pk: false, fk: null, nullable: false, default: "open" },
{ name: "message", type: "TEXT", pk: false, fk: null, nullable: true, default: null },
{ name: "opened_at", type: "DATETIME", pk: false, fk: null, nullable: false, default: "now()" },
{ name: "closed_at", type: "DATETIME", pk: false, fk: null, nullable: true, default: null },
{ name: "acknowledged_at", type: "DATETIME", pk: false, fk: null, nullable: true, default: null },
{ name: "acknowledged_by", type: "VARCHAR(100)", pk: false, fk: null, nullable: true, default: null },
{ name: "last_notified_at", type: "DATETIME", pk: false, fk: null, nullable: true, default: null },
]
},
docker_containers: {
group: "Docker",
description: "Inventaire des conteneurs Docker par hôte",
columns: [
{ name: "id", type: "INTEGER", pk: true, fk: null, nullable: false, default: null },
{ name: "host_id", type: "VARCHAR(50)", pk: false, fk: "hosts.id", nullable: false, default: null },
{ name: "container_id", type: "VARCHAR(64)", pk: false, fk: null, nullable: false, default: null },
{ name: "name", type: "VARCHAR(255)", pk: false, fk: null, nullable: false, default: null },
{ name: "image", type: "VARCHAR(255)", pk: false, fk: null, nullable: true, default: null },
{ name: "state", type: "VARCHAR(20)", pk: false, fk: null, nullable: false, default: "unknown" },
{ name: "status", type: "VARCHAR(255)", pk: false, fk: null, nullable: true, default: null },
{ name: "health", type: "VARCHAR(20)", pk: false, fk: null, nullable: true, default: null },
{ name: "created_at", type: "DATETIME", pk: false, fk: null, nullable: true, default: null },
{ name: "ports", type: "JSON", pk: false, fk: null, nullable: true, default: null },
{ name: "labels", type: "JSON", pk: false, fk: null, nullable: true, default: null },
{ name: "compose_project", type: "VARCHAR(255)", pk: false, fk: null, nullable: true, default: null },
{ name: "last_update_at", type: "DATETIME", pk: false, fk: null, nullable: false, default: "now()" },
]
},
docker_images: {
group: "Docker",
description: "Images Docker disponibles sur les hôtes",
columns: [
{ name: "id", type: "INTEGER", pk: true, fk: null, nullable: false, default: null },
{ name: "host_id", type: "VARCHAR(50)", pk: false, fk: "hosts.id", nullable: false, default: null },
{ name: "image_id", type: "VARCHAR(64)", pk: false, fk: null, nullable: false, default: null },
{ name: "repo_tags", type: "JSON", pk: false, fk: null, nullable: true, default: null },
{ name: "size", type: "BIGINT", pk: false, fk: null, nullable: true, default: null },
{ name: "created", type: "DATETIME", pk: false, fk: null, nullable: true, default: null },
{ name: "last_update_at", type: "DATETIME", pk: false, fk: null, nullable: false, default: "now()" },
]
},
docker_volumes: {
group: "Docker",
description: "Volumes Docker par hôte",
columns: [
{ name: "id", type: "INTEGER", pk: true, fk: null, nullable: false, default: null },
{ name: "host_id", type: "VARCHAR(50)", pk: false, fk: "hosts.id", nullable: false, default: null },
{ name: "name", type: "VARCHAR(255)", pk: false, fk: null, nullable: false, default: null },
{ name: "driver", type: "VARCHAR(50)", pk: false, fk: null, nullable: true, default: null },
{ name: "mountpoint", type: "TEXT", pk: false, fk: null, nullable: true, default: null },
{ name: "scope", type: "VARCHAR(20)", pk: false, fk: null, nullable: true, default: null },
{ name: "last_update_at", type: "DATETIME", pk: false, fk: null, nullable: false, default: "now()" },
]
},
favorite_containers: {
group: "Utilisateurs",
description: "Conteneurs favoris par utilisateur et groupe",
columns: [
{ name: "id", type: "INTEGER", pk: true, fk: null, nullable: false, default: null },
{ name: "user_id", type: "INTEGER", pk: false, fk: "users.id", nullable: true, default: null },
{ name: "docker_container_id", type: "INTEGER", pk: false, fk: "docker_containers.id", nullable: false, default: null },
{ name: "group_id", type: "INTEGER", pk: false, fk: "favorite_groups.id", nullable: true, default: null },
{ name: "created_at", type: "DATETIME", pk: false, fk: null, nullable: false, default: "now()" },
]
},
favorite_groups: {
group: "Utilisateurs",
description: "Groupes de favoris définis par l'utilisateur",
columns: [
{ name: "id", type: "INTEGER", pk: true, fk: null, nullable: false, default: null },
{ name: "user_id", type: "INTEGER", pk: false, fk: "users.id", nullable: true, default: null },
{ name: "name", type: "VARCHAR(100)", pk: false, fk: null, nullable: false, default: null },
{ name: "sort_order", type: "INTEGER", pk: false, fk: null, nullable: false, default: "0" },
{ name: "color", type: "VARCHAR(20)", pk: false, fk: null, nullable: true, default: null },
{ name: "icon_key", type: "VARCHAR(100)", pk: false, fk: null, nullable: true, default: null },
{ name: "created_at", type: "DATETIME", pk: false, fk: null, nullable: false, default: "now()" },
{ name: "updated_at", type: "DATETIME", pk: false, fk: null, nullable: false, default: "now()" },
]
},
host_metrics: {
group: "Hôtes",
description: "Métriques système collectées par hôte (CPU, RAM, disque, réseau…)",
columns: [
{ name: "id", type: "INTEGER", pk: true, fk: null, nullable: false, default: null },
{ name: "host_id", type: "VARCHAR(50)", pk: false, fk: "hosts.id", nullable: false, default: null },
{ name: "metric_type", type: "VARCHAR(50)", pk: false, fk: null, nullable: false, default: null },
{ name: "cpu_count", type: "INTEGER", pk: false, fk: null, nullable: true, default: null },
{ name: "cpu_model", type: "VARCHAR(200)", pk: false, fk: null, nullable: true, default: null },
{ name: "cpu_cores", type: "INTEGER", pk: false, fk: null, nullable: true, default: null },
{ name: "cpu_threads", type: "INTEGER", pk: false, fk: null, nullable: true, default: null },
{ name: "cpu_threads_per_core", type: "INTEGER", pk: false, fk: null, nullable: true, default: null },
{ name: "cpu_sockets", type: "INTEGER", pk: false, fk: null, nullable: true, default: null },
{ name: "cpu_mhz", type: "FLOAT", pk: false, fk: null, nullable: true, default: null },
{ name: "cpu_max_mhz", type: "FLOAT", pk: false, fk: null, nullable: true, default: null },
{ name: "cpu_min_mhz", type: "FLOAT", pk: false, fk: null, nullable: true, default: null },
{ name: "cpu_load_1m", type: "FLOAT", pk: false, fk: null, nullable: true, default: null },
{ name: "cpu_load_5m", type: "FLOAT", pk: false, fk: null, nullable: true, default: null },
{ name: "cpu_load_15m", type: "FLOAT", pk: false, fk: null, nullable: true, default: null },
{ name: "cpu_usage_percent", type: "FLOAT", pk: false, fk: null, nullable: true, default: null },
{ name: "cpu_temperature", type: "FLOAT", pk: false, fk: null, nullable: true, default: null },
{ name: "memory_total_mb", type: "INTEGER", pk: false, fk: null, nullable: true, default: null },
{ name: "memory_used_mb", type: "INTEGER", pk: false, fk: null, nullable: true, default: null },
{ name: "memory_free_mb", type: "INTEGER", pk: false, fk: null, nullable: true, default: null },
{ name: "memory_usage_percent", type: "FLOAT", pk: false, fk: null, nullable: true, default: null },
{ name: "swap_total_mb", type: "INTEGER", pk: false, fk: null, nullable: true, default: null },
{ name: "swap_used_mb", type: "INTEGER", pk: false, fk: null, nullable: true, default: null },
{ name: "swap_usage_percent", type: "FLOAT", pk: false, fk: null, nullable: true, default: null },
{ name: "disk_info", type: "JSON", pk: false, fk: null, nullable: true, default: null },
{ name: "disk_devices", type: "JSON", pk: false, fk: null, nullable: false, default: null },
{ name: "disk_root_total_gb", type: "FLOAT", pk: false, fk: null, nullable: true, default: null },
{ name: "disk_root_used_gb", type: "FLOAT", pk: false, fk: null, nullable: true, default: null },
{ name: "disk_root_usage_percent", type: "FLOAT", pk: false, fk: null, nullable: true, default: null },
{ name: "lvm_info", type: "JSON", pk: false, fk: null, nullable: true, default: null },
{ name: "zfs_info", type: "JSON", pk: false, fk: null, nullable: true, default: null },
{ name: "storage_details", type: "JSON", pk: false, fk: null, nullable: true, default: null },
{ name: "os_name", type: "VARCHAR(100)", pk: false, fk: null, nullable: true, default: null },
{ name: "os_version", type: "VARCHAR(100)", pk: false, fk: null, nullable: true, default: null },
{ name: "kernel_version", type: "VARCHAR(100)", pk: false, fk: null, nullable: true, default: null },
{ name: "hostname", type: "VARCHAR(200)", pk: false, fk: null, nullable: true, default: null },
{ name: "uptime_seconds", type: "INTEGER", pk: false, fk: null, nullable: true, default: null },
{ name: "uptime_human", type: "VARCHAR(100)", pk: false, fk: null, nullable: true, default: null },
{ name: "network_info", type: "JSON", pk: false, fk: null, nullable: true, default: null },
{ name: "raw_data", type: "JSON", pk: false, fk: null, nullable: true, default: null },
{ name: "collection_source", type: "VARCHAR(100)", pk: false, fk: null, nullable: true, default: null },
{ name: "collection_duration_ms", type: "INTEGER", pk: false, fk: null, nullable: true, default: null },
{ name: "error_message", type: "TEXT", pk: false, fk: null, nullable: true, default: null },
{ name: "collected_at", type: "DATETIME", pk: false, fk: null, nullable: false, default: "now()" },
{ name: "created_at", type: "DATETIME", pk: false, fk: null, nullable: false, default: "now()" },
]
},
hosts: {
group: "Hôtes",
description: "Inventaire des serveurs/hôtes gérés",
columns: [
{ name: "id", type: "VARCHAR(50)", pk: true, fk: null, nullable: false, default: null },
{ name: "name", type: "VARCHAR(255)", pk: false, fk: null, nullable: false, default: null },
{ name: "ip_address", type: "VARCHAR(50)", pk: false, fk: null, nullable: false, default: null },
{ name: "status", type: "VARCHAR(50)", pk: false, fk: null, nullable: false, default: "'unknown'" },
{ name: "ansible_group", type: "VARCHAR(50)", pk: false, fk: null, nullable: true, default: null },
{ name: "last_seen", type: "DATETIME", pk: false, fk: null, nullable: true, default: null },
{ name: "reachable", type: "BOOLEAN", pk: false, fk: null, nullable: false, default: "0" },
{ name: "created_at", type: "DATETIME", pk: false, fk: null, nullable: false, default: "now()" },
{ name: "updated_at", type: "DATETIME", pk: false, fk: null, nullable: false, default: "now()" },
{ name: "deleted_at", type: "DATETIME", pk: false, fk: null, nullable: true, default: null },
{ name: "docker_enabled", type: "BOOLEAN", pk: false, fk: null, nullable: false, default: "0" },
{ name: "docker_version", type: "VARCHAR(50)", pk: false, fk: null, nullable: true, default: null },
{ name: "docker_status", type: "VARCHAR(20)", pk: false, fk: null, nullable: true, default: null },
{ name: "docker_last_collect_at", type: "DATETIME", pk: false, fk: null, nullable: true, default: null },
]
},
logs: {
group: "Système",
description: "Journal applicatif général",
columns: [
{ name: "id", type: "INTEGER", pk: true, fk: null, nullable: false, default: null },
{ name: "level", type: "VARCHAR(50)", pk: false, fk: null, nullable: false, default: null },
{ name: "source", type: "VARCHAR(255)", pk: false, fk: null, nullable: true, default: null },
{ name: "message", type: "TEXT", pk: false, fk: null, nullable: false, default: null },
{ name: "details", type: "JSON", pk: false, fk: null, nullable: true, default: null },
{ name: "host_id", type: "VARCHAR(50)", pk: false, fk: null, nullable: true, default: null },
{ name: "task_id", type: "VARCHAR(50)", pk: false, fk: null, nullable: true, default: null },
{ name: "schedule_id", type: "VARCHAR(50)", pk: false, fk: null, nullable: true, default: null },
{ name: "created_at", type: "DATETIME", pk: false, fk: null, nullable: false, default: "now()" },
]
},
playbook_lint_results: {
group: "Ansible",
description: "Résultats d'analyse de qualité des playbooks Ansible",
columns: [
{ name: "id", type: "INTEGER", pk: true, fk: null, nullable: false, default: null },
{ name: "filename", type: "VARCHAR(255)", pk: false, fk: null, nullable: false, default: null },
{ name: "quality_score", type: "INTEGER", pk: false, fk: null, nullable: false, default: "100" },
{ name: "total_issues", type: "INTEGER", pk: false, fk: null, nullable: false, default: "0" },
{ name: "errors_count", type: "INTEGER", pk: false, fk: null, nullable: false, default: "0" },
{ name: "warnings_count", type: "INTEGER", pk: false, fk: null, nullable: false, default: "0" },
{ name: "execution_time_ms", type: "INTEGER", pk: false, fk: null, nullable: false, default: "0" },
{ name: "issues_json", type: "TEXT", pk: false, fk: null, nullable: true, default: null },
{ name: "raw_output", type: "TEXT", pk: false, fk: null, nullable: true, default: null },
{ name: "created_at", type: "DATETIME", pk: false, fk: null, nullable: false, default: "auto" },
{ name: "updated_at", type: "DATETIME", pk: false, fk: null, nullable: false, default: "auto" },
]
},
schedule_runs: {
group: "Ansible",
description: "Historique des exécutions de planifications",
columns: [
{ name: "id", type: "INTEGER", pk: true, fk: null, nullable: false, default: null },
{ name: "schedule_id", type: "VARCHAR(50)", pk: false, fk: "schedules.id", nullable: false, default: null },
{ name: "task_id", type: "VARCHAR(50)", pk: false, fk: "tasks.id", nullable: true, default: null },
{ name: "status", type: "VARCHAR(50)", pk: false, fk: null, nullable: false, default: null },
{ name: "started_at", type: "DATETIME", pk: false, fk: null, nullable: false, default: null },
{ name: "completed_at", type: "DATETIME", pk: false, fk: null, nullable: true, default: null },
{ name: "duration", type: "FLOAT", pk: false, fk: null, nullable: true, default: null },
{ name: "hosts_impacted", type: "INTEGER", pk: false, fk: null, nullable: true, default: "0" },
{ name: "error_message", type: "TEXT", pk: false, fk: null, nullable: true, default: null },
{ name: "output", type: "TEXT", pk: false, fk: null, nullable: true, default: null },
{ name: "created_at", type: "DATETIME", pk: false, fk: null, nullable: false, default: "now()" },
]
},
schedules: {
group: "Ansible",
description: "Planifications d'exécution de playbooks",
columns: [
{ name: "id", type: "VARCHAR(50)", pk: true, fk: null, nullable: false, default: null },
{ name: "name", type: "VARCHAR(255)", pk: false, fk: null, nullable: false, default: null },
{ name: "description", type: "TEXT", pk: false, fk: null, nullable: true, default: null },
{ name: "playbook", type: "VARCHAR(255)", pk: false, fk: null, nullable: false, default: null },
{ name: "target_type", type: "VARCHAR(50)", pk: false, fk: null, nullable: true, default: "group" },
{ name: "target", type: "VARCHAR(255)", pk: false, fk: null, nullable: false, default: null },
{ name: "extra_vars", type: "JSON", pk: false, fk: null, nullable: true, default: null },
{ name: "schedule_type", type: "VARCHAR(50)", pk: false, fk: null, nullable: false, default: null },
{ name: "schedule_time", type: "DATETIME", pk: false, fk: null, nullable: true, default: null },
{ name: "recurrence_type", type: "VARCHAR(50)", pk: false, fk: null, nullable: true, default: null },
{ name: "recurrence_time", type: "VARCHAR(50)", pk: false, fk: null, nullable: true, default: null },
{ name: "recurrence_days", type: "TEXT", pk: false, fk: null, nullable: true, default: null },
{ name: "cron_expression", type: "VARCHAR(100)", pk: false, fk: null, nullable: true, default: null },
{ name: "timezone", type: "VARCHAR(100)", pk: false, fk: null, nullable: true, default: "America/Montreal" },
{ name: "start_at", type: "DATETIME", pk: false, fk: null, nullable: true, default: null },
{ name: "end_at", type: "DATETIME", pk: false, fk: null, nullable: true, default: null },
{ name: "enabled", type: "BOOLEAN", pk: false, fk: null, nullable: false, default: "True" },
{ name: "tags", type: "TEXT", pk: false, fk: null, nullable: true, default: null },
{ name: "next_run", type: "DATETIME", pk: false, fk: null, nullable: true, default: null },
{ name: "last_run", type: "DATETIME", pk: false, fk: null, nullable: true, default: null },
{ name: "last_status", type: "VARCHAR(50)", pk: false, fk: null, nullable: true, default: "never" },
{ name: "retry_on_failure", type: "INTEGER", pk: false, fk: null, nullable: true, default: "0" },
{ name: "timeout", type: "INTEGER", pk: false, fk: null, nullable: true, default: "3600" },
{ name: "notification_type", type: "VARCHAR(50)", pk: false, fk: null, nullable: true, default: "all" },
{ name: "run_count", type: "INTEGER", pk: false, fk: null, nullable: true, default: "0" },
{ name: "success_count", type: "INTEGER", pk: false, fk: null, nullable: true, default: "0" },
{ name: "failure_count", type: "INTEGER", pk: false, fk: null, nullable: true, default: "0" },
{ name: "created_at", type: "DATETIME", pk: false, fk: null, nullable: false, default: "now()" },
{ name: "updated_at", type: "DATETIME", pk: false, fk: null, nullable: false, default: "now()" },
{ name: "deleted_at", type: "DATETIME", pk: false, fk: null, nullable: true, default: null },
]
},
tasks: {
group: "Ansible",
description: "Tâches Ansible déclenchées manuellement ou via planification",
columns: [
{ name: "id", type: "VARCHAR(50)", pk: true, fk: null, nullable: false, default: null },
{ name: "action", type: "VARCHAR(100)", pk: false, fk: null, nullable: false, default: null },
{ name: "target", type: "VARCHAR(255)", pk: false, fk: null, nullable: false, default: null },
{ name: "status", type: "VARCHAR(50)", pk: false, fk: null, nullable: false, default: "'pending'" },
{ name: "playbook", type: "VARCHAR(255)", pk: false, fk: null, nullable: true, default: null },
{ name: "started_at", type: "DATETIME", pk: false, fk: null, nullable: true, default: null },
{ name: "completed_at", type: "DATETIME", pk: false, fk: null, nullable: true, default: null },
{ name: "error_message", type: "TEXT", pk: false, fk: null, nullable: true, default: null },
{ name: "result_data", type: "JSON", pk: false, fk: null, nullable: true, default: null },
{ name: "created_at", type: "DATETIME", pk: false, fk: null, nullable: false, default: "now()" },
]
},
terminal_command_logs: {
group: "Terminal",
description: "Journal des commandes exécutées dans le terminal",
columns: [
{ name: "id", type: "INTEGER", pk: true, fk: null, nullable: false, default: null },
{ name: "created_at", type: "DATETIME", pk: false, fk: null, nullable: false, default: "now()" },
{ name: "host_id", type: "VARCHAR(50)", pk: false, fk: "hosts.id", nullable: false, default: null },
{ name: "user_id", type: "INTEGER", pk: false, fk: "users.id", nullable: true, default: null },
{ name: "terminal_session_id", type: "VARCHAR(64)", pk: false, fk: null, nullable: true, default: null },
{ name: "command", type: "TEXT", pk: false, fk: null, nullable: false, default: null },
{ name: "command_hash", type: "VARCHAR(64)", pk: false, fk: null, nullable: false, default: null },
{ name: "source", type: "VARCHAR(20)", pk: false, fk: null, nullable: false, default: "'terminal'" },
{ name: "is_pinned", type: "BOOLEAN", pk: false, fk: null, nullable: false, default: "0" },
{ name: "is_blocked", type: "BOOLEAN", pk: false, fk: null, nullable: false, default: "0" },
{ name: "blocked_reason", type: "VARCHAR(255)", pk: false, fk: null, nullable: true, default: null },
{ name: "username", type: "VARCHAR(100)", pk: false, fk: null, nullable: true, default: null },
{ name: "host_name", type: "VARCHAR(100)", pk: false, fk: null, nullable: true, default: null },
]
},
terminal_sessions: {
group: "Terminal",
description: "Sessions de terminal actives ou fermées",
columns: [
{ name: "id", type: "VARCHAR(50)", pk: true, fk: null, nullable: false, default: null },
{ name: "host_id", type: "VARCHAR(50)", pk: false, fk: null, nullable: false, default: null },
{ name: "host_name", type: "VARCHAR(255)", pk: false, fk: null, nullable: false, default: null },
{ name: "host_ip", type: "VARCHAR(50)", pk: false, fk: null, nullable: false, default: null },
{ name: "user_id", type: "INTEGER", pk: false, fk: null, nullable: true, default: null },
{ name: "username", type: "VARCHAR(50)", pk: false, fk: null, nullable: true, default: null },
{ name: "token_hash", type: "VARCHAR(128)", pk: false, fk: null, nullable: false, default: null },
{ name: "ttyd_port", type: "INTEGER", pk: false, fk: null, nullable: false, default: null },
{ name: "ttyd_pid", type: "INTEGER", pk: false, fk: null, nullable: true, default: null },
{ name: "mode", type: "VARCHAR(20)", pk: false, fk: null, nullable: false, default: "'embedded'" },
{ name: "status", type: "VARCHAR(20)", pk: false, fk: null, nullable: false, default: "'active'" },
{ name: "reason_closed", type: "VARCHAR(30)", pk: false, fk: null, nullable: true, default: null },
{ name: "created_at", type: "DATETIME", pk: false, fk: null, nullable: false, default: "now()" },
{ name: "last_seen_at", type: "DATETIME", pk: false, fk: null, nullable: false, default: "now()" },
{ name: "expires_at", type: "DATETIME", pk: false, fk: null, nullable: false, default: null },
{ name: "closed_at", type: "DATETIME", pk: false, fk: null, nullable: true, default: null },
]
},
users: {
group: "Utilisateurs",
description: "Comptes utilisateurs de l'application",
columns: [
{ name: "id", type: "INTEGER", pk: true, fk: null, nullable: false, default: null },
{ name: "username", type: "VARCHAR(50)", pk: false, fk: null, nullable: false, default: null },
{ name: "email", type: "VARCHAR(255)", pk: false, fk: null, nullable: true, default: null },
{ name: "hashed_password", type: "VARCHAR(255)", pk: false, fk: null, nullable: false, default: null },
{ name: "role", type: "VARCHAR(20)", pk: false, fk: null, nullable: false, default: "'admin'" },
{ name: "is_active", type: "BOOLEAN", pk: false, fk: null, nullable: false, default: "1" },
{ name: "is_superuser", type: "BOOLEAN", pk: false, fk: null, nullable: false, default: "0" },
{ name: "display_name", type: "VARCHAR(100)", pk: false, fk: null, nullable: true, default: null },
{ name: "created_at", type: "DATETIME", pk: false, fk: null, nullable: false, default: "now()" },
{ name: "updated_at", type: "DATETIME", pk: false, fk: null, nullable: false, default: "now()" },
{ name: "last_login", type: "DATETIME", pk: false, fk: null, nullable: true, default: null },
{ name: "password_changed_at", type: "DATETIME", pk: false, fk: null, nullable: true, default: null },
{ name: "deleted_at", type: "DATETIME", pk: false, fk: null, nullable: true, default: null },
]
},
};
const GROUP_COLORS = {
"Hôtes": { bg: "#0f172a", accent: "#38bdf8", badge: "#0369a1", badgeText: "#e0f2fe" },
"Docker": { bg: "#0a1628", accent: "#22d3ee", badge: "#0e7490", badgeText: "#cffafe" },
"Ansible": { bg: "#1a0a2e", accent: "#a78bfa", badge: "#6d28d9", badgeText: "#ede9fe" },
"Utilisateurs": { bg: "#0f1f0f", accent: "#4ade80", badge: "#15803d", badgeText: "#dcfce7" },
"Terminal": { bg: "#1a1400", accent: "#fbbf24", badge: "#b45309", badgeText: "#fef3c7" },
"Notifications": { bg: "#1a0a0a", accent: "#f87171", badge: "#b91c1c", badgeText: "#fee2e2" },
"Système": { bg: "#111827", accent: "#94a3b8", badge: "#475569", badgeText: "#f1f5f9" },
};
const TYPE_COLOR = (t) => {
if (t.startsWith("VARCHAR") || t === "TEXT") return "#7dd3fc";
if (t === "INTEGER" || t === "BIGINT" || t === "FLOAT") return "#86efac";
if (t === "DATETIME") return "#fcd34d";
if (t === "BOOLEAN") return "#f9a8d4";
if (t === "JSON") return "#c4b5fd";
return "#e2e8f0";
};
const GROUPS = [...new Set(Object.values(SCHEMA).map(t => t.group))];
export default function App() {
const [search, setSearch] = useState("");
const [activeGroup, setActiveGroup] = useState("Tous");
const [selectedTable, setSelectedTable] = useState(null);
const [colSearch, setColSearch] = useState("");
const tableNames = useMemo(() => Object.keys(SCHEMA).sort(), []);
const filtered = useMemo(() => {
return tableNames.filter(name => {
const matchGroup = activeGroup === "Tous" || SCHEMA[name].group === activeGroup;
const matchSearch = name.toLowerCase().includes(search.toLowerCase()) ||
SCHEMA[name].description.toLowerCase().includes(search.toLowerCase());
return matchGroup && matchSearch;
});
}, [tableNames, search, activeGroup]);
const tableData = selectedTable ? SCHEMA[selectedTable] : null;
const groupColor = tableData ? GROUP_COLORS[tableData.group] : null;
const filteredCols = useMemo(() => {
if (!tableData) return [];
return tableData.columns.filter(c =>
c.name.toLowerCase().includes(colSearch.toLowerCase()) ||
c.type.toLowerCase().includes(colSearch.toLowerCase())
);
}, [tableData, colSearch]);
const grouped = useMemo(() => {
const g = {};
GROUPS.forEach(grp => {
const tables = filtered.filter(n => SCHEMA[n].group === grp);
if (tables.length) g[grp] = tables;
});
return g;
}, [filtered]);
return (
<div style={{ display: "flex", height: "100vh", background: "#0d1117", fontFamily: "'JetBrains Mono', 'Fira Code', monospace", color: "#c9d1d9", overflow: "hidden" }}>
<style>{`
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;600;700&display=swap');
::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-track { background: #161b22; }
::-webkit-scrollbar-thumb { background: #30363d; border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: #484f58; }
.table-item { transition: background 0.15s, border-color 0.15s; }
.table-item:hover { background: #21262d !important; }
.col-row:hover { background: #1c2128 !important; }
.group-btn { transition: all 0.15s; }
.group-btn:hover { filter: brightness(1.2); }
`}</style>
{/* SIDEBAR */}
<div style={{ width: 280, background: "#161b22", borderRight: "1px solid #21262d", display: "flex", flexDirection: "column", flexShrink: 0 }}>
{/* Header */}
<div style={{ padding: "20px 16px 16px", borderBottom: "1px solid #21262d" }}>
<div style={{ fontSize: 11, color: "#58a6ff", fontWeight: 700, letterSpacing: 3, marginBottom: 6, textTransform: "uppercase" }}>Schema Explorer</div>
<div style={{ fontSize: 12, color: "#484f58" }}>{tableNames.length} tables · SQLAlchemy</div>
</div>
{/* Search */}
<div style={{ padding: "12px 12px 8px" }}>
<div style={{ position: "relative" }}>
<span style={{ position: "absolute", left: 10, top: "50%", transform: "translateY(-50%)", color: "#484f58", fontSize: 13 }}></span>
<input
value={search}
onChange={e => setSearch(e.target.value)}
placeholder="Rechercher une table…"
style={{ width: "100%", background: "#0d1117", border: "1px solid #30363d", borderRadius: 6, padding: "7px 10px 7px 30px", color: "#c9d1d9", fontSize: 12, outline: "none", boxSizing: "border-box" }}
/>
</div>
</div>
{/* Groups filter */}
<div style={{ padding: "0 12px 10px", display: "flex", flexWrap: "wrap", gap: 5 }}>
{["Tous", ...GROUPS].map(g => {
const isActive = activeGroup === g;
const gc = GROUP_COLORS[g];
return (
<button key={g} className="group-btn" onClick={() => setActiveGroup(g)} style={{
fontSize: 10, padding: "3px 8px", borderRadius: 4, cursor: "pointer", border: "none", fontFamily: "inherit", fontWeight: 600,
background: isActive ? (gc ? gc.badge : "#21262d") : "#0d1117",
color: isActive ? (gc ? gc.badgeText : "#c9d1d9") : "#58616a",
letterSpacing: 0.5
}}>
{g}
</button>
);
})}
</div>
{/* Table list */}
<div style={{ flex: 1, overflowY: "auto", padding: "0 8px 16px" }}>
{Object.keys(grouped).map(grp => {
const gc = GROUP_COLORS[grp];
return (
<div key={grp} style={{ marginBottom: 8 }}>
<div style={{ fontSize: 10, color: gc.accent, fontWeight: 700, letterSpacing: 2, textTransform: "uppercase", padding: "8px 8px 4px" }}>
{grp}
</div>
{grouped[grp].map(name => {
const isSelected = selectedTable === name;
return (
<div key={name} className="table-item" onClick={() => { setSelectedTable(name); setColSearch(""); }} style={{
padding: "8px 10px", borderRadius: 6, cursor: "pointer", marginBottom: 2,
background: isSelected ? "#1f2d3d" : "transparent",
border: isSelected ? `1px solid ${gc.accent}44` : "1px solid transparent",
}}>
<div style={{ display: "flex", alignItems: "center", gap: 7 }}>
<span style={{ color: isSelected ? gc.accent : "#484f58", fontSize: 12 }}></span>
<span style={{ fontSize: 12, color: isSelected ? "#e6edf3" : "#8b949e", fontWeight: isSelected ? 600 : 400 }}>{name}</span>
<span style={{ marginLeft: "auto", fontSize: 10, color: "#484f58" }}>{SCHEMA[name].columns.length}</span>
</div>
</div>
);
})}
</div>
);
})}
{filtered.length === 0 && (
<div style={{ padding: 20, color: "#484f58", fontSize: 12, textAlign: "center" }}>Aucune table trouvée</div>
)}
</div>
</div>
{/* MAIN CONTENT */}
<div style={{ flex: 1, display: "flex", flexDirection: "column", overflow: "hidden" }}>
{selectedTable && tableData ? (
<>
{/* Table header */}
<div style={{ padding: "20px 28px 16px", borderBottom: "1px solid #21262d", background: "#161b22" }}>
<div style={{ display: "flex", alignItems: "center", gap: 12, marginBottom: 6 }}>
<span style={{ fontSize: 22, color: groupColor.accent }}></span>
<h1 style={{ margin: 0, fontSize: 22, fontWeight: 700, color: "#e6edf3", letterSpacing: 1 }}>{selectedTable}</h1>
<span style={{ fontSize: 11, padding: "2px 10px", borderRadius: 4, fontWeight: 700, background: groupColor.badge, color: groupColor.badgeText }}>
{tableData.group}
</span>
</div>
<p style={{ margin: 0, fontSize: 13, color: "#8b949e" }}>{tableData.description}</p>
{/* Stats row */}
<div style={{ display: "flex", gap: 20, marginTop: 12 }}>
{[
{ label: "Colonnes", val: tableData.columns.length },
{ label: "Clés primaires", val: tableData.columns.filter(c => c.pk).length },
{ label: "Clés étrangères", val: tableData.columns.filter(c => c.fk).length },
{ label: "Nullable", val: tableData.columns.filter(c => c.nullable).length },
].map(s => (
<div key={s.label} style={{ background: "#0d1117", border: "1px solid #21262d", borderRadius: 8, padding: "6px 14px", textAlign: "center" }}>
<div style={{ fontSize: 18, fontWeight: 700, color: groupColor.accent }}>{s.val}</div>
<div style={{ fontSize: 10, color: "#484f58", letterSpacing: 1 }}>{s.label}</div>
</div>
))}
</div>
</div>
{/* Column search */}
<div style={{ padding: "12px 28px 0", background: "#0d1117" }}>
<div style={{ position: "relative", maxWidth: 340 }}>
<span style={{ position: "absolute", left: 10, top: "50%", transform: "translateY(-50%)", color: "#484f58", fontSize: 13 }}></span>
<input
value={colSearch}
onChange={e => setColSearch(e.target.value)}
placeholder="Filtrer les colonnes…"
style={{ width: "100%", background: "#161b22", border: "1px solid #30363d", borderRadius: 6, padding: "7px 10px 7px 30px", color: "#c9d1d9", fontSize: 12, outline: "none", boxSizing: "border-box" }}
/>
</div>
</div>
{/* Columns table */}
<div style={{ flex: 1, overflowY: "auto", padding: "12px 28px 28px", background: "#0d1117" }}>
<table style={{ width: "100%", borderCollapse: "collapse", fontSize: 12 }}>
<thead>
<tr style={{ borderBottom: "2px solid #21262d" }}>
{["Colonne", "Type", "Flags", "Défaut"].map(h => (
<th key={h} style={{ textAlign: "left", padding: "8px 12px", color: "#484f58", fontWeight: 700, fontSize: 10, letterSpacing: 2, textTransform: "uppercase" }}>{h}</th>
))}
</tr>
</thead>
<tbody>
{filteredCols.map((col, i) => (
<tr key={col.name} className="col-row" style={{ borderBottom: "1px solid #161b22", background: i % 2 === 0 ? "transparent" : "#0a0f16" }}>
<td style={{ padding: "10px 12px", fontWeight: col.pk ? 700 : 400 }}>
<div style={{ display: "flex", alignItems: "center", gap: 7 }}>
{col.pk && <span title="Clé primaire" style={{ fontSize: 11, color: "#f59e0b" }}>🔑</span>}
{col.fk && !col.pk && <span title={`Clé étrangère → ${col.fk}`} style={{ fontSize: 11 }}>🔗</span>}
{!col.pk && !col.fk && <span style={{ fontSize: 11, color: "#30363d" }}>·</span>}
<span style={{ color: col.pk ? "#fbbf24" : col.fk ? "#60a5fa" : "#e6edf3" }}>{col.name}</span>
</div>
{col.fk && (
<div style={{ fontSize: 10, color: "#484f58", marginLeft: 22, marginTop: 2 }}> {col.fk}</div>
)}
</td>
<td style={{ padding: "10px 12px" }}>
<span style={{ color: TYPE_COLOR(col.type), fontWeight: 600 }}>{col.type}</span>
</td>
<td style={{ padding: "10px 12px" }}>
<div style={{ display: "flex", gap: 5, flexWrap: "wrap" }}>
{!col.nullable && (
<span style={{ fontSize: 10, padding: "1px 7px", borderRadius: 3, background: "#1c2e1c", color: "#4ade80", fontWeight: 600 }}>NOT NULL</span>
)}
{col.nullable && (
<span style={{ fontSize: 10, padding: "1px 7px", borderRadius: 3, background: "#1c1c1c", color: "#484f58" }}>NULL</span>
)}
</div>
</td>
<td style={{ padding: "10px 12px" }}>
{col.default ? (
<code style={{ fontSize: 11, color: "#c4b5fd", background: "#1e1a2e", padding: "2px 7px", borderRadius: 3 }}>{col.default}</code>
) : (
<span style={{ color: "#30363d" }}></span>
)}
</td>
</tr>
))}
</tbody>
</table>
{filteredCols.length === 0 && (
<div style={{ padding: 40, textAlign: "center", color: "#484f58", fontSize: 12 }}>Aucune colonne trouvée</div>
)}
</div>
</>
) : (
/* Welcome / overview */
<div style={{ flex: 1, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", padding: 40, gap: 32 }}>
<div style={{ textAlign: "center" }}>
<div style={{ fontSize: 48, marginBottom: 12 }}></div>
<h2 style={{ margin: 0, fontSize: 24, color: "#e6edf3", fontWeight: 700 }}>Modèle de Données</h2>
<p style={{ color: "#484f58", fontSize: 13, marginTop: 8 }}>Sélectionnez une table dans le panneau de gauche</p>
</div>
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(160px, 1fr))", gap: 12, maxWidth: 700, width: "100%" }}>
{GROUPS.map(grp => {
const gc = GROUP_COLORS[grp];
const count = tableNames.filter(n => SCHEMA[n].group === grp).length;
const firstTable = tableNames.find(n => SCHEMA[n].group === grp);
return (
<div key={grp} onClick={() => { setActiveGroup(grp); setSelectedTable(firstTable); setColSearch(""); }} style={{
background: "#161b22", border: `1px solid ${gc.accent}33`, borderRadius: 10, padding: "16px 18px", cursor: "pointer",
transition: "all 0.15s"
}}
onMouseEnter={e => e.currentTarget.style.borderColor = gc.accent}
onMouseLeave={e => e.currentTarget.style.borderColor = gc.accent + "33"}
>
<div style={{ fontSize: 20, marginBottom: 8 }}></div>
<div style={{ fontSize: 13, fontWeight: 700, color: gc.accent, marginBottom: 4 }}>{grp}</div>
<div style={{ fontSize: 11, color: "#484f58" }}>{count} table{count > 1 ? "s" : ""}</div>
</div>
);
})}
</div>
</div>
)}
</div>
</div>
);
}