'success', 'text' => 'Laporan berhasil dianalisis!']; } else { $message = ['type' => 'error', 'text' => $result['error']]; } } // ── Handle: Delete history entry ─────────────────────────── if ($action === 'delete' && !empty($_GET['id'])) { deleteHistory($_GET['id']); header('Location: index.php?deleted=1'); exit; } // ── Handle: View historical report ───────────────────────── if ($action === 'view' && !empty($_GET['id'])) { $report = getHistoryItem($_GET['id']); } // ── Load history ─────────────────────────────────────────── $history = loadHistory(); // ============================================================ // FUNCTIONS // ============================================================ function handleUpload($file) { if ($file['error'] !== UPLOAD_ERR_OK) return ['success' => false, 'error' => 'Upload gagal. Coba lagi.']; if ($file['size'] > MAX_FILE_SIZE) return ['success' => false, 'error' => 'File terlalu besar (max 10MB).']; if (mime_content_type($file['tmp_name']) !== 'application/pdf') return ['success' => false, 'error' => 'File harus berformat PDF.']; // Extract text $text = extractPdfText($file['tmp_name']); if (!$text) return ['success' => false, 'error' => 'Gagal membaca isi PDF. Pastikan PDF berisi teks (bukan scan gambar).']; // AI Analysis $analysis = analyzeWithAI($text); if (!$analysis) return ['success' => false, 'error' => 'Gagal menghubungi AI. Periksa API key.']; // Save $report = [ 'id' => uniqid(), 'filename' => htmlspecialchars($file['name']), 'uploaded' => date('Y-m-d H:i:s'), 'analysis' => $analysis, 'raw_text' => substr($text, 0, 3000), // simpan preview ]; saveHistory($report); return ['success' => true, 'report' => $report]; } function extractPdfText($filepath) { global $needsComposer; if ($needsComposer) return null; try { $parser = new \Smalot\PdfParser\Parser(); $pdf = $parser->parseFile($filepath); $text = $pdf->getText(); return strlen(trim($text)) > 100 ? $text : null; } catch (Exception $e) { return null; } } function analyzeWithAI($text) { $prompt = buildPrompt($text); // Try Claude first $result = callClaude($prompt); if ($result) return $result; // Fallback to Groq $result = callGroq($prompt); return $result; } function buildPrompt($text) { return "You are a network security analyst. Analyze the following FortiGate report and return ONLY a valid JSON object (no markdown, no extra text) with this exact structure: { \"device\": \"device name\", \"period\": \"report period\", \"total_traffic_gb\": 0, \"total_sessions\": 0, \"total_threats\": 0, \"attacks\": 0, \"threat_items\": [ {\"name\": \"threat name\", \"level\": \"High/Medium/Low\", \"score\": 0, \"percent\": \"0%\"} ], \"top_apps\": [ {\"name\": \"app name\", \"traffic_gb\": 0, \"percent\": \"0%\"} ], \"web_categories\": [ {\"name\": \"category\", \"percent\": \"0%\"} ], \"vpn_threats\": [\"app1\", \"app2\"], \"suspicious_domains\": [\"domain1\"], \"top_users\": [ {\"name\": \"username\", \"visits\": 0} ], \"vpn_tunnels\": [ {\"name\": \"tunnel\", \"sent_gb\": 0, \"recv_gb\": 0} ], \"traffic_by_country\": [ {\"country\": \"country name\", \"traffic_gb\": 0, \"percent\": \"0%\"} ], \"admin_sessions\": [ {\"user\": \"admin\", \"interface\": \"https\", \"sessions\": 0, \"changes\": 0} ], \"failed_admin_logins\": 0, \"virus_detected\": false, \"recommendations\": [ { \"level\": \"high/medium/low\", \"title_id\": \"judul dalam Bahasa Indonesia\", \"title_en\": \"title in English\", \"desc_id\": \"penjelasan singkat dan jelas dalam Bahasa Indonesia\", \"desc_en\": \"concise explanation in English\" } ], \"summary_id\": \"Ringkasan 2-3 kalimat kondisi keamanan jaringan hari ini dalam Bahasa Indonesia.\", \"summary_en\": \"2-3 sentence summary of today's network security status in English.\" } IMPORTANT: The recommendations array MUST include both Indonesian (title_id, desc_id) and English (title_en, desc_en) fields for every item. Do not use the old 'title' or 'desc' fields. REPORT DATA: " . substr($text, 0, 8000); } function callClaude($prompt) { $payload = json_encode([ 'model' => 'claude-haiku-4-5', 'max_tokens' => 2000, 'messages' => [['role' => 'user', 'content' => $prompt]], ]); $response = httpPost('https://api.anthropic.com/v1/messages', $payload, [ 'x-api-key: ' . CLAUDE_API_KEY, 'anthropic-version: 2023-06-01', 'content-type: application/json', ]); if (!$response) return null; $data = json_decode($response, true); $text = $data['content'][0]['text'] ?? null; return $text ? parseJsonResponse($text) : null; } function callGroq($prompt) { $payload = json_encode([ 'model' => 'llama-3.3-70b-versatile', 'messages' => [ ['role' => 'system', 'content' => 'Kamu analis keamanan jaringan. Selalu balas HANYA dengan JSON valid.'], ['role' => 'user', 'content' => $prompt], ], 'max_tokens' => 2000, 'temperature' => 0.1, ]); $response = httpPost('https://api.groq.com/openai/v1/chat/completions', $payload, [ 'Authorization: Bearer ' . GROQ_API_KEY, 'Content-Type: application/json', ]); if (!$response) return null; $data = json_decode($response, true); $text = $data['choices'][0]['message']['content'] ?? null; return $text ? parseJsonResponse($text) : null; } function parseJsonResponse($text) { // Strip markdown fences if any $text = preg_replace('/^```(?:json)?\s*/m', '', $text); $text = preg_replace('/\s*```$/m', '', $text); $text = trim($text); $data = json_decode($text, true); return (json_last_error() === JSON_ERROR_NONE && is_array($data)) ? $data : null; } function httpPost($url, $payload, $headers) { $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => $payload, CURLOPT_HTTPHEADER => $headers, CURLOPT_TIMEOUT => 60, CURLOPT_SSL_VERIFYPEER => true, ]); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); return ($response && $httpCode === 200) ? $response : null; } function loadHistory() { if (!file_exists(HISTORY_FILE)) return []; $data = json_decode(file_get_contents(HISTORY_FILE), true); return is_array($data) ? array_reverse($data) : []; } function saveHistory($report) { $history = file_exists(HISTORY_FILE) ? json_decode(file_get_contents(HISTORY_FILE), true) : []; if (!is_array($history)) $history = []; $history[] = $report; // Keep max 90 entries if (count($history) > 90) $history = array_slice($history, -90); file_put_contents(HISTORY_FILE, json_encode($history, JSON_PRETTY_PRINT)); } function getHistoryItem($id) { $history = loadHistory(); foreach ($history as $item) { if ($item['id'] === $id) return $item; } return null; } function deleteHistory($id) { if (!file_exists(HISTORY_FILE)) return; $history = json_decode(file_get_contents(HISTORY_FILE), true); if (!is_array($history)) return; $history = array_values(array_filter($history, fn($h) => $h['id'] !== $id)); file_put_contents(HISTORY_FILE, json_encode($history, JSON_PRETTY_PRINT)); } function levelBadge($level) { $map = ['High' => 'danger', 'Medium' => 'warning', 'Low' => 'info']; $cls = $map[$level] ?? 'secondary'; return "{$level}"; } function recoBadge($level) { $map = ['high' => 'danger', 'medium' => 'warning', 'low' => 'info']; $cls = $map[strtolower($level)] ?? 'secondary'; $icons = ['high' => '🔴', 'medium' => '🟡', 'low' => '🔵']; $icon = $icons[strtolower($level)] ?? '⚪'; return "{$icon} " . ucfirst($level) . ""; } function fmtNum($n) { return number_format((float)$n, 0, ',', '.'); } ?> FortiGate Report Dashboard
Menganalisis laporan dengan AI...
Setup diperlukan

Install library pdfparser via Composer di cPanel sebelum menggunakan aplikasi ini:

  1. Login ke cPanel → Terminal
  2. Masuk ke folder aplikasi ini:
    cd
  3. Jalankan:
    composer require smalot/pdfparser
  4. Refresh halaman ini

Jika Composer belum tersedia: curl -sS https://getcomposer.org/installer | php

Upload Laporan FortiGate (PDF)
Klik atau drag & drop PDF di sini
Maksimal 10 MB · Format: FortiGate Cloud Summary Report
Pilih laporan dari riwayat di sebelah kiri
 ·   ·  Upload:
Upload Baru
ID
EN
'Total Traffic','value'=>($a['total_traffic_gb']??0).' GB','icon'=>'bi-hdd-network','bg'=>'#e8f4fd','color'=>'#0d6efd'], ['label'=>'Total Sesi','value'=>fmtNum($a['total_sessions']??0),'icon'=>'bi-diagram-3','bg'=>'#e8f5e9','color'=>'#198754'], ['label'=>'Total Ancaman','value'=>fmtNum($a['total_threats']??0),'icon'=>'bi-shield-exclamation','bg'=>'#fff3cd','color'=>'#fd7e14'], ['label'=>'Serangan URL','value'=>fmtNum($a['attacks']??0),'icon'=>'bi-bug','bg'=>'#fdecea','color'=>'#dc3545'], ]; foreach ($stats as $s): ?>
Rekomendasi Tindakan ID Bahasa Indonesia  ·  EN English
ID
EN
Ancaman Teratas
AncamanLevel%
Aplikasi Trafik Tertinggi
AplikasiTraffic (GB)%
VPN / Proxy Tidak Sah

Aplikasi di atas terdeteksi aktif dan berisiko bypass kebijakan jaringan.

Domain Mencurigakan
Kategori Web
Pengguna Web Paling Aktif
$u): ?>
#UserKunjungan
VPN Tunnel Resmi
TunnelKirim (GB)Terima (GB)
Traffic berdasarkan Negara
NegaraTraffic (GB)%
Aktivitas Admin
Tidak ada login gagal login gagal
Tidak ada virus Virus terdeteksi!
UserInterfaceSesiPerubahan Config