171 lines
9.6 KiB
HTML
171 lines
9.6 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Dashboard - OPNsense Backup Manager{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="space-y-8">
|
|
<div>
|
|
<h1 class="text-3xl font-bold text-neutral-100 mb-2">Dashboard</h1>
|
|
<p class="text-neutral-400">Overview of your OPNsense backup system</p>
|
|
</div>
|
|
|
|
<!-- Stats Cards -->
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
|
<div class="bg-neutral-800 rounded-lg p-6 border border-neutral-700">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p class="text-neutral-400 text-sm font-medium">Total Instances</p>
|
|
<p class="text-3xl font-bold text-orange-500 mt-2">{{ instances|length }}</p>
|
|
</div>
|
|
<div class="p-3 bg-orange-500/20 rounded-lg">
|
|
<svg class="w-8 h-8 text-orange-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"></path>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-neutral-800 rounded-lg p-6 border border-neutral-700">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p class="text-neutral-400 text-sm font-medium">Total Backups</p>
|
|
<p class="text-3xl font-bold text-orange-500 mt-2">{{ backups|length }}</p>
|
|
</div>
|
|
<div class="p-3 bg-orange-500/20 rounded-lg">
|
|
<svg class="w-8 h-8 text-orange-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 8h14M5 8a2 2 0 110-4h14a2 2 0 110 4M5 8v10a2 2 0 002 2h10a2 2 0 002-2V8m-9 4h4"></path>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-neutral-800 rounded-lg p-6 border border-neutral-700">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p class="text-neutral-400 text-sm font-medium">SFTP Port</p>
|
|
<p class="text-3xl font-bold text-orange-500 mt-2">{{ sftp_server.port if sftp_server else 'N/A' }}</p>
|
|
</div>
|
|
<div class="p-3 bg-orange-500/20 rounded-lg">
|
|
<svg class="w-8 h-8 text-orange-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9"></path>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Actions -->
|
|
<div class="flex justify-between items-center">
|
|
<h2 class="text-2xl font-bold text-neutral-100">Quick Actions</h2>
|
|
<a href="{{ url_for('new_instance') }}" class="bg-orange-500 hover:bg-orange-600 text-white font-medium py-2 px-4 rounded-md transition-colors duration-200">
|
|
+ Add Instance
|
|
</a>
|
|
</div>
|
|
|
|
<!-- Instances List -->
|
|
<div class="bg-neutral-800 rounded-lg border border-neutral-700 overflow-hidden">
|
|
<div class="px-6 py-4 border-b border-neutral-700">
|
|
<h2 class="text-xl font-bold text-neutral-100">OPNsense Instances</h2>
|
|
</div>
|
|
<div class="overflow-x-auto">
|
|
<table class="w-full">
|
|
<thead class="bg-neutral-700">
|
|
<tr>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-neutral-300 uppercase tracking-wider">Name</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-neutral-300 uppercase tracking-wider">Identifier</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-neutral-300 uppercase tracking-wider">Last Backup</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-neutral-300 uppercase tracking-wider">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="divide-y divide-neutral-700">
|
|
{% if instances %}
|
|
{% for instance in instances %}
|
|
<tr class="hover:bg-neutral-700/50 transition-colors">
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<div class="text-sm font-medium text-neutral-100">{{ instance.name }}</div>
|
|
{% if instance.description %}
|
|
<div class="text-sm text-neutral-400">{{ instance.description }}</div>
|
|
{% endif %}
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<code class="text-sm text-orange-400">{{ instance.identifier }}</code>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<span class="text-sm text-neutral-400">
|
|
{% if instance.last_backup %}
|
|
{{ instance.last_backup.strftime('%Y-%m-%d %H:%M') }}
|
|
{% else %}
|
|
<span class="text-neutral-500">Never</span>
|
|
{% endif %}
|
|
</span>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm">
|
|
<a href="{{ url_for('instance_detail', instance_id=instance.id) }}" class="text-orange-500 hover:text-orange-400 mr-4">View</a>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
{% else %}
|
|
<tr>
|
|
<td colspan="4" class="px-6 py-8 text-center text-neutral-400">
|
|
No instances configured. <a href="{{ url_for('new_instance') }}" class="text-orange-500 hover:text-orange-400">Create one</a> to get started.
|
|
</td>
|
|
</tr>
|
|
{% endif %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Recent Backups -->
|
|
<div class="bg-neutral-800 rounded-lg border border-neutral-700 overflow-hidden">
|
|
<div class="px-6 py-4 border-b border-neutral-700 flex justify-between items-center">
|
|
<h2 class="text-xl font-bold text-neutral-100">Recent Backups</h2>
|
|
<a href="{{ url_for('backups') }}" class="text-sm text-orange-500 hover:text-orange-400">View All</a>
|
|
</div>
|
|
<div class="overflow-x-auto">
|
|
<table class="w-full">
|
|
<thead class="bg-neutral-700">
|
|
<tr>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-neutral-300 uppercase tracking-wider">Instance</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-neutral-300 uppercase tracking-wider">Filename</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-neutral-300 uppercase tracking-wider">Size</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-neutral-300 uppercase tracking-wider">Date</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-neutral-300 uppercase tracking-wider">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="divide-y divide-neutral-700">
|
|
{% if backups %}
|
|
{% for backup in backups[:10] %}
|
|
<tr class="hover:bg-neutral-700/50 transition-colors">
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<span class="text-sm text-neutral-100">{{ backup.instance_name or backup.instance_identifier }}</span>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<code class="text-sm text-neutral-300">{{ backup.filename }}</code>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<span class="text-sm text-neutral-400">{{ "%.2f"|format(backup.file_size / 1024 / 1024) }} MB</span>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<span class="text-sm text-neutral-400">{{ backup.uploaded_at.strftime('%Y-%m-%d %H:%M') if backup.uploaded_at else 'N/A' }}</span>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm">
|
|
<a href="{{ url_for('download_backup', backup_id=backup.id) }}" class="text-orange-500 hover:text-orange-400">Download</a>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
{% else %}
|
|
<tr>
|
|
<td colspan="5" class="px-6 py-8 text-center text-neutral-400">
|
|
No backups yet. Configure an instance and start backing up!
|
|
</td>
|
|
</tr>
|
|
{% endif %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|