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
656 lines
43 KiB
JavaScript
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>
|
|
);
|
|
}
|