fix log
This commit is contained in:
168
LOG_SYSTEM.md
Normal file
168
LOG_SYSTEM.md
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
# Sistema di Logging - Territory Manager
|
||||||
|
|
||||||
|
## Panoramica
|
||||||
|
|
||||||
|
Il sistema di logging è stato implementato per tracciare tutte le azioni eseguite dagli utenti all'interno dell'applicazione Territory Manager.
|
||||||
|
|
||||||
|
## Funzionalità Implementate
|
||||||
|
|
||||||
|
### 1. Database
|
||||||
|
- **Tabella `activity_logs`**: Memorizza tutti i log delle attività
|
||||||
|
- `user_id`: ID dell'utente che ha eseguito l'azione
|
||||||
|
- `username`: Nome utente per riferimento rapido
|
||||||
|
- `action_type`: Tipo di azione (login, logout, create, update, delete, assign, return, export)
|
||||||
|
- `action_description`: Descrizione dettagliata dell'azione
|
||||||
|
- `entity_type`: Tipo di entità coinvolta (territory, assignment, user, config, auth)
|
||||||
|
- `entity_id`: ID dell'entità coinvolta
|
||||||
|
- `ip_address`: Indirizzo IP dell'utente
|
||||||
|
- `user_agent`: User agent del browser
|
||||||
|
- `created_at`: Timestamp dell'azione
|
||||||
|
|
||||||
|
### 2. Funzioni di Logging (functions.php)
|
||||||
|
|
||||||
|
#### `logActivity($action_type, $action_description, $entity_type, $entity_id)`
|
||||||
|
Registra un'attività nel log. Cattura automaticamente:
|
||||||
|
- Utente corrente dalla sessione
|
||||||
|
- Indirizzo IP
|
||||||
|
- User Agent del browser
|
||||||
|
|
||||||
|
#### `getActivityLogs($filters, $page, $per_page)`
|
||||||
|
Recupera i log con filtri e paginazione. Supporta filtri per:
|
||||||
|
- Utente
|
||||||
|
- Tipo di azione
|
||||||
|
- Tipo di entità
|
||||||
|
- Intervallo di date
|
||||||
|
- Ricerca testuale
|
||||||
|
|
||||||
|
### 3. Pagina Visualizzazione Log (logs.php)
|
||||||
|
**Accesso**: Solo amministratori
|
||||||
|
|
||||||
|
Funzionalità:
|
||||||
|
- Visualizzazione log con tabella paginata
|
||||||
|
- Filtri multipli:
|
||||||
|
- Ricerca testuale
|
||||||
|
- Utente
|
||||||
|
- Tipo di azione
|
||||||
|
- Tipo di entità
|
||||||
|
- Data da/a
|
||||||
|
- Paginazione (50 log per pagina)
|
||||||
|
- Badge colorati per tipo di azione
|
||||||
|
- Link per esportazione PDF
|
||||||
|
|
||||||
|
### 4. Esportazione PDF (export_logs_pdf.php)
|
||||||
|
**Accesso**: Solo amministratori
|
||||||
|
|
||||||
|
Funzionalità:
|
||||||
|
- Esporta tutti i log filtrati in formato PDF
|
||||||
|
- Include gli stessi filtri della pagina di visualizzazione
|
||||||
|
- Genera report con:
|
||||||
|
- Elenco completo dei log
|
||||||
|
- Riepilogo per tipo di azione
|
||||||
|
- Riepilogo per utente
|
||||||
|
- Timestamp di generazione
|
||||||
|
- Informazioni sui filtri applicati
|
||||||
|
|
||||||
|
### 5. Azioni Tracciate
|
||||||
|
|
||||||
|
#### Autenticazione
|
||||||
|
- `login`: Accesso al sistema
|
||||||
|
- `logout`: Uscita dal sistema
|
||||||
|
|
||||||
|
#### Territori
|
||||||
|
- `create`: Creazione nuovo territorio
|
||||||
|
- `update`: Modifica territorio esistente
|
||||||
|
- `delete`: Eliminazione territorio
|
||||||
|
|
||||||
|
#### Assegnazioni
|
||||||
|
- `assign`: Assegnazione territorio a un proclamatore
|
||||||
|
- `return`: Riconsegna territorio
|
||||||
|
|
||||||
|
#### Utenti
|
||||||
|
- `create`: Creazione nuovo utente
|
||||||
|
- `update`: Modifica password
|
||||||
|
- `delete`: Eliminazione utente
|
||||||
|
|
||||||
|
#### Configurazioni
|
||||||
|
- `update`: Modifica configurazioni di sistema
|
||||||
|
|
||||||
|
#### Log
|
||||||
|
- `export`: Esportazione log in PDF
|
||||||
|
|
||||||
|
## Integrazione
|
||||||
|
|
||||||
|
Il logging è stato integrato in tutte le operazioni principali:
|
||||||
|
- `login.php`: Log di accesso
|
||||||
|
- `logout.php`: Log di uscita
|
||||||
|
- `territories.php`: Log di creazione, modifica ed eliminazione territori
|
||||||
|
- `assignments.php`: Log di assegnazione e riconsegna
|
||||||
|
- `settings.php`: Log di modifiche configurazioni e gestione utenti
|
||||||
|
- `export_logs_pdf.php`: Log di esportazione
|
||||||
|
|
||||||
|
## Menu di Navigazione
|
||||||
|
|
||||||
|
Il link "Log" è stato aggiunto nel menu principale, visibile solo agli amministratori.
|
||||||
|
|
||||||
|
## Utilizzo
|
||||||
|
|
||||||
|
### Visualizzare i Log
|
||||||
|
1. Accedere come amministratore
|
||||||
|
2. Cliccare su "Log" nel menu
|
||||||
|
3. Utilizzare i filtri per trovare log specifici
|
||||||
|
|
||||||
|
### Esportare i Log
|
||||||
|
1. Dalla pagina Log, applicare eventuali filtri
|
||||||
|
2. Cliccare su "Esporta PDF"
|
||||||
|
3. Si aprirà una nuova finestra con il report
|
||||||
|
4. Utilizzare il pulsante "Stampa / Salva come PDF" del browser
|
||||||
|
|
||||||
|
## Note Tecniche
|
||||||
|
|
||||||
|
- Il sistema cattura automaticamente IP e User Agent per tracciabilità
|
||||||
|
- I log sono legati agli utenti tramite foreign key (cascade on delete)
|
||||||
|
- La paginazione limita il carico sul database
|
||||||
|
- L'export PDF è ottimizzato per la stampa (orientamento landscape)
|
||||||
|
- I filtri sono persistenti durante la navigazione
|
||||||
|
|
||||||
|
## Sicurezza
|
||||||
|
|
||||||
|
- Solo gli amministratori possono accedere ai log
|
||||||
|
- I log non possono essere modificati o eliminati dall'interfaccia
|
||||||
|
- Tutte le operazioni sensibili vengono registrate
|
||||||
|
- Gli IP vengono tracciati per audit trail
|
||||||
|
|
||||||
|
## Manutenzione
|
||||||
|
|
||||||
|
Per pulire vecchi log (da eseguire manualmente nel database):
|
||||||
|
```sql
|
||||||
|
-- Elimina log più vecchi di 1 anno
|
||||||
|
DELETE FROM activity_logs WHERE created_at < DATE_SUB(NOW(), INTERVAL 1 YEAR);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Database Migration
|
||||||
|
|
||||||
|
Per applicare le modifiche al database esistente:
|
||||||
|
```sql
|
||||||
|
-- Eseguire lo script SQL aggiornato
|
||||||
|
SOURCE database.sql;
|
||||||
|
```
|
||||||
|
|
||||||
|
O solo la parte relativa ai log:
|
||||||
|
```sql
|
||||||
|
CREATE TABLE IF NOT EXISTS activity_logs (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
user_id INT NOT NULL,
|
||||||
|
username VARCHAR(50) NOT NULL,
|
||||||
|
action_type VARCHAR(50) NOT NULL,
|
||||||
|
action_description TEXT NOT NULL,
|
||||||
|
entity_type VARCHAR(50),
|
||||||
|
entity_id INT,
|
||||||
|
ip_address VARCHAR(45),
|
||||||
|
user_agent TEXT,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||||
|
INDEX idx_user (user_id),
|
||||||
|
INDEX idx_action_type (action_type),
|
||||||
|
INDEX idx_created_at (created_at),
|
||||||
|
INDEX idx_entity (entity_type, entity_id)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
```
|
||||||
@@ -52,6 +52,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
|
|
||||||
if ($result) {
|
if ($result) {
|
||||||
$assignment_id = $db->getConnection()->lastInsertId();
|
$assignment_id = $db->getConnection()->lastInsertId();
|
||||||
|
$territory = $db->fetchOne("SELECT numero, zona FROM territories WHERE id = ?", [$territory_id]);
|
||||||
|
logActivity('assign', "Assegnato territorio {$territory['numero']} - {$territory['zona']} a $assigned_to", 'assignment', $assignment_id);
|
||||||
setFlashMessage('Territorio assegnato con successo', 'success');
|
setFlashMessage('Territorio assegnato con successo', 'success');
|
||||||
header("Location: assignments.php?action=view&id=$assignment_id");
|
header("Location: assignments.php?action=view&id=$assignment_id");
|
||||||
} else {
|
} else {
|
||||||
@@ -65,12 +67,22 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
$territory_id = (int)$_POST['territory_id'];
|
$territory_id = (int)$_POST['territory_id'];
|
||||||
$returned_date = $_POST['returned_date'];
|
$returned_date = $_POST['returned_date'];
|
||||||
|
|
||||||
|
// Recupera info assegnazione
|
||||||
|
$assignment = $db->fetchOne(
|
||||||
|
"SELECT a.id, a.assigned_to, t.numero, t.zona
|
||||||
|
FROM assignments a
|
||||||
|
JOIN territories t ON a.territory_id = t.id
|
||||||
|
WHERE a.territory_id = ? AND a.returned_date IS NULL",
|
||||||
|
[$territory_id]
|
||||||
|
);
|
||||||
|
|
||||||
$result = $db->query(
|
$result = $db->query(
|
||||||
"UPDATE assignments SET returned_date = ? WHERE territory_id = ? AND returned_date IS NULL",
|
"UPDATE assignments SET returned_date = ? WHERE territory_id = ? AND returned_date IS NULL",
|
||||||
[$returned_date, $territory_id]
|
[$returned_date, $territory_id]
|
||||||
);
|
);
|
||||||
|
|
||||||
if ($result) {
|
if ($result && $assignment) {
|
||||||
|
logActivity('return', "Riconsegnato territorio {$assignment['numero']} - {$assignment['zona']} da {$assignment['assigned_to']}", 'assignment', $assignment['id']);
|
||||||
setFlashMessage('Territorio riconsegnato con successo', 'success');
|
setFlashMessage('Territorio riconsegnato con successo', 'success');
|
||||||
} else {
|
} else {
|
||||||
setFlashMessage('Errore durante la riconsegna', 'error');
|
setFlashMessage('Errore durante la riconsegna', 'error');
|
||||||
|
|||||||
19
database.sql
19
database.sql
@@ -100,3 +100,22 @@ WHERE t.id NOT IN (
|
|||||||
)
|
)
|
||||||
GROUP BY t.id, t.numero, t.zona, t.tipologia, t.image_path
|
GROUP BY t.id, t.numero, t.zona, t.tipologia, t.image_path
|
||||||
ORDER BY last_returned_date ASC NULLS FIRST;
|
ORDER BY last_returned_date ASC NULLS FIRST;
|
||||||
|
|
||||||
|
-- Tabella log attività
|
||||||
|
CREATE TABLE IF NOT EXISTS activity_logs (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
user_id INT NOT NULL,
|
||||||
|
username VARCHAR(50) NOT NULL,
|
||||||
|
action_type VARCHAR(50) NOT NULL,
|
||||||
|
action_description TEXT NOT NULL,
|
||||||
|
entity_type VARCHAR(50),
|
||||||
|
entity_id INT,
|
||||||
|
ip_address VARCHAR(45),
|
||||||
|
user_agent TEXT,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||||
|
INDEX idx_user (user_id),
|
||||||
|
INDEX idx_action_type (action_type),
|
||||||
|
INDEX idx_created_at (created_at),
|
||||||
|
INDEX idx_entity (entity_type, entity_id)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|||||||
284
export_logs_pdf.php
Normal file
284
export_logs_pdf.php
Normal file
@@ -0,0 +1,284 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Export Log PDF
|
||||||
|
* Territory Manager
|
||||||
|
* Esporta i log delle attività in formato PDF
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once 'config.php';
|
||||||
|
require_once 'functions.php';
|
||||||
|
require_once 'db.php';
|
||||||
|
|
||||||
|
requireAdmin(); // Solo admin possono esportare i log
|
||||||
|
|
||||||
|
$db = getDB();
|
||||||
|
|
||||||
|
// Applica gli stessi filtri della pagina logs.php
|
||||||
|
$filters = [];
|
||||||
|
|
||||||
|
if (!empty($_GET['user_id'])) {
|
||||||
|
$filters['user_id'] = intval($_GET['user_id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($_GET['action_type'])) {
|
||||||
|
$filters['action_type'] = $_GET['action_type'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($_GET['entity_type'])) {
|
||||||
|
$filters['entity_type'] = $_GET['entity_type'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($_GET['date_from'])) {
|
||||||
|
$filters['date_from'] = $_GET['date_from'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($_GET['date_to'])) {
|
||||||
|
$filters['date_to'] = $_GET['date_to'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($_GET['search'])) {
|
||||||
|
$filters['search'] = $_GET['search'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ottieni tutti i log (senza limite di paginazione per l'export)
|
||||||
|
$where = [];
|
||||||
|
$params = [];
|
||||||
|
|
||||||
|
if (!empty($filters['user_id'])) {
|
||||||
|
$where[] = "user_id = ?";
|
||||||
|
$params[] = $filters['user_id'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($filters['action_type'])) {
|
||||||
|
$where[] = "action_type = ?";
|
||||||
|
$params[] = $filters['action_type'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($filters['entity_type'])) {
|
||||||
|
$where[] = "entity_type = ?";
|
||||||
|
$params[] = $filters['entity_type'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($filters['date_from'])) {
|
||||||
|
$where[] = "DATE(created_at) >= ?";
|
||||||
|
$params[] = $filters['date_from'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($filters['date_to'])) {
|
||||||
|
$where[] = "DATE(created_at) <= ?";
|
||||||
|
$params[] = $filters['date_to'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($filters['search'])) {
|
||||||
|
$where[] = "(action_description LIKE ? OR username LIKE ?)";
|
||||||
|
$search_term = '%' . $filters['search'] . '%';
|
||||||
|
$params[] = $search_term;
|
||||||
|
$params[] = $search_term;
|
||||||
|
}
|
||||||
|
|
||||||
|
$where_clause = !empty($where) ? 'WHERE ' . implode(' AND ', $where) : '';
|
||||||
|
|
||||||
|
$logs = $db->fetchAll(
|
||||||
|
"SELECT * FROM activity_logs
|
||||||
|
$where_clause
|
||||||
|
ORDER BY created_at DESC",
|
||||||
|
$params
|
||||||
|
);
|
||||||
|
|
||||||
|
// Classe semplice per generare PDF (HTML per stampa)
|
||||||
|
class LogPDF {
|
||||||
|
private $content = '';
|
||||||
|
|
||||||
|
public function addTitle($title) {
|
||||||
|
$this->content .= "<h1 style='color: #2c3e50; margin-bottom: 20px; text-align: center;'>$title</h1>";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addSubtitle($subtitle) {
|
||||||
|
$this->content .= "<h2 style='color: #34495e; margin: 20px 0 10px 0; font-size: 16px;'>$subtitle</h2>";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addText($text) {
|
||||||
|
$this->content .= "<p style='margin: 5px 0; color: #555;'>$text</p>";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addTable($headers, $rows) {
|
||||||
|
$this->content .= "<table style='width: 100%; border-collapse: collapse; margin: 20px 0; font-size: 11px;'>";
|
||||||
|
$this->content .= "<thead><tr style='background-color: #34495e; color: white;'>";
|
||||||
|
foreach ($headers as $header) {
|
||||||
|
$this->content .= "<th style='padding: 8px 6px; border: 1px solid #ddd; text-align: left; font-weight: bold;'>$header</th>";
|
||||||
|
}
|
||||||
|
$this->content .= "</tr></thead><tbody>";
|
||||||
|
|
||||||
|
$odd = true;
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
$bgColor = $odd ? '#ffffff' : '#f8f9fa';
|
||||||
|
$this->content .= "<tr style='background-color: $bgColor;'>";
|
||||||
|
foreach ($row as $cell) {
|
||||||
|
$this->content .= "<td style='padding: 6px; border: 1px solid #ddd;'>$cell</td>";
|
||||||
|
}
|
||||||
|
$this->content .= "</tr>";
|
||||||
|
$odd = !$odd;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->content .= "</tbody></table>";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function output($filename) {
|
||||||
|
header('Content-Type: text/html; charset=utf-8');
|
||||||
|
header('Content-Disposition: inline; filename="' . $filename . '.html"');
|
||||||
|
|
||||||
|
echo "<!DOCTYPE html>
|
||||||
|
<html lang='it'>
|
||||||
|
<head>
|
||||||
|
<meta charset='UTF-8'>
|
||||||
|
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
|
||||||
|
<title>$filename</title>
|
||||||
|
<style>
|
||||||
|
* { box-sizing: border-box; }
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', Arial, sans-serif;
|
||||||
|
padding: 20px;
|
||||||
|
margin: 0;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
@media print {
|
||||||
|
body { padding: 10px; }
|
||||||
|
.no-print { display: none; }
|
||||||
|
table { page-break-inside: auto; }
|
||||||
|
tr { page-break-inside: avoid; page-break-after: auto; }
|
||||||
|
thead { display: table-header-group; }
|
||||||
|
}
|
||||||
|
@page {
|
||||||
|
size: A4 landscape;
|
||||||
|
margin: 1cm;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>";
|
||||||
|
echo "<div class='no-print' style='margin-bottom: 20px; text-align: center;'>";
|
||||||
|
echo "<button onclick='window.print()' style='padding: 10px 20px; background: #007bff; color: white; border: none; border-radius: 5px; cursor: pointer; font-size: 14px;'>Stampa / Salva come PDF</button>";
|
||||||
|
echo "<button onclick='window.close()' style='padding: 10px 20px; background: #6c757d; color: white; border: none; border-radius: 5px; cursor: pointer; font-size: 14px; margin-left: 10px;'>Chiudi</button>";
|
||||||
|
echo "</div>";
|
||||||
|
echo $this->content;
|
||||||
|
echo "<div style='margin-top: 40px; text-align: center; color: #7f8c8d; font-size: 11px;'>";
|
||||||
|
echo "<p>Generato il " . date('d/m/Y H:i') . " da " . htmlspecialchars($_SESSION['username']) . " - " . APP_NAME . "</p>";
|
||||||
|
echo "</div>";
|
||||||
|
echo "</body></html>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdf = new LogPDF();
|
||||||
|
|
||||||
|
// Titolo
|
||||||
|
$pdf->addTitle('Log Attività Sistema');
|
||||||
|
|
||||||
|
// Informazioni sul report
|
||||||
|
$info_text = "Report generato il " . date('d/m/Y H:i');
|
||||||
|
if (!empty($filters)) {
|
||||||
|
$info_text .= " - Filtri applicati: ";
|
||||||
|
$filter_parts = [];
|
||||||
|
|
||||||
|
if (!empty($filters['user_id'])) {
|
||||||
|
$user = $db->fetchOne("SELECT username FROM users WHERE id = ?", [$filters['user_id']]);
|
||||||
|
$filter_parts[] = "Utente: " . htmlspecialchars($user['username']);
|
||||||
|
}
|
||||||
|
if (!empty($filters['action_type'])) {
|
||||||
|
$filter_parts[] = "Azione: " . htmlspecialchars($filters['action_type']);
|
||||||
|
}
|
||||||
|
if (!empty($filters['entity_type'])) {
|
||||||
|
$filter_parts[] = "Entità: " . htmlspecialchars($filters['entity_type']);
|
||||||
|
}
|
||||||
|
if (!empty($filters['date_from'])) {
|
||||||
|
$filter_parts[] = "Dal: " . formatDate($filters['date_from']);
|
||||||
|
}
|
||||||
|
if (!empty($filters['date_to'])) {
|
||||||
|
$filter_parts[] = "Al: " . formatDate($filters['date_to']);
|
||||||
|
}
|
||||||
|
if (!empty($filters['search'])) {
|
||||||
|
$filter_parts[] = "Ricerca: " . htmlspecialchars($filters['search']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$info_text .= implode(', ', $filter_parts);
|
||||||
|
}
|
||||||
|
$pdf->addText($info_text);
|
||||||
|
$pdf->addText("Totale log: " . count($logs));
|
||||||
|
|
||||||
|
// Prepara i dati per la tabella
|
||||||
|
$headers = ['Data/Ora', 'Utente', 'Azione', 'Descrizione', 'Entità', 'IP'];
|
||||||
|
$rows = [];
|
||||||
|
|
||||||
|
foreach ($logs as $log) {
|
||||||
|
$action_badge = strtoupper(htmlspecialchars($log['action_type']));
|
||||||
|
|
||||||
|
$entity_info = '-';
|
||||||
|
if ($log['entity_type']) {
|
||||||
|
$entity_info = htmlspecialchars($log['entity_type']);
|
||||||
|
if ($log['entity_id']) {
|
||||||
|
$entity_info .= ' #' . $log['entity_id'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$rows[] = [
|
||||||
|
formatDateTime($log['created_at']),
|
||||||
|
htmlspecialchars($log['username']),
|
||||||
|
$action_badge,
|
||||||
|
htmlspecialchars($log['action_description']),
|
||||||
|
$entity_info,
|
||||||
|
htmlspecialchars($log['ip_address'])
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdf->addTable($headers, $rows);
|
||||||
|
|
||||||
|
// Riepilogo per tipo di azione
|
||||||
|
$action_summary = $db->fetchAll(
|
||||||
|
"SELECT action_type, COUNT(*) as count
|
||||||
|
FROM activity_logs
|
||||||
|
$where_clause
|
||||||
|
GROUP BY action_type
|
||||||
|
ORDER BY count DESC",
|
||||||
|
$params
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!empty($action_summary)) {
|
||||||
|
$pdf->addSubtitle('Riepilogo per Tipo di Azione');
|
||||||
|
$summary_headers = ['Tipo Azione', 'Numero Occorrenze'];
|
||||||
|
$summary_rows = [];
|
||||||
|
foreach ($action_summary as $summary) {
|
||||||
|
$summary_rows[] = [
|
||||||
|
strtoupper(htmlspecialchars($summary['action_type'])),
|
||||||
|
number_format($summary['count'])
|
||||||
|
];
|
||||||
|
}
|
||||||
|
$pdf->addTable($summary_headers, $summary_rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Riepilogo per utente
|
||||||
|
$user_summary = $db->fetchAll(
|
||||||
|
"SELECT username, COUNT(*) as count
|
||||||
|
FROM activity_logs
|
||||||
|
$where_clause
|
||||||
|
GROUP BY username
|
||||||
|
ORDER BY count DESC",
|
||||||
|
$params
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!empty($user_summary)) {
|
||||||
|
$pdf->addSubtitle('Riepilogo per Utente');
|
||||||
|
$user_headers = ['Utente', 'Numero Azioni'];
|
||||||
|
$user_rows = [];
|
||||||
|
foreach ($user_summary as $summary) {
|
||||||
|
$user_rows[] = [
|
||||||
|
htmlspecialchars($summary['username']),
|
||||||
|
number_format($summary['count'])
|
||||||
|
];
|
||||||
|
}
|
||||||
|
$pdf->addTable($user_headers, $user_rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output del PDF
|
||||||
|
$filename = 'log_attivita_' . date('Y-m-d_H-i-s');
|
||||||
|
$pdf->output($filename);
|
||||||
|
|
||||||
|
// Log dell'esportazione
|
||||||
|
logActivity('export', 'Esportazione log attività in PDF', 'logs', null);
|
||||||
107
functions.php
107
functions.php
@@ -110,3 +110,110 @@ function formatDateTime($datetime) {
|
|||||||
if (empty($datetime)) return '-';
|
if (empty($datetime)) return '-';
|
||||||
return date('d/m/Y H:i', strtotime($datetime));
|
return date('d/m/Y H:i', strtotime($datetime));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registra un'attività nel log
|
||||||
|
*
|
||||||
|
* @param string $action_type Tipo di azione (es: 'login', 'create', 'update', 'delete', 'assign', 'return')
|
||||||
|
* @param string $action_description Descrizione dell'azione
|
||||||
|
* @param string $entity_type Tipo di entità (es: 'territory', 'assignment', 'user', 'config')
|
||||||
|
* @param int $entity_id ID dell'entità coinvolta
|
||||||
|
*/
|
||||||
|
function logActivity($action_type, $action_description, $entity_type = null, $entity_id = null) {
|
||||||
|
if (!isLoggedIn()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$db = getDB();
|
||||||
|
|
||||||
|
// Ottieni informazioni sull'utente e sulla richiesta
|
||||||
|
$user_id = $_SESSION['user_id'];
|
||||||
|
$username = $_SESSION['username'];
|
||||||
|
$ip_address = $_SERVER['REMOTE_ADDR'] ?? null;
|
||||||
|
$user_agent = $_SERVER['HTTP_USER_AGENT'] ?? null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
$db->execute(
|
||||||
|
"INSERT INTO activity_logs
|
||||||
|
(user_id, username, action_type, action_description, entity_type, entity_id, ip_address, user_agent)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
||||||
|
[$user_id, $username, $action_type, $action_description, $entity_type, $entity_id, $ip_address, $user_agent]
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log("Errore nel log dell'attività: " . $e->getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ottieni i log delle attività con filtri e paginazione
|
||||||
|
*
|
||||||
|
* @param array $filters Array di filtri (user_id, action_type, entity_type, date_from, date_to)
|
||||||
|
* @param int $page Numero pagina
|
||||||
|
* @param int $per_page Elementi per pagina
|
||||||
|
* @return array Array con 'logs' e 'total'
|
||||||
|
*/
|
||||||
|
function getActivityLogs($filters = [], $page = 1, $per_page = 50) {
|
||||||
|
$db = getDB();
|
||||||
|
|
||||||
|
$where = [];
|
||||||
|
$params = [];
|
||||||
|
|
||||||
|
if (!empty($filters['user_id'])) {
|
||||||
|
$where[] = "user_id = ?";
|
||||||
|
$params[] = $filters['user_id'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($filters['action_type'])) {
|
||||||
|
$where[] = "action_type = ?";
|
||||||
|
$params[] = $filters['action_type'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($filters['entity_type'])) {
|
||||||
|
$where[] = "entity_type = ?";
|
||||||
|
$params[] = $filters['entity_type'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($filters['date_from'])) {
|
||||||
|
$where[] = "DATE(created_at) >= ?";
|
||||||
|
$params[] = $filters['date_from'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($filters['date_to'])) {
|
||||||
|
$where[] = "DATE(created_at) <= ?";
|
||||||
|
$params[] = $filters['date_to'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($filters['search'])) {
|
||||||
|
$where[] = "(action_description LIKE ? OR username LIKE ?)";
|
||||||
|
$search_term = '%' . $filters['search'] . '%';
|
||||||
|
$params[] = $search_term;
|
||||||
|
$params[] = $search_term;
|
||||||
|
}
|
||||||
|
|
||||||
|
$where_clause = !empty($where) ? 'WHERE ' . implode(' AND ', $where) : '';
|
||||||
|
|
||||||
|
// Conta totale
|
||||||
|
$total = $db->fetchOne(
|
||||||
|
"SELECT COUNT(*) as count FROM activity_logs $where_clause",
|
||||||
|
$params
|
||||||
|
)['count'];
|
||||||
|
|
||||||
|
// Ottieni log con paginazione
|
||||||
|
$offset = ($page - 1) * $per_page;
|
||||||
|
$logs = $db->fetchAll(
|
||||||
|
"SELECT * FROM activity_logs
|
||||||
|
$where_clause
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
LIMIT ? OFFSET ?",
|
||||||
|
array_merge($params, [$per_page, $offset])
|
||||||
|
);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'logs' => $logs,
|
||||||
|
'total' => $total,
|
||||||
|
'pages' => ceil($total / $per_page),
|
||||||
|
'current_page' => $page
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ $current_page = basename($_SERVER['PHP_SELF'], '.php');
|
|||||||
<li><a href="assignments.php" class="<?php echo $current_page === 'assignments' ? 'active' : ''; ?>">Assegnazioni</a></li>
|
<li><a href="assignments.php" class="<?php echo $current_page === 'assignments' ? 'active' : ''; ?>">Assegnazioni</a></li>
|
||||||
<li><a href="statistics.php" class="<?php echo $current_page === 'statistics' ? 'active' : ''; ?>">Statistiche</a></li>
|
<li><a href="statistics.php" class="<?php echo $current_page === 'statistics' ? 'active' : ''; ?>">Statistiche</a></li>
|
||||||
<?php if (isAdmin()): ?>
|
<?php if (isAdmin()): ?>
|
||||||
|
<li><a href="logs.php" class="<?php echo $current_page === 'logs' ? 'active' : ''; ?>">Log</a></li>
|
||||||
<li><a href="settings.php" class="<?php echo $current_page === 'settings' ? 'active' : ''; ?>">Impostazioni</a></li>
|
<li><a href="settings.php" class="<?php echo $current_page === 'settings' ? 'active' : ''; ?>">Impostazioni</a></li>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
$error = 'Inserire username e password';
|
$error = 'Inserire username e password';
|
||||||
} else {
|
} else {
|
||||||
if (login($username, $password)) {
|
if (login($username, $password)) {
|
||||||
|
logActivity('login', 'Accesso effettuato al sistema', 'auth', null);
|
||||||
header('Location: index.php');
|
header('Location: index.php');
|
||||||
exit;
|
exit;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
require_once 'config.php';
|
require_once 'config.php';
|
||||||
require_once 'functions.php';
|
require_once 'functions.php';
|
||||||
|
|
||||||
|
logActivity('logout', 'Uscita dal sistema', 'auth', null);
|
||||||
logout();
|
logout();
|
||||||
header('Location: login.php');
|
header('Location: login.php');
|
||||||
exit;
|
exit;
|
||||||
|
|||||||
301
logs.php
Normal file
301
logs.php
Normal file
@@ -0,0 +1,301 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Pagina Visualizzazione Log Attività
|
||||||
|
* Territory Manager
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once 'config.php';
|
||||||
|
require_once 'functions.php';
|
||||||
|
require_once 'db.php';
|
||||||
|
|
||||||
|
requireAdmin(); // Solo gli admin possono visualizzare i log
|
||||||
|
|
||||||
|
$page_title = 'Log Attività';
|
||||||
|
$db = getDB();
|
||||||
|
|
||||||
|
// Parametri per filtri e paginazione
|
||||||
|
$filters = [];
|
||||||
|
$page = isset($_GET['page']) ? max(1, intval($_GET['page'])) : 1;
|
||||||
|
$per_page = 50;
|
||||||
|
|
||||||
|
// Applica filtri
|
||||||
|
if (!empty($_GET['user_id'])) {
|
||||||
|
$filters['user_id'] = intval($_GET['user_id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($_GET['action_type'])) {
|
||||||
|
$filters['action_type'] = $_GET['action_type'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($_GET['entity_type'])) {
|
||||||
|
$filters['entity_type'] = $_GET['entity_type'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($_GET['date_from'])) {
|
||||||
|
$filters['date_from'] = $_GET['date_from'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($_GET['date_to'])) {
|
||||||
|
$filters['date_to'] = $_GET['date_to'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($_GET['search'])) {
|
||||||
|
$filters['search'] = $_GET['search'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ottieni log
|
||||||
|
$result = getActivityLogs($filters, $page, $per_page);
|
||||||
|
$logs = $result['logs'];
|
||||||
|
$total = $result['total'];
|
||||||
|
$total_pages = $result['pages'];
|
||||||
|
|
||||||
|
// Ottieni lista utenti per filtro
|
||||||
|
$users = $db->fetchAll("SELECT id, username FROM users ORDER BY username");
|
||||||
|
|
||||||
|
// Ottieni tipi di azione unici
|
||||||
|
$action_types = $db->fetchAll("SELECT DISTINCT action_type FROM activity_logs ORDER BY action_type");
|
||||||
|
|
||||||
|
// Ottieni tipi di entità unici
|
||||||
|
$entity_types = $db->fetchAll("SELECT DISTINCT entity_type FROM activity_logs WHERE entity_type IS NOT NULL ORDER BY entity_type");
|
||||||
|
|
||||||
|
include 'header.php';
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="page-header">
|
||||||
|
<h1>
|
||||||
|
<svg width="24" height="24" fill="currentColor" viewBox="0 0 16 16">
|
||||||
|
<path d="M3 2.5a2.5 2.5 0 0 1 5 0 2.5 2.5 0 0 1 5 0v.006c0 .07 0 .27-.038.494H15a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1v7.5a1.5 1.5 0 0 1-1.5 1.5h-11A1.5 1.5 0 0 1 1 14.5V7a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h2.038A2.968 2.968 0 0 1 3 2.506V2.5zm1.068.5H7v-.5a1.5 1.5 0 1 0-3 0c0 .085.002.274.045.43a.522.522 0 0 0 .023.07zM9 3h2.932a.56.56 0 0 0 .023-.07c.043-.156.045-.345.045-.43a1.5 1.5 0 0 0-3 0V3zM1 4v2h6V4H1zm8 0v2h6V4H9zm5 3H9v8h4.5a.5.5 0 0 0 .5-.5V7zm-7 8V7H2v7.5a.5.5 0 0 0 .5.5H7z"/>
|
||||||
|
</svg>
|
||||||
|
<?php echo $page_title; ?>
|
||||||
|
</h1>
|
||||||
|
<div class="page-actions">
|
||||||
|
<a href="export_logs_pdf.php?<?php echo http_build_query($filters); ?>" class="btn btn-primary" target="_blank">
|
||||||
|
<svg width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
||||||
|
<path d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z"/>
|
||||||
|
<path d="M7.646 11.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V1.5a.5.5 0 0 0-1 0v8.793L5.354 8.146a.5.5 0 1 0-.708.708l3 3z"/>
|
||||||
|
</svg>
|
||||||
|
Esporta PDF
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Filtri -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3>Filtri</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form method="GET" action="logs.php" class="filter-form">
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="search">Cerca</label>
|
||||||
|
<input type="text" id="search" name="search" class="form-control"
|
||||||
|
placeholder="Cerca nella descrizione o utente..."
|
||||||
|
value="<?php echo htmlspecialchars($_GET['search'] ?? ''); ?>">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="user_id">Utente</label>
|
||||||
|
<select id="user_id" name="user_id" class="form-control">
|
||||||
|
<option value="">Tutti gli utenti</option>
|
||||||
|
<?php foreach ($users as $user): ?>
|
||||||
|
<option value="<?php echo $user['id']; ?>"
|
||||||
|
<?php echo (isset($_GET['user_id']) && $_GET['user_id'] == $user['id']) ? 'selected' : ''; ?>>
|
||||||
|
<?php echo htmlspecialchars($user['username']); ?>
|
||||||
|
</option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="action_type">Tipo Azione</label>
|
||||||
|
<select id="action_type" name="action_type" class="form-control">
|
||||||
|
<option value="">Tutte le azioni</option>
|
||||||
|
<?php foreach ($action_types as $type): ?>
|
||||||
|
<option value="<?php echo htmlspecialchars($type['action_type']); ?>"
|
||||||
|
<?php echo (isset($_GET['action_type']) && $_GET['action_type'] == $type['action_type']) ? 'selected' : ''; ?>>
|
||||||
|
<?php echo htmlspecialchars($type['action_type']); ?>
|
||||||
|
</option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="entity_type">Tipo Entità</label>
|
||||||
|
<select id="entity_type" name="entity_type" class="form-control">
|
||||||
|
<option value="">Tutte le entità</option>
|
||||||
|
<?php foreach ($entity_types as $type): ?>
|
||||||
|
<option value="<?php echo htmlspecialchars($type['entity_type']); ?>"
|
||||||
|
<?php echo (isset($_GET['entity_type']) && $_GET['entity_type'] == $type['entity_type']) ? 'selected' : ''; ?>>
|
||||||
|
<?php echo htmlspecialchars($type['entity_type']); ?>
|
||||||
|
</option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="date_from">Data Da</label>
|
||||||
|
<input type="date" id="date_from" name="date_from" class="form-control"
|
||||||
|
value="<?php echo htmlspecialchars($_GET['date_from'] ?? ''); ?>">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="date_to">Data A</label>
|
||||||
|
<input type="date" id="date_to" name="date_to" class="form-control"
|
||||||
|
value="<?php echo htmlspecialchars($_GET['date_to'] ?? ''); ?>">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group" style="align-self: flex-end;">
|
||||||
|
<button type="submit" class="btn btn-primary">Filtra</button>
|
||||||
|
<a href="logs.php" class="btn btn-secondary">Reset</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Risultati -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3>Risultati (<?php echo number_format($total); ?> log)</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<?php if (empty($logs)): ?>
|
||||||
|
<p class="text-muted">Nessun log trovato con i filtri selezionati.</p>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Data/Ora</th>
|
||||||
|
<th>Utente</th>
|
||||||
|
<th>Azione</th>
|
||||||
|
<th>Descrizione</th>
|
||||||
|
<th>Entità</th>
|
||||||
|
<th>IP</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($logs as $log): ?>
|
||||||
|
<tr>
|
||||||
|
<td><?php echo formatDateTime($log['created_at']); ?></td>
|
||||||
|
<td>
|
||||||
|
<strong><?php echo htmlspecialchars($log['username']); ?></strong>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="badge badge-<?php
|
||||||
|
echo match($log['action_type']) {
|
||||||
|
'login', 'create' => 'success',
|
||||||
|
'update', 'assign' => 'info',
|
||||||
|
'delete', 'logout' => 'danger',
|
||||||
|
'return' => 'warning',
|
||||||
|
default => 'secondary'
|
||||||
|
};
|
||||||
|
?>">
|
||||||
|
<?php echo htmlspecialchars($log['action_type']); ?>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td><?php echo htmlspecialchars($log['action_description']); ?></td>
|
||||||
|
<td>
|
||||||
|
<?php if ($log['entity_type']): ?>
|
||||||
|
<small class="text-muted">
|
||||||
|
<?php echo htmlspecialchars($log['entity_type']); ?>
|
||||||
|
<?php if ($log['entity_id']): ?>
|
||||||
|
#<?php echo $log['entity_id']; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</small>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="text-muted">-</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<small class="text-muted"><?php echo htmlspecialchars($log['ip_address']); ?></small>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Paginazione -->
|
||||||
|
<?php if ($total_pages > 1): ?>
|
||||||
|
<div class="pagination">
|
||||||
|
<?php
|
||||||
|
$query_params = $_GET;
|
||||||
|
unset($query_params['page']);
|
||||||
|
$base_query = !empty($query_params) ? '&' . http_build_query($query_params) : '';
|
||||||
|
?>
|
||||||
|
|
||||||
|
<?php if ($page > 1): ?>
|
||||||
|
<a href="?page=1<?php echo $base_query; ?>" class="btn btn-sm btn-secondary">Prima</a>
|
||||||
|
<a href="?page=<?php echo $page - 1; ?><?php echo $base_query; ?>" class="btn btn-sm btn-secondary">Precedente</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<span class="pagination-info">
|
||||||
|
Pagina <?php echo $page; ?> di <?php echo $total_pages; ?>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<?php if ($page < $total_pages): ?>
|
||||||
|
<a href="?page=<?php echo $page + 1; ?><?php echo $base_query; ?>" class="btn btn-sm btn-secondary">Successiva</a>
|
||||||
|
<a href="?page=<?php echo $total_pages; ?><?php echo $base_query; ?>" class="btn btn-sm btn-secondary">Ultima</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.filter-form .form-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-form .form-group {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-success { background-color: #28a745; color: white; }
|
||||||
|
.badge-info { background-color: #17a2b8; color: white; }
|
||||||
|
.badge-warning { background-color: #ffc107; color: black; }
|
||||||
|
.badge-danger { background-color: #dc3545; color: white; }
|
||||||
|
.badge-secondary { background-color: #6c757d; color: white; }
|
||||||
|
|
||||||
|
.pagination {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-top: 1rem;
|
||||||
|
padding-top: 1rem;
|
||||||
|
border-top: 1px solid #dee2e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-info {
|
||||||
|
margin: 0 1rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-responsive {
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table tbody tr:hover {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<?php include 'footer.php'; ?>
|
||||||
@@ -28,6 +28,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
$db->updateConfig('warning_days_priority', $warning_days_priority);
|
$db->updateConfig('warning_days_priority', $warning_days_priority);
|
||||||
$db->updateConfig('warning_days_return', $warning_days_return);
|
$db->updateConfig('warning_days_return', $warning_days_return);
|
||||||
|
|
||||||
|
logActivity('update', 'Aggiornate configurazioni di sistema', 'config', null);
|
||||||
setFlashMessage('Configurazioni salvate con successo', 'success');
|
setFlashMessage('Configurazioni salvate con successo', 'success');
|
||||||
header('Location: settings.php');
|
header('Location: settings.php');
|
||||||
exit;
|
exit;
|
||||||
@@ -50,6 +51,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
} else {
|
} else {
|
||||||
$hashed = password_hash($new_password, PASSWORD_DEFAULT);
|
$hashed = password_hash($new_password, PASSWORD_DEFAULT);
|
||||||
$db->query("UPDATE users SET password = ? WHERE id = ?", [$hashed, $user['id']]);
|
$db->query("UPDATE users SET password = ? WHERE id = ?", [$hashed, $user['id']]);
|
||||||
|
logActivity('update', 'Modificata la propria password', 'user', $user['id']);
|
||||||
setFlashMessage('Password modificata con successo', 'success');
|
setFlashMessage('Password modificata con successo', 'success');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,6 +75,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if ($result) {
|
if ($result) {
|
||||||
|
$new_user_id = $db->getConnection()->lastInsertId();
|
||||||
|
$role = $is_admin ? 'amministratore' : 'utente';
|
||||||
|
logActivity('create', "Creato nuovo utente '$username' con ruolo $role", 'user', $new_user_id);
|
||||||
setFlashMessage('Utente aggiunto con successo', 'success');
|
setFlashMessage('Utente aggiunto con successo', 'success');
|
||||||
} else {
|
} else {
|
||||||
setFlashMessage('Errore: username già esistente', 'error');
|
setFlashMessage('Errore: username già esistente', 'error');
|
||||||
@@ -90,7 +95,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
if ($user_id == $_SESSION['user_id']) {
|
if ($user_id == $_SESSION['user_id']) {
|
||||||
setFlashMessage('Non puoi eliminare il tuo account', 'error');
|
setFlashMessage('Non puoi eliminare il tuo account', 'error');
|
||||||
} else {
|
} else {
|
||||||
|
$user_to_delete = $db->fetchOne("SELECT username FROM users WHERE id = ?", [$user_id]);
|
||||||
$db->query("DELETE FROM users WHERE id = ?", [$user_id]);
|
$db->query("DELETE FROM users WHERE id = ?", [$user_id]);
|
||||||
|
if ($user_to_delete) {
|
||||||
|
logActivity('delete', "Eliminato utente '{$user_to_delete['username']}'", 'user', $user_id);
|
||||||
|
}
|
||||||
setFlashMessage('Utente eliminato con successo', 'success');
|
setFlashMessage('Utente eliminato con successo', 'success');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -52,6 +52,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if ($result) {
|
if ($result) {
|
||||||
|
$territory_id = $db->getConnection()->lastInsertId();
|
||||||
|
logActivity('create', "Creato territorio $numero - $zona", 'territory', $territory_id);
|
||||||
setFlashMessage('Territorio aggiunto con successo', 'success');
|
setFlashMessage('Territorio aggiunto con successo', 'success');
|
||||||
} else {
|
} else {
|
||||||
setFlashMessage('Errore durante l\'aggiunta del territorio', 'error');
|
setFlashMessage('Errore durante l\'aggiunta del territorio', 'error');
|
||||||
@@ -99,6 +101,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if ($result) {
|
if ($result) {
|
||||||
|
logActivity('update', "Modificato territorio $numero - $zona", 'territory', $id);
|
||||||
setFlashMessage('Territorio modificato con successo', 'success');
|
setFlashMessage('Territorio modificato con successo', 'success');
|
||||||
} else {
|
} else {
|
||||||
setFlashMessage('Errore durante la modifica del territorio', 'error');
|
setFlashMessage('Errore durante la modifica del territorio', 'error');
|
||||||
@@ -112,7 +115,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
$id = (int)$_POST['id'];
|
$id = (int)$_POST['id'];
|
||||||
|
|
||||||
// Recupera l'immagine per eliminarla
|
// Recupera l'immagine per eliminarla
|
||||||
$territory = $db->fetchOne("SELECT image_path FROM territories WHERE id = ?", [$id]);
|
$territory = $db->fetchOne("SELECT numero, zona, image_path FROM territories WHERE id = ?", [$id]);
|
||||||
if ($territory && $territory['image_path']) {
|
if ($territory && $territory['image_path']) {
|
||||||
$file_path = BASE_PATH . $territory['image_path'];
|
$file_path = BASE_PATH . $territory['image_path'];
|
||||||
if (file_exists($file_path)) {
|
if (file_exists($file_path)) {
|
||||||
@@ -123,6 +126,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
$result = $db->query("DELETE FROM territories WHERE id = ?", [$id]);
|
$result = $db->query("DELETE FROM territories WHERE id = ?", [$id]);
|
||||||
|
|
||||||
if ($result) {
|
if ($result) {
|
||||||
|
if ($territory) {
|
||||||
|
logActivity('delete', "Eliminato territorio {$territory['numero']} - {$territory['zona']}", 'territory', $id);
|
||||||
|
}
|
||||||
setFlashMessage('Territorio eliminato con successo', 'success');
|
setFlashMessage('Territorio eliminato con successo', 'success');
|
||||||
} else {
|
} else {
|
||||||
setFlashMessage('Errore durante l\'eliminazione del territorio', 'error');
|
setFlashMessage('Errore durante l\'eliminazione del territorio', 'error');
|
||||||
|
|||||||
Reference in New Issue
Block a user