219 lines
11 KiB
HTML
219 lines
11 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block content %}
|
|
<div class="container" x-data="dashboardApp()" x-init="init()">
|
|
|
|
{# GitHub logo #}
|
|
<a href="https://github.com/BlessedRebuS/Krawl" target="_blank" class="github-logo">
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/></svg>
|
|
<span class="github-logo-text">Krawl</span>
|
|
</a>
|
|
|
|
{# Banlist export dropdown - Alpine.js #}
|
|
<div class="download-section">
|
|
<div class="banlist-dropdown" @click.outside="banlistOpen = false">
|
|
<button class="banlist-dropdown-btn" @click="banlistOpen = !banlistOpen">
|
|
Export IPs Banlist ▾
|
|
</button>
|
|
<div class="banlist-dropdown-menu" :class="{ 'show': banlistOpen }">
|
|
<a :href="dashboardPath + '/api/get_banlist?fwtype=raw'" download>
|
|
<span class="banlist-icon">📄</span> Raw IPs List
|
|
</a>
|
|
<a :href="dashboardPath + '/api/get_banlist?fwtype=iptables'" download>
|
|
<span class="banlist-icon">🔥</span> IPTables Rules
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<h1>Krawl Dashboard</h1>
|
|
|
|
{# Stats cards - server-rendered #}
|
|
{% include "dashboard/partials/stats_cards.html" %}
|
|
|
|
{# Search bar #}
|
|
<div class="search-bar-container">
|
|
<div class="search-bar">
|
|
<svg class="search-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
|
<path fill-rule="evenodd" d="M9 3.5a5.5 5.5 0 100 11 5.5 5.5 0 000-11zM2 9a7 7 0 1112.452 4.391l3.328 3.329a.75.75 0 11-1.06 1.06l-3.329-3.328A7 7 0 012 9z" clip-rule="evenodd"/>
|
|
</svg>
|
|
<input id="search-input"
|
|
type="search"
|
|
name="q"
|
|
placeholder="Search attacks, IPs, patterns, locations..."
|
|
autocomplete="off"
|
|
hx-get="{{ dashboard_path }}/htmx/search"
|
|
hx-trigger="input changed delay:300ms, search"
|
|
hx-target="#search-results-container"
|
|
hx-swap="innerHTML"
|
|
hx-indicator="#search-spinner" />
|
|
<span id="search-spinner" class="htmx-indicator search-spinner"></span>
|
|
</div>
|
|
<div id="search-results-container"></div>
|
|
</div>
|
|
|
|
{# Tab navigation - Alpine.js #}
|
|
<div class="tabs-container">
|
|
<a class="tab-button" :class="{ active: tab === 'overview' }" @click.prevent="switchToOverview()" href="#overview">Overview</a>
|
|
<a class="tab-button" :class="{ active: tab === 'attacks' }" @click.prevent="switchToAttacks()" href="#ip-stats">Attacks</a>
|
|
<a class="tab-button" :class="{ active: tab === 'ip-insight', disabled: !insightIp }" @click.prevent="insightIp && switchToIpInsight()" href="#ip-insight">
|
|
IP Insight<span x-show="insightIp" x-text="' (' + insightIp + ')'"></span>
|
|
</a>
|
|
<a class="tab-button tab-right" :class="{ active: tab === 'tracked-ips' }" x-show="authenticated" x-cloak @click.prevent="switchToTrackedIps()" href="#tracked-ips">Tracked IPs</a>
|
|
<a class="tab-button" :class="{ active: tab === 'banlist' }" x-show="authenticated" x-cloak @click.prevent="switchToBanlist()" href="#banlist">IP Banlist</a>
|
|
{# Lock icon (not authenticated) #}
|
|
<a class="tab-button tab-lock-btn" :class="{ 'tab-right': !authenticated }" @click.prevent="promptAuth()" x-show="!authenticated" href="#" title="Unlock protected panels">
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="currentColor">
|
|
<path d="M4 4a4 4 0 0 1 8 0v2h.25c.966 0 1.75.784 1.75 1.75v5.5A1.75 1.75 0 0 1 12.25 15h-8.5A1.75 1.75 0 0 1 2 13.25v-5.5C2 6.784 2.784 6 3.75 6H4Zm8.25 3.5h-8.5a.25.25 0 0 0-.25.25v5.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-5.5a.25.25 0 0 0-.25-.25ZM10.5 6V4a2.5 2.5 0 1 0-5 0v2Z"/>
|
|
</svg>
|
|
</a>
|
|
{# Logout icon (authenticated) #}
|
|
<a class="tab-button tab-lock-btn" @click.prevent="logout()" x-show="authenticated" x-cloak href="#" title="Logout">
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="currentColor">
|
|
<path d="M2 2.75C2 1.784 2.784 1 3.75 1h2.5a.75.75 0 0 1 0 1.5h-2.5a.25.25 0 0 0-.25.25v10.5c0 .138.112.25.25.25h2.5a.75.75 0 0 1 0 1.5h-2.5A1.75 1.75 0 0 1 2 13.25Zm10.44 4.5-1.97-1.97a.749.749 0 0 1 .326-1.275.749.749 0 0 1 .734.215l3.25 3.25a.75.75 0 0 1 0 1.06l-3.25 3.25a.749.749 0 0 1-1.275-.326.749.749 0 0 1 .215-.734l1.97-1.97H6.75a.75.75 0 0 1 0-1.5Z"/>
|
|
</svg>
|
|
</a>
|
|
</div>
|
|
|
|
{# ==================== OVERVIEW TAB ==================== #}
|
|
<div x-show="tab === 'overview'" x-init="$nextTick(() => { if (!mapInitialized && typeof initializeAttackerMap === 'function') { initializeAttackerMap(); mapInitialized = true; } })">
|
|
|
|
{# Map section #}
|
|
{% include "dashboard/partials/map_section.html" %}
|
|
|
|
{# Suspicious Activity - server-rendered (last 10 requests) #}
|
|
{% include "dashboard/partials/suspicious_table.html" %}
|
|
|
|
{# Top IPs + Top User-Agents side by side #}
|
|
<div style="display: flex; gap: 20px; flex-wrap: wrap;">
|
|
<div class="table-container" style="flex: 1; min-width: 300px;">
|
|
<h2>Top IP Addresses</h2>
|
|
<div class="htmx-container"
|
|
hx-get="{{ dashboard_path }}/htmx/top-ips?page=1"
|
|
hx-trigger="load"
|
|
hx-swap="innerHTML">
|
|
<div class="htmx-indicator">Loading...</div>
|
|
</div>
|
|
</div>
|
|
<div class="table-container" style="flex: 1; min-width: 300px;">
|
|
<h2>Top User-Agents</h2>
|
|
<div class="htmx-container"
|
|
hx-get="{{ dashboard_path }}/htmx/top-ua?page=1"
|
|
hx-trigger="load"
|
|
hx-swap="innerHTML">
|
|
<div class="htmx-indicator">Loading...</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{# Top Paths #}
|
|
<div class="table-container">
|
|
<h2>Top Paths</h2>
|
|
<div class="htmx-container"
|
|
hx-get="{{ dashboard_path }}/htmx/top-paths?page=1"
|
|
hx-trigger="load"
|
|
hx-swap="innerHTML">
|
|
<div class="htmx-indicator">Loading...</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{# ==================== ATTACKS TAB ==================== #}
|
|
<div x-show="tab === 'attacks'" x-cloak>
|
|
|
|
{# Attackers table - HTMX loaded #}
|
|
<div class="table-container alert-section">
|
|
<h2>Attackers by Total Requests</h2>
|
|
<div class="htmx-container"
|
|
hx-get="{{ dashboard_path }}/htmx/attackers?page=1"
|
|
hx-trigger="revealed"
|
|
hx-swap="innerHTML">
|
|
<div class="htmx-indicator">Loading...</div>
|
|
</div>
|
|
</div>
|
|
|
|
{# Credentials table #}
|
|
<div class="table-container alert-section">
|
|
<h2>Captured Credentials</h2>
|
|
<div class="htmx-container"
|
|
hx-get="{{ dashboard_path }}/htmx/credentials?page=1"
|
|
hx-trigger="revealed"
|
|
hx-swap="innerHTML">
|
|
<div class="htmx-indicator">Loading...</div>
|
|
</div>
|
|
</div>
|
|
|
|
{# Honeypot Triggers - HTMX loaded #}
|
|
<div class="table-container alert-section">
|
|
<h2>Honeypot Triggers by IP</h2>
|
|
<div class="htmx-container"
|
|
hx-get="{{ dashboard_path }}/htmx/honeypot?page=1"
|
|
hx-trigger="revealed"
|
|
hx-swap="innerHTML">
|
|
<div class="htmx-indicator">Loading...</div>
|
|
</div>
|
|
</div>
|
|
|
|
{# Attack Types table #}
|
|
<div class="table-container alert-section">
|
|
<h2>Detected Attack Types</h2>
|
|
<div class="htmx-container"
|
|
hx-get="{{ dashboard_path }}/htmx/attacks?page=1"
|
|
hx-trigger="revealed"
|
|
hx-swap="innerHTML">
|
|
<div class="htmx-indicator">Loading...</div>
|
|
</div>
|
|
</div>
|
|
|
|
{# Charts + Patterns side by side #}
|
|
<div class="charts-container">
|
|
<div class="table-container chart-section">
|
|
<h2>Most Recurring Attack Types</h2>
|
|
<div class="chart-wrapper">
|
|
<canvas id="attack-types-chart"></canvas>
|
|
</div>
|
|
</div>
|
|
<div class="table-container chart-section">
|
|
<h2>Most Recurring Attack Patterns</h2>
|
|
<div class="htmx-container"
|
|
hx-get="{{ dashboard_path }}/htmx/patterns?page=1"
|
|
hx-trigger="revealed"
|
|
hx-swap="innerHTML">
|
|
<div class="htmx-indicator">Loading...</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{# ==================== IP INSIGHT TAB ==================== #}
|
|
<div x-show="tab === 'ip-insight'" x-cloak>
|
|
{# IP Insight content - loaded via HTMX when IP is selected #}
|
|
<div id="ip-insight-container">
|
|
<template x-if="!insightIp">
|
|
<div class="table-container" style="text-align: center; padding: 60px 20px;">
|
|
<p style="color: #8b949e; font-size: 16px;">Select an IP address from any table to view detailed insights.</p>
|
|
</div>
|
|
</template>
|
|
<div x-show="insightIp" id="ip-insight-htmx-container"></div>
|
|
</div>
|
|
</div>
|
|
|
|
{# ==================== TRACKED IPS TAB (protected, loaded via HTMX with server-side auth) ==================== #}
|
|
<div x-show="tab === 'tracked-ips'" x-cloak>
|
|
<div id="tracked-ips-htmx-container"></div>
|
|
</div>
|
|
|
|
{# ==================== IP BANLIST TAB (protected, loaded via HTMX with server-side auth) ==================== #}
|
|
<div x-show="tab === 'banlist'" x-cloak>
|
|
<div id="banlist-htmx-container"></div>
|
|
</div>
|
|
|
|
{# Raw request modal - Alpine.js #}
|
|
{% include "dashboard/partials/raw_request_modal.html" %}
|
|
|
|
{# Auth modal - Alpine.js #}
|
|
{% include "dashboard/partials/auth_modal.html" %}
|
|
|
|
</div>
|
|
{% endblock %}
|