sartUP — Contenitore Admin + Agent Locale (Industria 4.0)

sartUP — Contenitore Admin + Agent Locale (Industria 4.0)

Target: Preparare l’ambiente di sviluppo Laravel 11 con contenitore Admin funzionante, autenticazione attiva, RBAC con super admin, menu dinamico (orizzontale + sidebar multilivello) con placeholder e sezione Industria 4.0 → Report → Macchine → Elenco macchine collegate. Scope fase attuale: Contenitore + groundwork (senza dominio dati definitivo).

---

0) Obiettivo gestionale (sintesi)

sartUP è un gestionale modulare per aziende manifatturiere (camicie) che copre Setting (Organizzazione, Modelli/BOM, Tempi & Metodi, HR, Risorse), Operativo (Ordini, Avanzamento & Shop-Floor), Industria 4.0 (Edge gateway, connettori, agent locale) e Integrazioni (ERP/WMS/BI). Questa fase consegna il contenitore amministrativo e l’infrastruttura per sviluppi incrementali (menu, RBAC, layout, services base).

---

1) Piattaforma & servizi (ambiente VPS)

Stack principale
  • VPS con cPanel (AlmaLinux)
  • PHP 8.2+, Laravel 11
  • MySQL 8 (DB transazionale)
  • Redis (opzionale, cache/queue) o file/database cache
  • Storage locale o S3-compatible (opzionale)
  • SSL/TLS automatico (AutoSSL cPanel)
  • Observability & Qualità

  • Sentry (error tracking)
  • Laravel Health (health checks)
  • Log monitoring (storage/logs)
  • PHPStan/Larastan, Pint, Pest
  • Dev tools

  • Node 20 + Vite per asset
  • Composer 2.7+
  • VS Code/Cursor + estensioni Mermaid/Blade/PHP
  • SSH/Terminal cPanel
  • Servizi VPS disponibili

  • PHP-FPM (gestito da cPanel)
  • MySQL (database cPanel)
  • Redis (se disponibile)
  • Cron jobs (Laravel Scheduler)
  • File storage locale
  • Variabili .env VPS `` APP_ENV=production APP_KEY= APP_URL=https://sartup.it

    DB_HOST=localhost DB_DATABASE=cpanel_user_sartup DB_USERNAME=cpanel_user_xxxxx DB_PASSWORD=secure_password

    CACHE_DRIVER=file QUEUE_CONNECTION=database

    Se Redis disponibile:

    CACHE_DRIVER=redis

    QUEUE_CONNECTION=redis

    REDIS_HOST=localhost

    FILESYSTEM_DISK=local

    Se usi S3:

    FILESYSTEM_DISK=s3

    AWS_ACCESS_KEY_ID=

    AWS_SECRET_ACCESS_KEY=

    AWS_DEFAULT_REGION=us-east-1

    AWS_BUCKET=sartup

    `

    > Nota: SSL gestito automaticamente da cPanel (AutoSSL o Let's Encrypt).

    ---

    2) Autenticazione & RBAC con Super Admin

    Package:
    spatie/laravel-permission per ruoli (permessi granulari attivabili in futuro).

    Concetto chiave: ruolo attivo in sessione

  • Un utente può avere N ruoli (es. super-admin, admin, operator, maintenance, viewer).
  • Se l’utente ha più ruoli, dopo login mostra modal per scegliere ruolo attivo (active_role in sessione).
  • Aggiungere voce “Cambia ruolo” nel menu utente per switch rapido (senza riloggare).
  • Seeder Super Admin (snippet) `php // database/seeders/SuperAdminSeeder.php public function run(): void { $role = Role::firstOrCreate(['name' => 'super-admin']); User::firstOrCreate( ['email' => 'root@sartup.local'], ['name' => 'Root', 'password' => bcrypt('ChangeMe!')] )->assignRole($role); } `

    Gate super-admin (snippet) `php // AuthServiceProvider Gate::before(function ($user, $ability) { return $user->hasRole('super-admin') ? true : null; }); `

    ---

    3) Contenitore Admin (layout + menu dinamico)

    Requisiti UI
  • Main top bar (Livello 1): orizzontale, voci principali.
  • Sidebar sinistra (Livelli 2/3/4): dipende dalla voce L1 selezionata.
  • Layout responsive, dark mode opzionale, icone (Lucide).
  • Dati menu amministrabili da back-end

  • Tabella menus e menu_items (multilivello via parent_id).
  • Ogni voce: label, route_name o url, icon, description, order_index, is_visible,
  • required_roles (JSON) / required_permissions (JSON), opzionali badge/tag.

    Schema tabelle (bozza) `sql CREATE TABLE menus ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(64) UNIQUE NOT NULL, -- es. 'admin_main' description VARCHAR(255) NULL, is_active TINYINT(1) DEFAULT 1, created_at TIMESTAMP NULL, updated_at TIMESTAMP NULL );

    CREATE TABLE menu_items ( id BIGINT PRIMARY KEY AUTO_INCREMENT, menu_id BIGINT NOT NULL, parent_id BIGINT NULL, label VARCHAR(100) NOT NULL, route_name VARCHAR(120) NULL, url VARCHAR(255) NULL, icon VARCHAR(60) NULL, description VARCHAR(255) NULL, order_index INT DEFAULT 0, is_visible TINYINT(1) DEFAULT 1, required_roles JSON NULL, required_permissions JSON NULL, created_at TIMESTAMP NULL, updated_at TIMESTAMP NULL, CONSTRAINT fk_menu FOREIGN KEY (menu_id) REFERENCES menus(id), CONSTRAINT fk_parent FOREIGN KEY (parent_id) REFERENCES menu_items(id) ); `

    Seed iniziale (placeholder + Industria 4.0) `php // database/seeders/MenuSeeder.php (estratto) $admin = Menu::firstOrCreate(['name' => 'admin_main'], ['description' => 'Menu principale admin']);

    $ind40 = MenuItem::firstOrCreate([ 'menu_id'=>$admin->id,'parent_id'=>null,'label'=>'Industria 4.0' ],['icon'=>'lucide-cpu','order_index'=>2]);

    $report = MenuItem::firstOrCreate([ 'menu_id'=>$admin->id,'parent_id'=>$ind40->id,'label'=>'Report' ],['order_index'=>1]);

    $macchine = MenuItem::firstOrCreate([ 'menu_id'=>$admin->id,'parent_id'=>$report->id,'label'=>'Macchine' ],['order_index'=>1]);

    MenuItem::firstOrCreate([ 'menu_id'=>$admin->id,'parent_id'=>$macchine->id,'label'=>'Elenco macchine collegate' ],[ 'route_name'=>'admin.i40.machines.connected', 'order_index'=>1, 'required_roles'=>json_encode(['admin','maintenance','super-admin']) ]); `

    Rendering

  • Service MenuService → albero filtrato per ruolo attivo (Spatie: hasRole() / can()).
  • Blade: top bar da parent_id = NULL, sidebar dai figli della voce L1 corrente.
  • ---

    4) Rotte & Policies (placeholder)

    Rotte base ` GET /admin → DashboardController@index (policy: dashboard.view) GET /admin/i40 → I40\HomeController@index (policy: i40.view) GET /admin/i40/machines → I40\MachinesController@index (policy: machines.view) GET /admin/i40/machines/connected → I40\MachinesController@connected (policy: machines.view) POST /admin/devices/enroll → DevicesController@enroll (policy: agent.manage) POST /admin/devices/heartbeat → DevicesController@heartbeat (policy: agent.manage) POST /admin/uploads/presign → UploadsController@presign (policy: files.create) `

    Policies/permessi minimi

  • dashboard.view, i40.view, machines.view, agent.manage, files.create, menu.manage, users.manage
  • ---

    5) Agent locale (Python) — MVP orientato all'ambiente

    Scopo: mostrare "Macchine collegate" e stato device, senza dominio complesso.
  • Runtime: Python 3.11+, packaging con PyInstaller o eseguibile standalone.
  • Config: config.yaml con server_url, device_token, lista macchine/protocolli (anche placeholder).
  • Funzioni:
  • - Enroll (una tantum): invio fingerprint, hostname, OS, versione agent → ottiene
    device_token (se non pre-provisioned). - Heartbeat (15–30s): stato online, uptime, versione, elenco macchine (id | ip | protocol | status | last_seen). - Upload (quando serve): file diagnostici/log via storage locale o S3-compatible.
  • Sicurezza:
  • -
    device_token + TLS, rate-limit per dispositivo, opzionale mutual TLS in LAN. - Firma HMAC opzionale sui payload.
  • UI Admin:
  • - Tabella "Elenco macchine collegate": badge stato, protocollo, ultimo heartbeat, versione agent, azioni (forza sync, rigenera token).

    ---

    6) Service layer richiesto (Cursor: crea skeleton)

  • App\Services\MenuService — build albero filtrato per permessi
  • App\Http\Controllers\Admin\... — Dashboard, I40, Machines
  • App\Http\Controllers\Admin\DevicesController — enroll/heartbeat
  • App\Http\Controllers\Admin\UploadsController — presign S3 (MinIO)
  • App\Policies\* — policies per viste/azioni
  • database/seeders/* — SuperAdmin, MenuSeeder, PermissionSeeder
  • resources\views\layouts\admin.blade.php — contenitore con top bar + sidebar
  • resources\views\admin\i40\machines\connected.blade.php — placeholder tabella
  • ---

    7) Ottimizzazione ambiente VPS (richieste a Cursor)

  • Configura database MySQL in cPanel
  • Setup .env per ambiente VPS con credenziali corrette
  • Setup Spatie + SuperAdmin seeder + Role selector
  • Seeder: permessi base, menu placeholder con Industria 4.0 → Report → Macchine → Elenco macchine collegate.
  • Crea layout admin (top bar + sidebar multilivello) leggendo da DB.
  • Implementa role selection in sessione post-login (modal) + switch in user menu.
  • Endpoint: /devices/enroll, /devices/heartbeat, /uploads con Policies e test Pest.
  • Cron job: configura in cPanel per Laravel Scheduler ( * php artisan schedule:run)
  • Queue worker: configura cron o supervisord per php artisan queue:work (se necessario)
  • Health: rotta /admin/health e check MySQL/Redis/Storage.
  • Script composer per setup (composer run-script post-install).
  • ---

    8) Roadmap step-by-step (questa fase + prossime)

    Fase 1 — Applicazione pronta all'uso su VPS (QUESTA CONSEGNA) 1. Setup VPS cPanel + database MySQL 2. Spatie + SuperAdmin seeder + Role selector 3. Tabelle menu + editor semplice (CRUD) + seed placeholder 4. Layout admin (top bar + sidebar) + routing base 5. Endpoints device & upload (scheletri) + Cron jobs + Health

    Fase 2 — Agent Python MVP + Vista "Macchine collegate" 1. Agent (enroll, heartbeat, config) 2. UI tabellare (status, last_seen, versione, azioni) 3. Logging/metrics + alert basi (offline > X min)

    Fase 3 — Import/Upload edge & sicurezza avanzata 1. Upload per file diagnostici + validazione 2. Rate limit per device + revoca token + audit trail 3. (Opz.) MQTT heartbeat + collector minimale

    Fase 4 — Estensioni dominio e moduli operativi

  • Setting/Operativo/Shop-floor secondo priorità
  • ---

    9) Criteri di Accettazione (Fase 1)

  • Login funzionante; utente super-admin creato
  • Role selector visibile per utenti con più ruoli
  • Menu dinamico amministrabile; voce Industria 4.0 presente con percorso Report → Macchine → Elenco macchine collegate (view placeholder)
  • Endpoint enroll/heartbeat/upload esposti (anche con risposte mock)
  • Health check funzionante, storage accessibile
  • Applicazione accessibile via https://sartup.it
  • Cron job configurato per Laravel Scheduler
  • ---

    10) TODO per Cursor (lista operativa)

  • [ ] Configura database MySQL in cPanel
  • [ ] Setup .env per ambiente VPS
  • [ ] Installa Spatie + config Gates/Policies
  • [ ] Seeder: SuperAdmin, Permission, Menu (placeholder + I4.0)
  • [ ] MenuService + componenti Blade per top bar + sidebar
  • [ ] Middleware "ActiveRole" + controller per switch ruolo
  • [ ] Controller/Routes: Dashboard, I40 Home, Machines Connected
  • [ ] Controller upload (storage locale/S3) + policy
  • [ ] Endpoint enroll/heartbeat (mock) + test Pest di base
  • [ ] Health check + Sentry config
  • [ ] Cron job in cPanel per php artisan schedule:run`
  • [ ] Script composer per bootstrap rapido

Analisi Codice

Blocco 1
APP_ENV=production
APP_KEY=
APP_URL=https://sartup.it

DB_HOST=localhost
DB_DATABASE=cpanel_user_sartup
DB_USERNAME=cpanel_user_xxxxx
DB_PASSWORD=secure_password

CACHE_DRIVER=file
QUEUE_CONNECTION=database
# Se Redis disponibile:
# CACHE_DRIVER=redis
# QUEUE_CONNECTION=redis
# REDIS_HOST=localhost

FILESYSTEM_DISK=local
# Se usi S3:
# FILESYSTEM_DISK=s3
# AWS_ACCESS_KEY_ID=
# AWS_SECRET_ACCESS_KEY=
# AWS_DEFAULT_REGION=us-east-1
# AWS_BUCKET=sartup
Blocco 2 php
// database/seeders/SuperAdminSeeder.php
public function run(): void {
    $role = Role::firstOrCreate(['name' => 'super-admin']);
    User::firstOrCreate(
      ['email' => 'root@sartup.local'],
      ['name' => 'Root', 'password' => bcrypt('ChangeMe!')]
    )->assignRole($role);
}
Blocco 3 php
// AuthServiceProvider
Gate::before(function ($user, $ability) {
    return $user->hasRole('super-admin') ? true : null;
});
Blocco 4 sql
CREATE TABLE menus (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(64) UNIQUE NOT NULL,      -- es. 'admin_main'
  description VARCHAR(255) NULL,
  is_active TINYINT(1) DEFAULT 1,
  created_at TIMESTAMP NULL,
  updated_at TIMESTAMP NULL
);

CREATE TABLE menu_items (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  menu_id BIGINT NOT NULL,
  parent_id BIGINT NULL,
  label VARCHAR(100) NOT NULL,
  route_name VARCHAR(120) NULL,
  url VARCHAR(255) NULL,
  icon VARCHAR(60) NULL,
  description VARCHAR(255) NULL,
  order_index INT DEFAULT 0,
  is_visible TINYINT(1) DEFAULT 1,
  required_roles JSON NULL,
  required_permissions JSON NULL,
  created_at TIMESTAMP NULL,
  updated_at TIMESTAMP NULL,
  CONSTRAINT fk_menu FOREIGN KEY (menu_id) REFERENCES menus(id),
  CONSTRAINT fk_parent FOREIGN KEY (parent_id) REFERENCES menu_items(id)
);
Blocco 5 php
// database/seeders/MenuSeeder.php (estratto)
$admin = Menu::firstOrCreate(['name' => 'admin_main'], ['description' => 'Menu principale admin']);

$ind40 = MenuItem::firstOrCreate([
  'menu_id'=>$admin->id,'parent_id'=>null,'label'=>'Industria 4.0'
],['icon'=>'lucide-cpu','order_index'=>2]);

$report = MenuItem::firstOrCreate([
  'menu_id'=>$admin->id,'parent_id'=>$ind40->id,'label'=>'Report'
],['order_index'=>1]);

$macchine = MenuItem::firstOrCreate([
  'menu_id'=>$admin->id,'parent_id'=>$report->id,'label'=>'Macchine'
],['order_index'=>1]);

MenuItem::firstOrCreate([
  'menu_id'=>$admin->id,'parent_id'=>$macchine->id,'label'=>'Elenco macchine collegate'
],[
  'route_name'=>'admin.i40.machines.connected',
  'order_index'=>1,
  'required_roles'=>json_encode(['admin','maintenance','super-admin'])
]);
Blocco 6
GET  /admin                  → DashboardController@index  (policy: dashboard.view)
GET  /admin/i40              → I40\HomeController@index  (policy: i40.view)
GET  /admin/i40/machines     → I40\MachinesController@index  (policy: machines.view)
GET  /admin/i40/machines/connected → I40\MachinesController@connected (policy: machines.view)
POST /admin/devices/enroll   → DevicesController@enroll  (policy: agent.manage)
POST /admin/devices/heartbeat → DevicesController@heartbeat (policy: agent.manage)
POST /admin/uploads/presign  → UploadsController@presign (policy: files.create)