mirror of
https://github.com/idrainformatica/PecFlow.git
synced 2026-06-16 12:45:42 +02:00
Ruolo supervisor
This commit is contained in:
@@ -64,12 +64,7 @@ backend/app/api/v1/archival.py (endpoint GET /archival/batches, POST /archival/d
|
|||||||
frontend/src/pages/Archival/ (pagina log versamenti, download RdV, richiesta DIP)
|
frontend/src/pages/Archival/ (pagina log versamenti, download RdV, richiesta DIP)
|
||||||
Il modello archival.py esiste ma la tabella archival_batches non e' nella migrazione corrente
|
Il modello archival.py esiste ma la tabella archival_batches non e' nella migrazione corrente
|
||||||
La configurazione conservatore nelle impostazioni tenant e' pronta, ma il "pulsante" che avvia il versamento non esiste
|
La configurazione conservatore nelle impostazioni tenant e' pronta, ma il "pulsante" che avvia il versamento non esiste
|
||||||
4. Dashboard e Reportistica (Fase 7 – completamente mancante)
|
|
||||||
|
|
||||||
Non esistono endpoint /reports/summary, /reports/export
|
|
||||||
Non esiste pagina Reports/Dashboard nel frontend (nessuna rotta in App.tsx)
|
|
||||||
Non c'e' generazione PDF (WeasyPrint) ne' export CSV
|
|
||||||
Non c'e' nessun grafico o KPI visibile (PEC ricevute/inviate oggi, anomalie, tasso consegna)
|
|
||||||
5. Audit Log – modello esistente, tutto il resto mancante
|
5. Audit Log – modello esistente, tutto il resto mancante
|
||||||
|
|
||||||
Il modello audit_log.py e la tabella esistono
|
Il modello audit_log.py e la tabella esistono
|
||||||
@@ -89,12 +84,7 @@ La cifratura dei segreti notifiche usa base64.b64encode() senza encryption reale
|
|||||||
Il CI/CD GitHub Actions e' disabilitato (ci.yml.bak): non c'e' lint automatico, test o build su PR
|
Il CI/CD GitHub Actions e' disabilitato (ci.yml.bak): non c'e' lint automatico, test o build su PR
|
||||||
Non c'e' docker-compose.prod.yml (override produzione con configurazioni rafforzate)
|
Non c'e' docker-compose.prod.yml (override produzione con configurazioni rafforzate)
|
||||||
Docs /docs, /redoc sono disabilitate in produzione ma non c'e' un meccanismo di secret scan
|
Docs /docs, /redoc sono disabilitate in produzione ma non c'e' un meccanismo di secret scan
|
||||||
8. Invio PEC – funzionalita' mancanti
|
|
||||||
|
|
||||||
Non c'e' Forward messaggio (la risposta e' parzialmente implementata in ComposePage ma non e' chiaro se funziona end-to-end)
|
|
||||||
Non c'e' endpoint per forzare un re-sync manuale di una casella (utile dopo un errore di connessione)
|
|
||||||
Non c'e' indicazione visiva del numero di messaggi non letti nella sidebar per casella
|
|
||||||
La barra ricerca nell'Inbox non ha filtri per data (da/a), stato PEC, tipo PEC
|
|
||||||
9. Ruolo Supervisor
|
9. Ruolo Supervisor
|
||||||
|
|
||||||
Il ruolo supervisor e' definito nell'enum DB e nella documentazione ma non ha logica differenziata dal operator nel codice: is_admin controlla solo admin/super_admin, tutto il resto e' trattato uguale
|
Il ruolo supervisor e' definito nell'enum DB e nella documentazione ma non ha logica differenziata dal operator nel codice: is_admin controlla solo admin/super_admin, tutto il resto e' trattato uguale
|
||||||
|
|||||||
@@ -77,15 +77,15 @@ async def list_mailboxes(
|
|||||||
"""
|
"""
|
||||||
svc = _svc(db)
|
svc = _svc(db)
|
||||||
|
|
||||||
if current_user.is_admin:
|
if current_user.is_supervisor_or_admin:
|
||||||
# Admin: tutte le caselle del tenant
|
# Admin e supervisor: tutte le caselle del tenant
|
||||||
items, total = await svc.list_mailboxes(
|
items, total = await svc.list_mailboxes(
|
||||||
tenant_id=current_user.tenant_id,
|
tenant_id=current_user.tenant_id,
|
||||||
page=page,
|
page=page,
|
||||||
page_size=page_size,
|
page_size=page_size,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# Operatori: caselle con permesso
|
# Operator e readonly: caselle con permesso esplicito
|
||||||
from app.services.permission_service import PermissionService
|
from app.services.permission_service import PermissionService
|
||||||
perm_svc = PermissionService(db)
|
perm_svc = PermissionService(db)
|
||||||
visible_ids = await perm_svc.get_visible_mailboxes(current_user)
|
visible_ids = await perm_svc.get_visible_mailboxes(current_user)
|
||||||
@@ -140,7 +140,9 @@ async def get_unread_counts(
|
|||||||
from app.models.message import Message
|
from app.models.message import Message
|
||||||
|
|
||||||
# Determina le caselle visibili
|
# Determina le caselle visibili
|
||||||
if current_user.is_admin:
|
# Admin e supervisor: nessun filtro (accesso a tutto il tenant)
|
||||||
|
# Operator e readonly: solo caselle con permesso esplicito can_read
|
||||||
|
if current_user.is_supervisor_or_admin:
|
||||||
visible_ids = None # nessun filtro
|
visible_ids = None # nessun filtro
|
||||||
else:
|
else:
|
||||||
from app.services.permission_service import PermissionService
|
from app.services.permission_service import PermissionService
|
||||||
|
|||||||
@@ -96,11 +96,14 @@ async def _get_visible_mailbox_ids(
|
|||||||
user, db: AsyncSession
|
user, db: AsyncSession
|
||||||
) -> Optional[list[uuid.UUID]]:
|
) -> Optional[list[uuid.UUID]]:
|
||||||
"""
|
"""
|
||||||
Per utenti non-admin restituisce la lista di mailbox_id accessibili.
|
Per utenti non-admin/supervisor restituisce la lista di mailbox_id accessibili.
|
||||||
Restituisce None se l'utente e admin (accesso illimitato al tenant).
|
Restituisce None se l'utente e' admin o supervisor (accesso illimitato al tenant).
|
||||||
|
|
||||||
|
Admin e supervisor: None (nessun filtro, query diretta sull'intero tenant).
|
||||||
|
Operator e readonly: lista esplicita di caselle con can_read=True.
|
||||||
"""
|
"""
|
||||||
if user.is_admin:
|
if user.is_supervisor_or_admin:
|
||||||
return None # nessun filtro per admin
|
return None # nessun filtro per admin e supervisor
|
||||||
|
|
||||||
from app.services.permission_service import PermissionService
|
from app.services.permission_service import PermissionService
|
||||||
perm_svc = PermissionService(db)
|
perm_svc = PermissionService(db)
|
||||||
|
|||||||
@@ -153,6 +153,20 @@ async def require_super_admin(
|
|||||||
return current_user
|
return current_user
|
||||||
|
|
||||||
|
|
||||||
|
async def require_supervisor_or_admin(
|
||||||
|
current_user: Annotated[User, Depends(get_current_user)],
|
||||||
|
) -> User:
|
||||||
|
"""
|
||||||
|
Richiede ruolo supervisor, admin o super_admin.
|
||||||
|
|
||||||
|
Il supervisor ha accesso in lettura implicito a tutte le caselle del tenant
|
||||||
|
ma non puo' gestire la configurazione (caselle, utenti, permessi, impostazioni).
|
||||||
|
"""
|
||||||
|
if not current_user.is_supervisor_or_admin:
|
||||||
|
raise ForbiddenError("Richiesto ruolo supervisore o amministratore")
|
||||||
|
return current_user
|
||||||
|
|
||||||
|
|
||||||
# ─── Protezione endpoint admin con X-Admin-Key header ─────────────────────────
|
# ─── Protezione endpoint admin con X-Admin-Key header ─────────────────────────
|
||||||
|
|
||||||
async def verify_admin_key(
|
async def verify_admin_key(
|
||||||
@@ -176,4 +190,5 @@ CurrentUser = Annotated[User, Depends(get_current_user)]
|
|||||||
CurrentTenant = Annotated[Tenant, Depends(get_current_tenant)]
|
CurrentTenant = Annotated[Tenant, Depends(get_current_tenant)]
|
||||||
AdminUser = Annotated[User, Depends(require_admin)]
|
AdminUser = Annotated[User, Depends(require_admin)]
|
||||||
SuperAdminUser = Annotated[User, Depends(require_super_admin)]
|
SuperAdminUser = Annotated[User, Depends(require_super_admin)]
|
||||||
|
SupervisorOrAdminUser = Annotated[User, Depends(require_supervisor_or_admin)]
|
||||||
DB = Annotated[AsyncSession, Depends(get_db)]
|
DB = Annotated[AsyncSession, Depends(get_db)]
|
||||||
|
|||||||
@@ -86,6 +86,16 @@ class User(Base):
|
|||||||
def is_super_admin(self) -> bool:
|
def is_super_admin(self) -> bool:
|
||||||
return self.role == "super_admin"
|
return self.role == "super_admin"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_supervisor(self) -> bool:
|
||||||
|
"""Ruolo supervisor: lettura implicita su tutte le caselle, senza poteri di gestione."""
|
||||||
|
return self.role == "supervisor"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_supervisor_or_admin(self) -> bool:
|
||||||
|
"""True per super_admin, admin e supervisor (accesso in lettura a tutto il tenant)."""
|
||||||
|
return self.role in ("super_admin", "admin", "supervisor")
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"<User {self.email!r} role={self.role!r}>"
|
return f"<User {self.email!r} role={self.role!r}>"
|
||||||
|
|
||||||
|
|||||||
@@ -27,9 +27,13 @@ class PermissionService:
|
|||||||
async def get_visible_mailboxes(
|
async def get_visible_mailboxes(
|
||||||
self, user: User
|
self, user: User
|
||||||
) -> list[uuid.UUID]:
|
) -> list[uuid.UUID]:
|
||||||
"""Restituisce gli UUID delle caselle visibili all'utente."""
|
"""Restituisce gli UUID delle caselle visibili all'utente.
|
||||||
if user.role in ("super_admin", "admin"):
|
|
||||||
# Admin vede tutte le caselle del tenant
|
Admin e supervisor vedono tutte le caselle del tenant.
|
||||||
|
Operator e readonly vedono solo le caselle con can_read=True esplicito.
|
||||||
|
"""
|
||||||
|
if user.role in ("super_admin", "admin", "supervisor"):
|
||||||
|
# Admin e supervisor vedono tutte le caselle del tenant
|
||||||
result = await self.db.execute(
|
result = await self.db.execute(
|
||||||
select(Mailbox.id).where(
|
select(Mailbox.id).where(
|
||||||
Mailbox.tenant_id == user.tenant_id,
|
Mailbox.tenant_id == user.tenant_id,
|
||||||
@@ -38,7 +42,7 @@ class PermissionService:
|
|||||||
)
|
)
|
||||||
return [row[0] for row in result.all()]
|
return [row[0] for row in result.all()]
|
||||||
|
|
||||||
# Operatori: solo caselle con can_read=True
|
# Operator e readonly: solo caselle con can_read=True esplicito
|
||||||
result = await self.db.execute(
|
result = await self.db.execute(
|
||||||
select(MailboxPermission.mailbox_id).where(
|
select(MailboxPermission.mailbox_id).where(
|
||||||
MailboxPermission.user_id == user.id,
|
MailboxPermission.user_id == user.id,
|
||||||
@@ -50,9 +54,13 @@ class PermissionService:
|
|||||||
async def check_can_read(
|
async def check_can_read(
|
||||||
self, user: User, mailbox_id: uuid.UUID
|
self, user: User, mailbox_id: uuid.UUID
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Verifica se l'utente può leggere i messaggi della casella."""
|
"""Verifica se l'utente puo' leggere i messaggi della casella.
|
||||||
if user.role in ("super_admin", "admin"):
|
|
||||||
# Verifica solo che la casella appartenga al tenant
|
Admin e supervisor hanno accesso implicito a tutte le caselle del tenant.
|
||||||
|
Operator e readonly richiedono permesso esplicito can_read.
|
||||||
|
"""
|
||||||
|
if user.role in ("super_admin", "admin", "supervisor"):
|
||||||
|
# Admin e supervisor: verifica solo che la casella appartenga al tenant
|
||||||
return await self._mailbox_belongs_to_tenant(mailbox_id, user.tenant_id)
|
return await self._mailbox_belongs_to_tenant(mailbox_id, user.tenant_id)
|
||||||
|
|
||||||
perm = await self._get_permission(user.id, mailbox_id)
|
perm = await self._get_permission(user.id, mailbox_id)
|
||||||
@@ -62,12 +70,15 @@ class PermissionService:
|
|||||||
self, user: User, mailbox_id: uuid.UUID
|
self, user: User, mailbox_id: uuid.UUID
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""
|
"""
|
||||||
Verifica se l'utente può inviare dalla casella.
|
Verifica se l'utente puo' inviare dalla casella.
|
||||||
|
|
||||||
L'accesso in invio è concesso se:
|
L'accesso in invio e' concesso se:
|
||||||
1. L'utente è admin del tenant, oppure
|
1. L'utente e' admin del tenant, oppure
|
||||||
2. L'utente ha un permesso diretto can_send sulla casella, oppure
|
2. L'utente ha un permesso diretto can_send sulla casella, oppure
|
||||||
3. L'utente è assegnato a una Virtual Box attiva che include la casella.
|
3. L'utente e' assegnato a una Virtual Box attiva che include la casella.
|
||||||
|
|
||||||
|
Nota: il supervisor NON ha invio implicito – richiede can_send esplicito
|
||||||
|
come operator, ma diversamente da operator vede tutte le caselle.
|
||||||
"""
|
"""
|
||||||
if user.role in ("super_admin", "admin"):
|
if user.role in ("super_admin", "admin"):
|
||||||
return await self._mailbox_belongs_to_tenant(mailbox_id, user.tenant_id)
|
return await self._mailbox_belongs_to_tenant(mailbox_id, user.tenant_id)
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ export function Sidebar() {
|
|||||||
/** Set degli ID virtual box che l'utente ha esplicitamente chiuso. */
|
/** Set degli ID virtual box che l'utente ha esplicitamente chiuso. */
|
||||||
const [collapsedVboxes, setCollapsedVboxes] = useState<Set<string>>(new Set())
|
const [collapsedVboxes, setCollapsedVboxes] = useState<Set<string>>(new Set())
|
||||||
|
|
||||||
const { user, isAdmin, isSuperAdmin, logout } = useAuth()
|
const { user, isAdmin, isSuperAdmin, isSupervisor, logout } = useAuth()
|
||||||
const unreadCount = useInboxStore((s) => s.unreadCount)
|
const unreadCount = useInboxStore((s) => s.unreadCount)
|
||||||
|
|
||||||
// Le caselle PEC vengono caricate qui e condivise via React Query cache
|
// Le caselle PEC vengono caricate qui e condivise via React Query cache
|
||||||
@@ -416,6 +416,40 @@ export function Sidebar() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* ── Sezione Supervisione – visibile solo ai supervisor ── */}
|
||||||
|
{isSupervisor && (
|
||||||
|
<div>
|
||||||
|
{!collapsed && (
|
||||||
|
<>
|
||||||
|
<div className="border-t border-gray-700 mx-4 mb-3" />
|
||||||
|
<p className="px-4 mb-1.5 text-xs font-semibold text-cyan-400 uppercase tracking-wider">
|
||||||
|
Supervisione
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{collapsed && <div className="border-t border-gray-700 mx-3 mb-2" />}
|
||||||
|
|
||||||
|
<div className="space-y-0.5 px-2">
|
||||||
|
<NavLink
|
||||||
|
to="/mailboxes"
|
||||||
|
className={({ isActive }) =>
|
||||||
|
cn(
|
||||||
|
'flex items-center gap-3 px-3 py-2 rounded-lg text-sm font-medium transition-colors',
|
||||||
|
isActive
|
||||||
|
? 'bg-cyan-700 text-white'
|
||||||
|
: 'text-cyan-300 hover:bg-cyan-900/40 hover:text-white',
|
||||||
|
collapsed && 'justify-center px-2',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
title={collapsed ? 'Caselle PEC' : undefined}
|
||||||
|
>
|
||||||
|
<MailCheck className="h-5 w-5 flex-shrink-0" />
|
||||||
|
{!collapsed && <span>Caselle PEC</span>}
|
||||||
|
</NavLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* ── Sezione Amministrazione ── */}
|
{/* ── Sezione Amministrazione ── */}
|
||||||
{isAdmin && (
|
{isAdmin && (
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -2,6 +2,14 @@ import { useAuthStore } from '@/store/auth.store'
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Hook helper per accedere all'utente corrente e ai permessi.
|
* Hook helper per accedere all'utente corrente e ai permessi.
|
||||||
|
*
|
||||||
|
* Gerarchia ruoli:
|
||||||
|
* super_admin > admin > supervisor > operator > readonly
|
||||||
|
*
|
||||||
|
* Supervisor:
|
||||||
|
* - Lettura implicita su tutte le caselle del tenant
|
||||||
|
* - Invio solo se ha permesso esplicito can_send sulla casella
|
||||||
|
* - Non puo' gestire caselle, utenti, permessi o impostazioni
|
||||||
*/
|
*/
|
||||||
export function useAuth() {
|
export function useAuth() {
|
||||||
const user = useAuthStore((s) => s.user)
|
const user = useAuthStore((s) => s.user)
|
||||||
@@ -11,6 +19,8 @@ export function useAuth() {
|
|||||||
|
|
||||||
const isAdmin = user?.role === 'admin' || user?.role === 'super_admin'
|
const isAdmin = user?.role === 'admin' || user?.role === 'super_admin'
|
||||||
const isSuperAdmin = user?.role === 'super_admin'
|
const isSuperAdmin = user?.role === 'super_admin'
|
||||||
|
const isSupervisor = user?.role === 'supervisor'
|
||||||
|
const isSupervisorOrAdmin = isAdmin || isSupervisor
|
||||||
const canSend = user?.role !== 'readonly'
|
const canSend = user?.role !== 'readonly'
|
||||||
const canManage = isAdmin
|
const canManage = isAdmin
|
||||||
|
|
||||||
@@ -20,6 +30,8 @@ export function useAuth() {
|
|||||||
isLoading,
|
isLoading,
|
||||||
isAdmin,
|
isAdmin,
|
||||||
isSuperAdmin,
|
isSuperAdmin,
|
||||||
|
isSupervisor,
|
||||||
|
isSupervisorOrAdmin,
|
||||||
canSend,
|
canSend,
|
||||||
canManage,
|
canManage,
|
||||||
logout,
|
logout,
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import { mailboxesApi } from '@/api/mailboxes.api'
|
|||||||
import { getErrorMessage } from '@/api/client'
|
import { getErrorMessage } from '@/api/client'
|
||||||
import { formatDate, MAILBOX_STATUS_LABELS } from '@/lib/utils'
|
import { formatDate, MAILBOX_STATUS_LABELS } from '@/lib/utils'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
import { useAuth } from '@/hooks/useAuth'
|
||||||
import type { MailboxCreateRequest, MailboxResponse } from '@/types/api.types'
|
import type { MailboxCreateRequest, MailboxResponse } from '@/types/api.types'
|
||||||
|
|
||||||
const STATUS_COLORS = {
|
const STATUS_COLORS = {
|
||||||
@@ -45,6 +46,7 @@ const STATUS_ICONS = {
|
|||||||
|
|
||||||
export function MailboxesPage() {
|
export function MailboxesPage() {
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
|
const { isAdmin } = useAuth()
|
||||||
const [showCreateDialog, setShowCreateDialog] = useState(false)
|
const [showCreateDialog, setShowCreateDialog] = useState(false)
|
||||||
const [editingMailbox, setEditingMailbox] = useState<MailboxResponse | null>(null)
|
const [editingMailbox, setEditingMailbox] = useState<MailboxResponse | null>(null)
|
||||||
const [testingId, setTestingId] = useState<string | null>(null)
|
const [testingId, setTestingId] = useState<string | null>(null)
|
||||||
@@ -112,10 +114,12 @@ export function MailboxesPage() {
|
|||||||
<h1 className="text-xl font-semibold">Caselle PEC</h1>
|
<h1 className="text-xl font-semibold">Caselle PEC</h1>
|
||||||
<span className="text-sm text-muted-foreground">({mailboxes.length})</span>
|
<span className="text-sm text-muted-foreground">({mailboxes.length})</span>
|
||||||
</div>
|
</div>
|
||||||
<Button onClick={() => setShowCreateDialog(true)}>
|
{isAdmin && (
|
||||||
<Plus className="h-4 w-4 mr-2" />
|
<Button onClick={() => setShowCreateDialog(true)}>
|
||||||
Aggiungi casella
|
<Plus className="h-4 w-4 mr-2" />
|
||||||
</Button>
|
Aggiungi casella
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Contenuto */}
|
{/* Contenuto */}
|
||||||
@@ -129,12 +133,14 @@ export function MailboxesPage() {
|
|||||||
<MailCheck className="h-12 w-12 text-muted-foreground/30 mb-3" />
|
<MailCheck className="h-12 w-12 text-muted-foreground/30 mb-3" />
|
||||||
<p className="text-muted-foreground font-medium">Nessuna casella PEC configurata</p>
|
<p className="text-muted-foreground font-medium">Nessuna casella PEC configurata</p>
|
||||||
<p className="text-sm text-muted-foreground/70 mt-1">
|
<p className="text-sm text-muted-foreground/70 mt-1">
|
||||||
Aggiungi una casella per iniziare a gestire le PEC
|
{isAdmin ? 'Aggiungi una casella per iniziare a gestire le PEC' : 'Nessuna casella disponibile'}
|
||||||
</p>
|
</p>
|
||||||
<Button className="mt-4" onClick={() => setShowCreateDialog(true)}>
|
{isAdmin && (
|
||||||
<Plus className="h-4 w-4 mr-2" />
|
<Button className="mt-4" onClick={() => setShowCreateDialog(true)}>
|
||||||
Aggiungi casella
|
<Plus className="h-4 w-4 mr-2" />
|
||||||
</Button>
|
Aggiungi casella
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="grid gap-4">
|
<div className="grid gap-4">
|
||||||
@@ -186,51 +192,53 @@ export function MailboxesPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-2 flex-shrink-0">
|
{isAdmin && (
|
||||||
{/* Test connessione */}
|
<div className="flex items-center gap-2 flex-shrink-0">
|
||||||
<Button
|
{/* Test connessione */}
|
||||||
variant="outline"
|
<Button
|
||||||
size="sm"
|
variant="outline"
|
||||||
onClick={() => handleTest(mailbox)}
|
size="sm"
|
||||||
isLoading={testingId === mailbox.id}
|
onClick={() => handleTest(mailbox)}
|
||||||
title="Testa connessione IMAP"
|
isLoading={testingId === mailbox.id}
|
||||||
>
|
title="Testa connessione IMAP"
|
||||||
<TestTube className="h-4 w-4 mr-1" />
|
>
|
||||||
Test
|
<TestTube className="h-4 w-4 mr-1" />
|
||||||
</Button>
|
Test
|
||||||
|
</Button>
|
||||||
|
|
||||||
{/* Forza sincronizzazione */}
|
{/* Forza sincronizzazione */}
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => handleForceSync(mailbox)}
|
onClick={() => handleForceSync(mailbox)}
|
||||||
isLoading={syncingId === mailbox.id}
|
isLoading={syncingId === mailbox.id}
|
||||||
title="Forza sincronizzazione IMAP immediata"
|
title="Forza sincronizzazione IMAP immediata"
|
||||||
disabled={mailbox.status === 'deleted'}
|
disabled={mailbox.status === 'deleted'}
|
||||||
>
|
>
|
||||||
<RefreshCw className="h-4 w-4 mr-1" />
|
<RefreshCw className="h-4 w-4 mr-1" />
|
||||||
Sync
|
Sync
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{/* Modifica */}
|
{/* Modifica */}
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => setEditingMailbox(mailbox)}
|
onClick={() => setEditingMailbox(mailbox)}
|
||||||
>
|
>
|
||||||
<Edit className="h-4 w-4" />
|
<Edit className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{/* Elimina */}
|
{/* Elimina */}
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => handleDelete(mailbox)}
|
onClick={() => handleDelete(mailbox)}
|
||||||
className="text-destructive hover:bg-destructive hover:text-destructive-foreground"
|
className="text-destructive hover:bg-destructive hover:text-destructive-foreground"
|
||||||
>
|
>
|
||||||
<Trash2 className="h-4 w-4" />
|
<Trash2 className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
|||||||
Reference in New Issue
Block a user