33800 Docs

← Retour

Proposition : Connectors Hub - Actions Phase 1

Date : 28/01/2026 01:22 Status : IMPLEMENTEE Priorité : HAUTE Projet : connectors-api + connectors-front Estimation : Phase 1 complète


Objectif

Implémenter le système Actions pour Connectors Hub :


1. Schéma Base de Données

Table connectors.actions

CREATE TABLE connectors.actions (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id UUID REFERENCES auth.users(id),

  -- Identification
  name VARCHAR(100) NOT NULL,
  description TEXT,

  -- Type d'action
  action_type VARCHAR(20) NOT NULL CHECK (action_type IN ('ssh', 'api')),

  -- Référence connecteur/instance
  connector_id UUID REFERENCES connectors.config(id),      -- Pour API
  instance_id UUID REFERENCES connectors.user_connectors(id), -- Pour SSH ou instance spécifique

  -- Configuration (JSON selon type)
  config JSONB NOT NULL,

  -- Schémas I/O
  input_schema JSONB,        -- JSON Schema des paramètres attendus
  output_schema JSONB,       -- JSON Schema de la sortie

  -- Metadata
  is_public BOOLEAN DEFAULT false,   -- Partageable avec autres users
  tags TEXT[] DEFAULT '{}',          -- Pour filtrage

  created_at TIMESTAMPTZ DEFAULT now(),
  updated_at TIMESTAMPTZ DEFAULT now(),

  CONSTRAINT actions_user_name_unique UNIQUE (user_id, name)
);

-- Index pour recherche rapide
CREATE INDEX idx_actions_user ON connectors.actions(user_id);
CREATE INDEX idx_actions_type ON connectors.actions(action_type);
CREATE INDEX idx_actions_connector ON connectors.actions(connector_id);
CREATE INDEX idx_actions_instance ON connectors.actions(instance_id);
CREATE INDEX idx_actions_public ON connectors.actions(is_public) WHERE is_public = true;

Table connectors.action_executions

CREATE TABLE connectors.action_executions (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  action_id UUID REFERENCES connectors.actions(id) ON DELETE CASCADE,
  user_id UUID REFERENCES auth.users(id),

  -- Paramètres d'entrée
  input JSONB,

  -- Résultat
  output JSONB,
  status VARCHAR(20) NOT NULL CHECK (status IN ('pending', 'running', 'success', 'error')),
  error_message TEXT,

  -- Timing
  started_at TIMESTAMPTZ DEFAULT now(),
  completed_at TIMESTAMPTZ,
  duration_ms INTEGER,

  -- Context
  trigger_type VARCHAR(20) DEFAULT 'manual' CHECK (trigger_type IN ('manual', 'routine', 'webhook', 'cron')),
  routine_execution_id UUID,  -- Si déclenché par routine (Phase 2)

  created_at TIMESTAMPTZ DEFAULT now()
);

-- Index pour historique
CREATE INDEX idx_action_exec_action ON connectors.action_executions(action_id, created_at DESC);
CREATE INDEX idx_action_exec_user ON connectors.action_executions(user_id, created_at DESC);
CREATE INDEX idx_action_exec_status ON connectors.action_executions(status);

Types de Configuration (JSONB)

Config SSH (action_type = 'ssh')

interface SSHActionConfig {
  type: 'command' | 'script';

  // Commande ou script
  command?: string;           // "df -h", "docker ps --format json"
  script?: string;            // Contenu script bash multi-lignes

  // Options
  sudo?: boolean;
  timeout?: number;           // ms, default 30000
  working_dir?: string;       // cd avant exécution

  // Parsing output
  output_parser: 'raw' | 'json' | 'lines' | 'table' | 'regex';
  parser_config?: {
    // Pour 'table'
    columns?: string[];       // ["filesystem", "size", "used", "avail", "use%", "mount"]
    separator?: string;       // Default: whitespace
    skip_header?: number;     // Lignes à ignorer

    // Pour 'regex'
    pattern?: string;         // Regex avec groupes nommés

    // Pour 'json'
    json_path?: string;       // jq-like: ".data[].name"
  };
}

Config API (action_type = 'api')

interface APIActionConfig {
  method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
  path: string;               // "/api/nodes/{node}/status"

  // Paramètres avec placeholders {{param}}
  path_params?: Record<string, string>;   // Valeurs par défaut
  query_params?: Record<string, any>;
  body?: any;
  headers?: Record<string, string>;

  // Extraction output
  output_path?: string;       // "data.status" → extrait ce champ du JSON
}

2. Endpoints API

Service src/services/actions.ts

// CRUD Actions
list(userId: string, filters?: ActionFilters): Promise<Action[]>
get(actionId: string, userId: string): Promise<Action | null>
create(data: CreateActionInput, userId: string): Promise<Action>
update(actionId: string, data: UpdateActionInput, userId: string): Promise<Action>
delete(actionId: string, userId: string): Promise<void>

// Exécution
execute(actionId: string, input: Record<string, any>, userId: string): Promise<ActionExecution>
getHistory(actionId: string, userId: string, limit?: number): Promise<ActionExecution[]>

// Helpers
validateConfig(actionType: string, config: any): ValidationResult
parseOutput(output: string, parser: string, config: any): any

Routes src/index.ts

// Liste actions (user + public)
GET /api/actions
  Query: ?type=ssh|api &connector_id=xxx &tags=tag1,tag2 &include_public=true
  Response: Action[]

// Détails action
GET /api/actions/:id
  Response: Action (avec config complète)

// Créer action
POST /api/actions
  Body: {
    name: string,
    description?: string,
    action_type: 'ssh' | 'api',
    connector_id?: string,      // Pour API
    instance_id?: string,       // Pour SSH ou instance spécifique
    config: SSHActionConfig | APIActionConfig,
    input_schema?: JSONSchema,
    output_schema?: JSONSchema,
    is_public?: boolean,
    tags?: string[]
  }
  Response: Action

// Modifier action
PUT /api/actions/:id
  Body: Partial<CreateActionInput>
  Response: Action

// Supprimer action
DELETE /api/actions/:id
  Response: { success: true }

// Exécuter action
POST /api/actions/:id/execute
  Body: {
    input?: Record<string, any>   // Paramètres selon input_schema
  }
  Response: ActionExecution

// Historique exécutions
GET /api/actions/:id/history
  Query: ?limit=20 &status=success|error
  Response: ActionExecution[]

// Actions prédéfinies (templates)
GET /api/actions/templates
  Response: ActionTemplate[]   // disk-usage, docker-ps, etc.

// Créer depuis template
POST /api/actions/from-template
  Body: {
    template_id: string,
    instance_id: string,        // SSH instance à utiliser
    name?: string               // Override nom
  }
  Response: Action

3. Actions Prédéfinies (Templates)

SSH Templates

Template ID Nom Commande Parser
ssh-disk-usage Disk Usage df -h table (filesystem, size, used, avail, use%, mount)
ssh-docker-ps Docker Containers docker ps --format json json
ssh-docker-stats Docker Stats docker stats --no-stream --format json json
ssh-top-snapshot System Load top -bn1 \| head -20 raw
ssh-memory Memory Usage free -h table
ssh-uptime Uptime uptime -p raw
ssh-service-status Service Status systemctl status {{service}} --no-pager raw
ssh-tail-logs Tail Logs tail -n {{lines}} {{path}} lines
ssh-apt-update APT Update sudo apt update && sudo apt upgrade -y raw

API Templates (par connecteur)

Connecteur Template ID Path Method
Proxmox proxmox-node-status /api2/json/nodes/{node}/status GET
Proxmox proxmox-list-vms /api2/json/nodes/{node}/qemu GET
Jellyfin jellyfin-sessions /Sessions GET
Jellyfin jellyfin-scan-library /Library/Refresh POST
GitHub github-repos /user/repos GET
Portainer portainer-containers /endpoints/{id}/docker/containers/json GET

4. Frontend - Routes & Pages

Structure

src/routes/actions/
├── index.tsx                    # Liste actions
├── new/index.tsx                # Créer action
├── templates/index.tsx          # Choisir template
└── [id]/
    ├── index.tsx                # Détails + exécuter
    ├── edit/index.tsx           # Modifier
    └── history/index.tsx        # Historique

Page Liste (/actions)

┌─────────────────────────────────────────────────────────────┐
│  Actions                                    [+ Nouvelle]    │
├─────────────────────────────────────────────────────────────┤
│  Filtres: [Type ▼] [Connecteur ▼] [Tags] [□ Inclure public] │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 🖥️ Disk Usage (prod-portainer)           [▶ Run]    │   │
│  │ SSH • df -h • Dernière exec: il y a 2h              │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 🐳 Docker Containers (prod-portainer)    [▶ Run]    │   │
│  │ SSH • docker ps --format json • Jamais exécuté      │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 📊 Proxmox Node Status (pve)             [▶ Run]    │   │
│  │ API • GET /api2/json/nodes/pve/status               │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Page Créer (/actions/new)

┌─────────────────────────────────────────────────────────────┐
│  Nouvelle Action                                            │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Nom*          [_________________________________]          │
│  Description   [_________________________________]          │
│                                                             │
│  Type*         (•) SSH    ( ) API                          │
│                                                             │
│  ─── Configuration SSH ───────────────────────────────      │
│                                                             │
│  Instance*     [prod-portainer-SSH        ▼]                │
│                                                             │
│  Type          (•) Commande    ( ) Script                   │
│                                                             │
│  Commande*     [df -h_____________________________]         │
│                                                             │
│  [ ] Sudo                                                   │
│  Timeout       [30000] ms                                   │
│                                                             │
│  Parser        [table ▼]                                    │
│  Colonnes      [filesystem, size, used, avail, use%, mount] │
│                                                             │
│  ─── Paramètres (optionnel) ──────────────────────────      │
│                                                             │
│  La commande peut contenir {{param}} pour paramétrage       │
│  Ex: tail -n {{lines}} {{path}}                            │
│                                                             │
│  [+ Ajouter paramètre]                                      │
│                                                             │
│  | Nom    | Type   | Défaut | Requis |                     │
│  |--------|--------|--------|--------|                     │
│  | lines  | number | 100    | [ ]    |                     │
│  | path   | string | -      | [x]    |                     │
│                                                             │
│                         [Annuler]  [Créer Action]           │
└─────────────────────────────────────────────────────────────┘

Page Détails + Exécution (/actions/[id])

┌─────────────────────────────────────────────────────────────┐
│  🖥️ Disk Usage                        [Modifier] [Supprimer]│
│  SSH sur prod-portainer                                     │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ Configuration                                        │   │
│  ├─────────────────────────────────────────────────────┤   │
│  │ Commande: df -h                                      │   │
│  │ Parser: table                                        │   │
│  │ Colonnes: filesystem, size, used, avail, use%, mount │   │
│  │ Timeout: 30s                                         │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ Exécuter                                    [▶ Run] │   │
│  ├─────────────────────────────────────────────────────┤   │
│  │ (Pas de paramètres requis)                          │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ Dernier résultat (il y a 5 min) ✓                   │   │
│  ├─────────────────────────────────────────────────────┤   │
│  │ ┌────────────┬──────┬──────┬───────┬─────┬───────┐ │   │
│  │ │ Filesystem │ Size │ Used │ Avail │ Use%│ Mount │ │   │
│  │ ├────────────┼──────┼──────┼───────┼─────┼───────┤ │   │
│  │ │ /dev/sda1  │ 50G  │ 32G  │ 18G   │ 64% │ /     │ │   │
│  │ │ /dev/sdb1  │ 500G │ 320G │ 180G  │ 64% │ /data │ │   │
│  │ └────────────┴──────┴──────┴───────┴─────┴───────┘ │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ Historique                          [Voir tout →]   │   │
│  ├─────────────────────────────────────────────────────┤   │
│  │ • il y a 5 min   ✓ success   42ms                   │   │
│  │ • il y a 2h      ✓ success   38ms                   │   │
│  │ • hier 14:30     ✗ error     timeout                │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

5. Composants Réutilisables

Composant Usage
ActionCard Card dans liste (nom, type, instance, bouton run)
ActionForm Formulaire création/édition
ActionExecutor Zone exécution avec params + bouton
ActionResult Affichage résultat (table, json, raw)
ActionHistory Liste exécutions avec status
ParserConfigEditor Config du parser (colonnes, regex, etc.)
InputSchemaEditor Définition paramètres dynamiques

6. Plan d'Implémentation

Étape 1 : Base de données

Étape 2 : Service Backend

Étape 3 : Routes API

Étape 4 : Templates

Étape 5 : Frontend - Liste

Étape 6 : Frontend - Création

Étape 7 : Frontend - Détails & Exécution

Étape 8 : Frontend - Templates


7. Sécurité

Validation commandes SSH

// Liste noire de commandes dangereuses
const FORBIDDEN_PATTERNS = [
  /rm\s+(-rf?|--recursive)\s+\//,   // rm -rf /
  /mkfs/,
  /dd\s+.*of=\/dev/,
  />\s*\/dev\/sd/,
  /shutdown|reboot|halt|poweroff/,
  /:(){ :|:& };:/,                   // Fork bomb
];

// Validation avant exécution
function validateCommand(cmd: string): boolean {
  return !FORBIDDEN_PATTERNS.some(p => p.test(cmd));
}

Permissions


8. Fichiers à Créer/Modifier

Backend (connectors-api)

Fichier Action
migrations/004_actions.sql Créer
src/services/actions.ts Créer
src/services/parsers.ts Créer
src/data/action-templates.ts Créer
src/types/action.ts Créer
src/index.ts Modifier (ajouter routes)

Frontend (connectors-front)

Fichier Action
src/routes/actions/index.tsx Créer
src/routes/actions/new/index.tsx Créer
src/routes/actions/templates/index.tsx Créer
src/routes/actions/[id]/index.tsx Créer
src/routes/actions/[id]/edit/index.tsx Créer
src/routes/actions/[id]/history/index.tsx Créer
src/components/actions/ActionCard.tsx Créer
src/components/actions/ActionForm.tsx Créer
src/components/actions/ActionExecutor.tsx Créer
src/components/actions/ActionResult.tsx Créer
src/components/actions/ActionHistory.tsx Créer
src/lib/api.ts Modifier (ajouter endpoints)

Validation