Add project files
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
{% extends "layout.twig" %}
|
||||
{% block title %}Add Server{% endblock %}
|
||||
{% block content %}
|
||||
<div class="max-w-3xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<h1 class="text-3xl font-bold mb-8"><i class="fas fa-plus-circle text-purple-600"></i> Add New Server</h1>
|
||||
{% if error %}<div class="mb-4 bg-red-50 border border-red-400 text-red-700 px-4 py-3 rounded">{{ error }}</div>{% endif %}
|
||||
<form method="POST" class="bg-white shadow rounded-lg p-6 space-y-6">
|
||||
<div><label class="block text-sm font-medium text-gray-700">Server Name</label><input name="name" required class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md" placeholder="US Server 1"></div>
|
||||
<div><label class="block text-sm font-medium text-gray-700">Host IP/Domain</label><input name="host" required class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md" placeholder="123.456.789.0"></div>
|
||||
<div><label class="block text-sm font-medium text-gray-700">SSH Port</label><input name="port" type="number" value="22" class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md"></div>
|
||||
<div><label class="block text-sm font-medium text-gray-700">SSH Username</label><input name="username" value="root" class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md"></div>
|
||||
<div><label class="block text-sm font-medium text-gray-700">SSH Password</label><input name="password" type="password" required class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md"></div>
|
||||
<button type="submit" class="w-full gradient-bg text-white py-2 px-4 rounded-md hover:opacity-90"><i class="fas fa-save mr-2"></i>Create Server</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,57 @@
|
||||
{% extends "layout.twig" %}
|
||||
{% block title %}Deploy {{ server.name }}{% endblock %}
|
||||
{% block content %}
|
||||
<div class="max-w-4xl mx-auto px-4 py-8">
|
||||
<h1 class="text-2xl font-bold mb-6">Deploying: {{ server.name }}</h1>
|
||||
<div id="deployLog" class="bg-gray-900 text-green-400 p-4 rounded font-mono text-sm h-96 overflow-y-auto mb-4">
|
||||
<div>Ready to deploy...</div>
|
||||
</div>
|
||||
<button id="deployBtn" onclick="deploy()" class="gradient-bg text-white px-6 py-2 rounded hover:opacity-90 transition-all disabled:opacity-50 disabled:cursor-not-allowed">
|
||||
<span id="btnText">Start Deployment</span>
|
||||
<i id="btnSpinner" class="fas fa-spinner fa-spin ml-2 hidden"></i>
|
||||
</button>
|
||||
</div>
|
||||
<script>
|
||||
function deploy() {
|
||||
const btn = document.getElementById('deployBtn');
|
||||
const btnText = document.getElementById('btnText');
|
||||
const btnSpinner = document.getElementById('btnSpinner');
|
||||
const log = document.getElementById('deployLog');
|
||||
|
||||
// Disable button and show spinner
|
||||
btn.disabled = true;
|
||||
btnText.textContent = 'Deploying...';
|
||||
btnSpinner.classList.remove('hidden');
|
||||
|
||||
log.innerHTML = '<div>📡 Connecting to server...</div>';
|
||||
log.innerHTML += '<div>🔧 Installing Docker...</div>';
|
||||
log.innerHTML += '<div>📦 Building container...</div>';
|
||||
log.innerHTML += '<div>🔐 Generating keys...</div>';
|
||||
log.innerHTML += '<div>⚙️ Configuring WireGuard...</div>';
|
||||
|
||||
fetch('/servers/{{ server.id }}/deploy', {method: 'POST'})
|
||||
.then(r => r.json())
|
||||
.then(d => {
|
||||
if (d.success) {
|
||||
log.innerHTML += '<div class="text-green-500 font-bold">✅ Deployment successful!</div>';
|
||||
log.innerHTML += '<div class="text-yellow-300">🔌 VPN Port: ' + d.vpn_port + '</div>';
|
||||
log.innerHTML += '<div class="text-yellow-300">🔑 Public Key: ' + d.public_key.substring(0, 40) + '...</div>';
|
||||
btnText.textContent = 'Redirecting...';
|
||||
btnSpinner.classList.add('hidden');
|
||||
setTimeout(() => window.location.href = '/servers/{{ server.id }}', 2000);
|
||||
} else {
|
||||
log.innerHTML += '<div class="text-red-500 font-bold">❌ Error: ' + (d.error || 'Unknown error') + '</div>';
|
||||
btn.disabled = false;
|
||||
btnText.textContent = 'Retry Deployment';
|
||||
btnSpinner.classList.add('hidden');
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
log.innerHTML += '<div class="text-red-500 font-bold">❌ Network error: ' + e.message + '</div>';
|
||||
btn.disabled = false;
|
||||
btnText.textContent = 'Retry Deployment';
|
||||
btnSpinner.classList.add('hidden');
|
||||
});
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,66 @@
|
||||
{% extends "layout.twig" %}
|
||||
{% block title %}{{ t('servers.title') }}{% endblock %}
|
||||
{% block content %}
|
||||
<div class="max-w-7xl mx-auto px-4 py-8">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h1 class="text-3xl font-bold">{{ t('servers.title') }}</h1>
|
||||
<a href="/servers/create" class="gradient-bg text-white px-4 py-2 rounded"><i class="fas fa-plus"></i> {{ t('servers.add') }}</a>
|
||||
</div>
|
||||
|
||||
{% if session.success_message %}
|
||||
<div class="mb-4 bg-green-50 border-l-4 border-green-400 p-4">
|
||||
<p class="text-sm text-green-700"><i class="fas fa-check-circle mr-2"></i>{{ session.success_message }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if session.error_message %}
|
||||
<div class="mb-4 bg-red-50 border-l-4 border-red-400 p-4">
|
||||
<p class="text-sm text-red-700"><i class="fas fa-exclamation-circle mr-2"></i>{{ session.error_message }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if servers|length > 0 %}
|
||||
<div class="bg-white rounded shadow overflow-hidden">
|
||||
<table class="w-full">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">{{ t('servers.name') }}</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">{{ t('servers.host') }}</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">{{ t('servers.status') }}</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">{{ t('servers.actions') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for server in servers %}
|
||||
<tr class="border-t">
|
||||
<td class="px-6 py-4 font-medium">{{ server.name }}</td>
|
||||
<td class="px-6 py-4">{{ server.host }}</td>
|
||||
<td class="px-6 py-4">
|
||||
<span class="px-2 py-1 bg-{{ server.status == 'active' ? 'green' : 'yellow' }}-100 text-{{ server.status == 'active' ? 'green' : 'yellow' }}-800 rounded text-sm">
|
||||
{{ server.status }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-6 py-4 space-x-3">
|
||||
<a href="/servers/{{ server.id }}" class="text-purple-600 hover:text-purple-900">
|
||||
<i class="fas fa-eye mr-1"></i>{{ t('servers.view') }}
|
||||
</a>
|
||||
<form method="POST" action="/servers/{{ server.id }}/delete" class="inline"
|
||||
onsubmit="return confirm('{{ t('message.confirm') }} Delete server {{ server.name }}?');">
|
||||
<button type="submit" class="text-red-600 hover:text-red-900">
|
||||
<i class="fas fa-trash mr-1"></i>{{ t('servers.delete') }}
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="bg-white rounded shadow p-12 text-center">
|
||||
<p class="text-gray-500 mb-4">{{ t('dashboard.no_servers') }}</p>
|
||||
<a href="/servers/create" class="gradient-bg text-white px-6 py-3 rounded inline-block">{{ t('dashboard.add_first_server') }}</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,134 @@
|
||||
{% extends "layout.twig" %}
|
||||
{% block title %}{{ server.name }}{% endblock %}
|
||||
{% block content %}
|
||||
<div class="max-w-7xl mx-auto px-4 py-8">
|
||||
<div class="mb-6"><h1 class="text-3xl font-bold">{{ server.name }}</h1><p class="text-gray-600">{{ server.host }}</p></div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
|
||||
<div class="bg-white rounded shadow p-6">
|
||||
<h3 class="font-bold mb-4">Server Info</h3>
|
||||
<dl class="space-y-2">
|
||||
<div><dt class="text-sm text-gray-600">Status</dt><dd><span class="px-2 py-1 bg-green-100 text-green-800 rounded text-sm">{{ server.status }}</span></dd></div>
|
||||
<div><dt class="text-sm text-gray-600">VPN Port</dt><dd>{{ server.vpn_port }}</dd></div>
|
||||
<div><dt class="text-sm text-gray-600">Subnet</dt><dd>{{ server.vpn_subnet }}</dd></div>
|
||||
</dl>
|
||||
</div>
|
||||
<div class="bg-white rounded shadow p-6">
|
||||
<h3 class="font-bold mb-4">Create Client</h3>
|
||||
<form method="POST" action="/servers/{{ server.id }}/clients/create" class="flex gap-2" id="createClientForm">
|
||||
<input name="name" placeholder="Client name" required class="flex-1 px-3 py-2 border rounded" id="clientName">
|
||||
<button type="submit" class="gradient-bg text-white px-4 py-2 rounded" id="createClientBtn">
|
||||
<span id="createClientText">Create</span>
|
||||
<i class="fas fa-spinner fa-spin" id="createClientSpinner" style="display:none;"></i>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-white rounded shadow">
|
||||
<div class="px-6 py-4 border-b flex justify-between items-center">
|
||||
<h3 class="font-bold">Clients ({{ clients|length }})</h3>
|
||||
<button onclick="syncAllStats({{ server.id }})" class="text-purple-600 hover:text-purple-800 text-sm">
|
||||
<i class="fas fa-sync-alt"></i> Sync Stats
|
||||
</button>
|
||||
</div>
|
||||
{% if clients|length > 0 %}
|
||||
<table class="w-full">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Name</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">IP</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Status</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Traffic</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Last Seen</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for client in clients %}
|
||||
<tr class="border-t">
|
||||
<td class="px-6 py-4">{{ client.name }}</td>
|
||||
<td class="px-6 py-4">{{ client.client_ip }}</td>
|
||||
<td class="px-6 py-4">
|
||||
{% if client.status == 'active' %}
|
||||
<span class="px-2 py-1 bg-green-100 text-green-800 rounded text-xs">Active</span>
|
||||
{% else %}
|
||||
<span class="px-2 py-1 bg-red-100 text-red-800 rounded text-xs">Disabled</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm">
|
||||
<div class="text-gray-600">
|
||||
↑ {{ (client.bytes_sent|default(0) / 1024 / 1024)|number_format(2) }} MB
|
||||
</div>
|
||||
<div class="text-gray-600">
|
||||
↓ {{ (client.bytes_received|default(0) / 1024 / 1024)|number_format(2) }} MB
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm">
|
||||
{% if client.last_handshake %}
|
||||
<span class="text-gray-600">{{ client.last_handshake }}</span>
|
||||
{% else %}
|
||||
<span class="text-gray-400">Never</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<a href="/clients/{{ client.id }}" class="text-purple-600 hover:text-purple-800 mr-2">View</a>
|
||||
{% if client.status == 'active' %}
|
||||
<form method="POST" action="/clients/{{ client.id }}/revoke" style="display:inline;">
|
||||
<button type="submit" class="text-orange-600 hover:text-orange-800 mr-2" onclick="return confirm('Revoke access for this client?')">Revoke</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<form method="POST" action="/clients/{{ client.id }}/restore" style="display:inline;">
|
||||
<button type="submit" class="text-green-600 hover:text-green-800 mr-2">Restore</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
<form method="POST" action="/clients/{{ client.id }}/delete" style="display:inline;">
|
||||
<button type="submit" class="text-red-600 hover:text-red-800" onclick="return confirm('Delete this client permanently?')">Delete</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<div class="p-12 text-center text-gray-500">No clients yet</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const form = document.getElementById('createClientForm');
|
||||
if (form) {
|
||||
form.addEventListener('submit', function(e) {
|
||||
const btn = document.getElementById('createClientBtn');
|
||||
const text = document.getElementById('createClientText');
|
||||
const spinner = document.getElementById('createClientSpinner');
|
||||
|
||||
// Show spinner and disable button
|
||||
btn.disabled = true;
|
||||
text.style.display = 'none';
|
||||
spinner.style.display = 'inline-block';
|
||||
|
||||
// Form will submit normally
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
async function syncAllStats(serverId) {
|
||||
try {
|
||||
const response = await fetch(`/servers/${serverId}/sync-stats`, {
|
||||
method: 'POST'
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
location.reload();
|
||||
} else {
|
||||
alert('Failed to sync stats: ' + (data.error || 'Unknown error'));
|
||||
}
|
||||
} catch (error) {
|
||||
alert('Error: ' + error.message);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user