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)
- Sentry (error tracking)
- Laravel Health (health checks)
- Log monitoring (storage/logs)
- PHPStan/Larastan, Pint, Pest
- Node 20 + Vite per asset
- Composer 2.7+
- VS Code/Cursor + estensioni Mermaid/Blade/PHP
- SSH/Terminal cPanel
- PHP-FPM (gestito da cPanel)
- MySQL (database cPanel)
- Redis (se disponibile)
- Cron jobs (Laravel Scheduler)
- File storage locale
- Un utente può avere N ruoli (es.
- Se l’utente ha più ruoli, dopo login mostra modal per scegliere ruolo attivo (
- Aggiungere voce “Cambia ruolo” nel menu utente per switch rapido (senza riloggare).
- 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).
- Tabella
- Ogni voce:
- Service
- Blade: top bar da
- Runtime: Python 3.11+, packaging con PyInstaller o eseguibile standalone.
- Config:
- Funzioni: - Enroll (una tantum): invio fingerprint, hostname, OS, versione agent → ottiene
- Sicurezza: -
- UI Admin: - Tabella "Elenco macchine collegate": badge stato, protocollo, ultimo heartbeat, versione agent, azioni (forza sync, rigenera token).
- Configura database MySQL in cPanel
- Setup
- 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:
- Cron job: configura in cPanel per Laravel Scheduler (
- Queue worker: configura cron o supervisord per
- Health: rotta
- Script composer per setup (
- Setting/Operativo/Shop-floor secondo priorità
- 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
- Cron job configurato per Laravel Scheduler
- [ ] Configura database MySQL in cPanel
- [ ] Setup
- [ ] Installa Spatie + config Gates/Policies
- [ ] Seeder: SuperAdmin, Permission, Menu (placeholder + I4.0)
- [ ]
- [ ] 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
- [ ] Script composer per bootstrap rapido
Observability & Qualità
Dev tools
Servizi VPS disponibili
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
super-admin, admin, operator, maintenance, viewer).
active_role in sessione).
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
Dati menu amministrabili da back-end
menus e menu_items (multilivello via parent_id).
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
MenuService → albero filtrato per ruolo attivo (Spatie: hasRole() / can()).
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.
config.yaml con server_url, device_token, lista macchine/protocolli (anche placeholder).
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.
device_token + TLS, rate-limit per dispositivo, opzionale mutual TLS in LAN.
- Firma HMAC opzionale sui payload.
---
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)
.env per ambiente VPS con credenziali corrette
/devices/enroll, /devices/heartbeat, /uploads con Policies e test Pest.
* php artisan schedule:run)
php artisan queue:work (se necessario)
/admin/health e check MySQL/Redis/Storage.
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 + HealthFase 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
---
9) Criteri di Accettazione (Fase 1)
https://sartup.it
---
10) TODO per Cursor (lista operativa)
.env per ambiente VPS
MenuService + componenti Blade per top bar + sidebar
php artisan schedule:run`
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)