Added TG bot

This commit is contained in:
Ultradesu
2025-08-15 04:02:22 +03:00
parent 402e4d84fc
commit 36f9e495b5
52 changed files with 6376 additions and 2081 deletions

View File

@@ -0,0 +1,304 @@
<!-- vpn/templates/admin/purge_users.html -->
{% extends "admin/base_site.html" %}
{% load i18n static %}
{% block title %}{{ title }} | {{ site_title|default:_('Django site admin') }}{% endblock %}
{% block extrahead %}
{{ block.super }}
<link rel="stylesheet" type="text/css" href="{% static 'admin/css/server_actions.css' %}">
{% endblock %}
{% block content %}
<div class="content-main">
<h1>{{ title }}</h1>
<!-- Context Information -->
<div class="alert alert-info" style="margin: 10px 0; padding: 10px; border-radius: 4px; background-color: #d1ecf1; border: 1px solid #bee5eb; color: #0c5460;">
{% if servers_info|length == 1 %}
<strong>🎯 Single Server Operation:</strong> You are managing users for server "{{ servers_info.0.server.name }}"
{% elif servers_info|length > 10 %}
<strong>🌐 Bulk Operation:</strong> You are managing users for {{ servers_info|length }} servers (all available servers)
{% else %}
<strong>📋 Multi-Server Operation:</strong> You are managing users for {{ servers_info|length }} selected servers
{% endif %}
</div>
<div class="alert alert-warning" style="margin: 10px 0; padding: 15px; border-radius: 4px; background-color: #fff3cd; border: 1px solid #ffeaa7; color: #856404;">
<strong>⚠️ WARNING:</strong> This operation will permanently delete users directly from the VPN servers.
This action cannot be undone and may affect active VPN connections.
</div>
{% if messages %}
{% for message in messages %}
<div class="alert alert-{{ message.tags }}" style="margin: 10px 0; padding: 10px; border-radius: 4px;
{% if message.tags == 'error' %}background-color: #f8d7da; border: 1px solid #f5c6cb; color: #721c24;
{% elif message.tags == 'success' %}background-color: #d4edda; border: 1px solid #c3e6cb; color: #155724;
{% elif message.tags == 'warning' %}background-color: #fff3cd; border: 1px solid #ffeaa7; color: #856404;
{% elif message.tags == 'info' %}background-color: #d1ecf1; border: 1px solid #bee5eb; color: #0c5460;
{% endif %}">
{{ message }}
</div>
{% endfor %}
{% endif %}
<form method="post" id="purge-form">
{% csrf_token %}
<div class="form-row" style="margin-bottom: 20px;">
<h3>Select Servers and Purge Mode:</h3>
<div style="margin-bottom: 15px;">
<label for="purge_mode"><strong>Purge Mode:</strong></label>
<div style="margin-top: 5px;">
<input type="radio" id="purge_unmanaged" name="purge_mode" value="unmanaged" checked onchange="updatePurgeDescription()">
<label for="purge_unmanaged" style="font-weight: normal; margin-left: 5px; margin-right: 20px;">
<span style="color: #28a745;">Safe Purge</span> - Only unmanaged users
</label>
<input type="radio" id="purge_all" name="purge_mode" value="all" onchange="updatePurgeDescription()">
<label for="purge_all" style="font-weight: normal; margin-left: 5px; color: #dc3545;">
<span style="color: #dc3545;">⚠️ Full Purge</span> - ALL users (including OutFleet managed)
</label>
</div>
</div>
<div id="purge-description" style="padding: 10px; border-radius: 5px; margin-bottom: 15px;">
<!-- Description will be updated by JavaScript -->
</div>
</div>
<div class="form-row" style="margin-bottom: 20px;">
<h3>Select Servers to Purge:</h3>
<div style="max-height: 400px; overflow-y: auto; border: 1px solid #ddd; padding: 10px;">
<div style="margin-bottom: 10px;">
<button type="button" onclick="toggleAllServers()" style="padding: 5px 10px; margin-right: 10px;">Select All</button>
<button type="button" onclick="toggleAllServers(false)" style="padding: 5px 10px;">Deselect All</button>
</div>
<table style="width: 100%; border-collapse: collapse;">
<thead>
<tr style="background-color: #f5f5f5;">
<th style="padding: 8px; border: 1px solid #ddd; width: 50px;">Select</th>
<th style="padding: 8px; border: 1px solid #ddd;">Server Name</th>
<th style="padding: 8px; border: 1px solid #ddd;">Type</th>
<th style="padding: 8px; border: 1px solid #ddd;">Status</th>
<th style="padding: 8px; border: 1px solid #ddd;">Users on Server</th>
<th style="padding: 8px; border: 1px solid #ddd;">Details</th>
</tr>
</thead>
<tbody>
{% for server_info in servers_info %}
<tr class="server-row" data-server-id="{{ server_info.server.id }}">
<td style="padding: 8px; border: 1px solid #ddd; text-align: center;">
{% if server_info.status == 'online' %}
<input type="checkbox" name="selected_servers" value="{{ server_info.server.id }}"
class="server-checkbox" onchange="updateSubmitButton()">
{% else %}
<span style="color: #ccc;" title="Server unavailable"></span>
{% endif %}
</td>
<td style="padding: 8px; border: 1px solid #ddd;">
<strong>{{ server_info.server.name }}</strong>
</td>
<td style="padding: 8px; border: 1px solid #ddd;">
{{ server_info.server.server_type }}
</td>
<td style="padding: 8px; border: 1px solid #ddd;">
{% if server_info.status == 'online' %}
<span style="color: #28a745;">✅ Online</span>
{% else %}
<span style="color: #dc3545;">❌ Error</span>
{% endif %}
</td>
<td style="padding: 8px; border: 1px solid #ddd; text-align: center;">
{% if server_info.status == 'online' %}
<strong>{{ server_info.user_count }}</strong>
{% else %}
<span style="color: #ccc;">N/A</span>
{% endif %}
</td>
<td style="padding: 8px; border: 1px solid #ddd; font-size: 12px;">
{% if server_info.status == 'online' %}
{% if server_info.user_count > 0 %}
<details>
<summary style="cursor: pointer;">View users ({{ server_info.user_count }})</summary>
<div style="margin-top: 5px; max-height: 150px; overflow-y: auto; background-color: #f8f9fa; padding: 5px; border-radius: 3px;">
{% for user in server_info.users %}
<div style="margin: 2px 0; font-family: monospace; font-size: 11px;">
<strong>{{ user.name }}</strong> (ID: {{ user.key_id }})
<br><span style="color: #666;">Pass: {{ user.password|slice:":8" }}...</span>
</div>
{% endfor %}
</div>
</details>
{% else %}
<span style="color: #666;">No users</span>
{% endif %}
{% else %}
<span style="color: #dc3545;">{{ server_info.error }}</span>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="submit-row">
<input type="submit" value="🗑️ Purge Selected Servers" class="default" id="submit-btn" disabled
style="background-color: #dc3545; border-color: #dc3545;">
<a href="{% url 'admin:vpn_server_changelist' %}" class="button cancel">Cancel</a>
</div>
</form>
</div>
<script>
function updatePurgeDescription() {
var purgeMode = document.querySelector('input[name="purge_mode"]:checked').value;
var descriptionDiv = document.getElementById('purge-description');
if (purgeMode === 'unmanaged') {
descriptionDiv.innerHTML = `
<div style="background-color: #d4edda; border-left: 4px solid #28a745; color: #155724;">
<h4 style="margin: 0 0 5px 0;">Safe Purge Mode</h4>
<p style="margin: 0;">
• Only removes users that are <strong>NOT</strong> managed by OutFleet<br>
• Preserves all users that exist in the OutFleet database<br>
• Safe to use - will not affect your managed users<br>
• Recommended for cleaning up orphaned or manually created users
</p>
</div>
`;
} else {
descriptionDiv.innerHTML = `
<div style="background-color: #f8d7da; border-left: 4px solid #dc3545; color: #721c24;">
<h4 style="margin: 0 0 5px 0;">⚠️ DANGEROUS: Full Purge Mode</h4>
<p style="margin: 0;">
• <strong>REMOVES ALL USERS</strong> from the server, including OutFleet managed users<br>
• <strong>WILL DISCONNECT ALL ACTIVE VPN SESSIONS</strong><br>
• OutFleet managed users will be recreated during next sync<br>
• Use only if you want to completely reset the server<br>
• <strong>THIS ACTION CANNOT BE UNDONE</strong>
</p>
</div>
`;
}
}
function toggleAllServers(selectAll = true) {
var checkboxes = document.getElementsByClassName('server-checkbox');
for (var i = 0; i < checkboxes.length; i++) {
checkboxes[i].checked = selectAll;
}
updateSubmitButton();
}
function updateSubmitButton() {
var checkboxes = document.getElementsByClassName('server-checkbox');
var submitBtn = document.getElementById('submit-btn');
var hasSelected = false;
for (var i = 0; i < checkboxes.length; i++) {
if (checkboxes[i].checked) {
hasSelected = true;
break;
}
}
submitBtn.disabled = !hasSelected;
// Update button text based on purge mode
var purgeMode = document.querySelector('input[name="purge_mode"]:checked').value;
if (purgeMode === 'all') {
submitBtn.innerHTML = '<i class="fas fa-exclamation-triangle mr-2"></i>PURGE ALL USERS';
submitBtn.className = 'btn btn-danger';
} else {
submitBtn.innerHTML = '<i class="fas fa-trash mr-2"></i>Purge Unmanaged Users';
submitBtn.className = 'btn btn-warning';
}
}
// Form submission confirmation
document.getElementById('purge-form').addEventListener('submit', function(e) {
var checkboxes = document.getElementsByClassName('server-checkbox');
var selectedCount = 0;
var selectedServers = [];
for (var i = 0; i < checkboxes.length; i++) {
if (checkboxes[i].checked) {
selectedCount++;
var row = checkboxes[i].closest('tr');
var serverName = row.querySelector('td:nth-child(2) strong').textContent;
selectedServers.push(serverName);
}
}
var purgeMode = document.querySelector('input[name="purge_mode"]:checked').value;
var totalUsers = 0;
// Calculate total users that will be affected
for (var i = 0; i < checkboxes.length; i++) {
if (checkboxes[i].checked) {
var row = checkboxes[i].closest('tr');
var userCountBadge = row.querySelector('td:nth-child(5) .badge');
if (userCountBadge) {
totalUsers += parseInt(userCountBadge.textContent) || 0;
}
}
}
var confirmMessage = '';
if (purgeMode === 'all') {
confirmMessage = `⚠️ DANGER: FULL PURGE CONFIRMATION ⚠️\n\n`;
confirmMessage += `You are about to PERMANENTLY DELETE ALL USERS from ${selectedCount} server(s):\n`;
confirmMessage += `${selectedServers.join(', ')}\n\n`;
confirmMessage += `This will:\n`;
confirmMessage += `• DELETE ALL ${totalUsers} users from selected servers\n`;
confirmMessage += `• DISCONNECT ALL ACTIVE VPN SESSIONS\n`;
confirmMessage += `• REMOVE BOTH managed and unmanaged users\n`;
confirmMessage += `• Cannot be undone\n\n`;
confirmMessage += `OutFleet managed users will be recreated during next sync.\n\n`;
confirmMessage += `Type "PURGE ALL" to confirm this dangerous operation:`;
var userInput = prompt(confirmMessage);
if (userInput !== "PURGE ALL") {
e.preventDefault();
alert("Operation cancelled. You must type exactly 'PURGE ALL' to confirm.");
return;
}
} else {
confirmMessage = `Safe Purge Confirmation\n\n`;
confirmMessage += `You are about to purge unmanaged users from ${selectedCount} server(s):\n`;
confirmMessage += `${selectedServers.join(', ')}\n\n`;
confirmMessage += `This will:\n`;
confirmMessage += `• Remove only users NOT managed by OutFleet\n`;
confirmMessage += `• Preserve all OutFleet managed users\n`;
confirmMessage += `• Help clean up orphaned users\n\n`;
confirmMessage += `Are you sure you want to continue?`;
if (!confirm(confirmMessage)) {
e.preventDefault();
}
}
});
// Initialize page
document.addEventListener('DOMContentLoaded', function() {
updatePurgeDescription();
updateSubmitButton();
// Add event listeners to purge mode radio buttons
document.querySelectorAll('input[name="purge_mode"]').forEach(function(radio) {
radio.addEventListener('change', function() {
updatePurgeDescription();
updateSubmitButton();
});
});
});
</script>
{% endblock %}

View File

@@ -0,0 +1,18 @@
{% extends "admin/change_form.html" %}
{% block content %}
{% if show_tab_navigation %}
<div class="module" style="margin-bottom: 20px;">
<div style="display: flex; border-bottom: 1px solid #ddd;">
{% for tab in tabs %}
<a href="{{ tab.url }}"
style="padding: 10px 20px; text-decoration: none; border-bottom: 3px solid {% if tab.active %}#417690{% else %}transparent{% endif %}; color: {% if tab.active %}#417690{% else %}#666{% endif %};">
{% if tab.name == 'subscription_groups' %}📋{% elif tab.name == 'user_subscriptions' %}👥{% elif tab.name == 'certificates' %}🔒{% elif tab.name == 'inbound_templates' %}⚙️{% endif %} {{ tab.label }}
</a>
{% endfor %}
</div>
</div>
{% endif %}
{{ block.super }}
{% endblock %}

View File

@@ -0,0 +1,18 @@
{% extends "admin/change_list.html" %}
{% block content %}
{% if show_tab_navigation %}
<div class="module" style="margin-bottom: 20px;">
<div style="display: flex; border-bottom: 1px solid #ddd;">
{% for tab in tabs %}
<a href="{{ tab.url }}"
style="padding: 10px 20px; text-decoration: none; border-bottom: 3px solid {% if tab.active %}#417690{% else %}transparent{% endif %}; color: {% if tab.active %}#417690{% else %}#666{% endif %};">
{% if tab.name == 'subscription_groups' %}📋{% elif tab.name == 'user_subscriptions' %}👥{% elif tab.name == 'certificates' %}🔒{% elif tab.name == 'inbound_templates' %}⚙️{% endif %} {{ tab.label }}
</a>
{% endfor %}
</div>
</div>
{% endif %}
{{ block.super }}
{% endblock %}

View File

@@ -0,0 +1,18 @@
{% extends "admin/change_form.html" %}
{% block content %}
{% if show_tab_navigation %}
<div class="module" style="margin-bottom: 20px;">
<div style="display: flex; border-bottom: 1px solid #ddd;">
{% for tab in tabs %}
<a href="{{ tab.url }}"
style="padding: 10px 20px; text-decoration: none; border-bottom: 3px solid {% if tab.active %}#417690{% else %}transparent{% endif %}; color: {% if tab.active %}#417690{% else %}#666{% endif %};">
{% if tab.name == 'subscription_groups' %}📋{% elif tab.name == 'user_subscriptions' %}👥{% elif tab.name == 'certificates' %}🔒{% elif tab.name == 'inbound_templates' %}⚙️{% endif %} {{ tab.label }}
</a>
{% endfor %}
</div>
</div>
{% endif %}
{{ block.super }}
{% endblock %}

View File

@@ -0,0 +1,18 @@
{% extends "admin/change_list.html" %}
{% block content %}
{% if show_tab_navigation %}
<div class="module" style="margin-bottom: 20px;">
<div style="display: flex; border-bottom: 1px solid #ddd;">
{% for tab in tabs %}
<a href="{{ tab.url }}"
style="padding: 10px 20px; text-decoration: none; border-bottom: 3px solid {% if tab.active %}#417690{% else %}transparent{% endif %}; color: {% if tab.active %}#417690{% else %}#666{% endif %};">
{% if tab.name == 'subscription_groups' %}📋{% elif tab.name == 'user_subscriptions' %}👥{% elif tab.name == 'certificates' %}🔒{% elif tab.name == 'inbound_templates' %}⚙️{% endif %} {{ tab.label }}
</a>
{% endfor %}
</div>
</div>
{% endif %}
{{ block.super }}
{% endblock %}

View File

@@ -0,0 +1,39 @@
<!-- vpn/templates/admin/vpn/server/change_form.html -->
{% extends "admin/change_form.html" %}
{% load static %}
{% block extrahead %}
{{ block.super }}
<link rel="stylesheet" type="text/css" href="{% static 'admin/css/main.css' %}">
{% endblock %}
{% block submit_buttons_bottom %}
{{ block.super }}
{% if original %}
<div class="row mt-3">
<div class="col-12">
<div class="card card-outline card-primary">
<div class="card-header">
<h3 class="card-title">
<i class="fas fa-tools mr-2"></i>Server Actions
</h3>
</div>
<div class="card-body">
<div class="btn-group" role="group">
<a href="{% url 'admin:server_move_clients' %}?servers={{ original.id }}"
class="btn btn-primary">
<i class="fas fa-exchange-alt mr-2"></i>Move Links
</a>
<a href="{% url 'admin:server_purge_users' %}?servers={{ original.id }}"
class="btn btn-danger"
onclick="return confirm('Open purge interface for {{ original.name }}?')">
<i class="fas fa-trash mr-2"></i>Purge Users
</a>
</div>
</div>
</div>
</div>
</div>
{% endif %}
{% endblock %}

View File

@@ -0,0 +1,166 @@
<!-- vpn/templates/admin/vpn/server/change_list.html -->
{% extends "admin/change_list.html" %}
{% block content_title %}
<h1>{{ title }}</h1>
<!-- Bulk Action Buttons -->
<div class="bulk-actions-section" style="margin: 20px 0; padding: 15px; background-color: #f8f9fa; border: 1px solid #dee2e6; border-radius: 5px;">
<h3 style="margin-top: 0; color: #495057;">🚀 Bulk Server Operations</h3>
<p style="margin-bottom: 15px; color: #6c757d;">
Perform operations on all available servers at once. These actions will include all servers in the system.
</p>
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
<a href="{{ bulk_move_clients_url }}"
class="bulk-action-btn btn-move-clients"
style="background-color: #007cba; color: white; padding: 10px 15px; text-decoration: none; border-radius: 4px; display: inline-flex; align-items: center; gap: 5px;">
📦 <span>Bulk Move Clients</span>
</a>
<a href="{{ bulk_purge_users_url }}"
class="bulk-action-btn btn-purge-users"
style="background-color: #dc3545; color: white; padding: 10px 15px; text-decoration: none; border-radius: 4px; display: inline-flex; align-items: center; gap: 5px;"
onclick="return confirm('⚠️ Warning: This will open the purge interface for ALL servers. Continue?')">
🗑️ <span>Bulk Purge Users</span>
</a>
</div>
<div class="tip-section" style="margin-top: 10px; padding: 10px; background-color: #fff3cd; border-left: 4px solid #ffc107; border-radius: 3px;">
<small style="color: #856404;">
<strong>💡 Tip:</strong> You can also select specific servers below and use the "Actions" dropdown,
or click individual action buttons in the "Actions" column for single-server operations.
</small>
</div>
</div>
{% endblock %}
{% block extrahead %}
{{ block.super }}
{% load static %}
<link rel="stylesheet" type="text/css" href="{% static 'admin/css/server_actions.css' %}">
<style>
/* Style for action buttons in the list */
.field-server_actions .button {
background-color: #007cba;
color: white;
border: none;
border-radius: 3px;
padding: 5px 8px;
font-size: 11px;
text-decoration: none;
display: inline-block;
margin: 2px;
transition: background-color 0.2s;
}
.field-server_actions .button:hover {
background-color: #005a8b;
color: white;
}
.field-server_actions .button[style*="background-color: #dc3545"] {
background-color: #dc3545 !important;
}
.field-server_actions .button[style*="background-color: #dc3545"]:hover {
background-color: #c82333 !important;
}
/* Make action column wider */
.field-server_actions {
min-width: 150px;
}
/* Responsive design for action buttons */
@media (max-width: 768px) {
.field-server_actions > div {
flex-direction: column;
}
.field-server_actions .button {
margin: 1px 0;
text-align: center;
}
}
/* Enhanced bulk action section styling */
.bulk-actions-section {
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
border-left: 4px solid #007cba;
}
.bulk-actions-section h3 {
color: #007cba;
display: flex;
align-items: center;
gap: 8px;
}
/* Hover effects for bulk buttons */
.bulk-action-btn {
transition: all 0.3s ease;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.bulk-action-btn:hover {
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
/* Status indicators */
.server-status-online {
color: #28a745;
font-weight: bold;
}
.server-status-error {
color: #dc3545;
font-weight: bold;
}
</style>
<script>
// Add loading states to bulk action buttons
document.addEventListener('DOMContentLoaded', function() {
const bulkButtons = document.querySelectorAll('.bulk-action-btn');
bulkButtons.forEach(button => {
button.addEventListener('click', function(e) {
// Don't prevent default, but add loading state
this.classList.add('loading');
this.style.pointerEvents = 'none';
// Remove loading state after a delay (in case user navigates back)
setTimeout(() => {
this.classList.remove('loading');
this.style.pointerEvents = 'auto';
}, 5000);
});
});
});
</script>
{% endblock %}
{% block result_list %}
<!-- Server Statistics -->
<div class="server-stats-section" style="margin: 15px 0; padding: 10px; background-color: #e8f4fd; border: 1px solid #bee5eb; border-radius: 4px;">
<div class="server-stats-grid" style="display: flex; gap: 20px; flex-wrap: wrap; align-items: center;">
<div class="stat-item">
<strong>📊 Server Overview:</strong>
</div>
<div class="stat-item">
<span class="stat-label" style="color: #007cba;">Total Servers:</span>
<strong class="stat-value">{{ cl.result_count }}</strong>
</div>
{% if cl.result_count > 0 %}
<div class="stat-item">
<span class="stat-label" style="color: #28a745;">Available Operations:</span>
<strong class="stat-value">Move Links, Purge Users, Bulk Actions</strong>
</div>
{% endif %}
</div>
</div>
{{ block.super }}
{% endblock %}

View File

@@ -4,14 +4,12 @@
{% if show_tab_navigation %}
<div class="module" style="margin-bottom: 20px;">
<div style="display: flex; border-bottom: 1px solid #ddd;">
<a href="{% url 'admin:vpn_subscriptiongroup_changelist' %}"
style="padding: 10px 20px; text-decoration: none; border-bottom: 3px solid #417690; color: #417690;">
📋 Subscription Groups
</a>
<a href="/admin/vpn/usersubscription/"
style="padding: 10px 20px; text-decoration: none; border-bottom: 3px solid transparent; color: #666;">
👥 User Subscriptions
{% for tab in tabs %}
<a href="{{ tab.url }}"
style="padding: 10px 20px; text-decoration: none; border-bottom: 3px solid {% if tab.active %}#417690{% else %}transparent{% endif %}; color: {% if tab.active %}#417690{% else %}#666{% endif %};">
{% if tab.name == 'subscription_groups' %}📋{% elif tab.name == 'user_subscriptions' %}👥{% elif tab.name == 'certificates' %}🔒{% elif tab.name == 'inbound_templates' %}⚙️{% endif %} {{ tab.label }}
</a>
{% endfor %}
</div>
</div>
{% endif %}

View File

@@ -4,14 +4,12 @@
{% if show_tab_navigation %}
<div class="module" style="margin-bottom: 20px;">
<div style="display: flex; border-bottom: 1px solid #ddd;">
<a href="{% url 'admin:vpn_subscriptiongroup_changelist' %}"
style="padding: 10px 20px; text-decoration: none; border-bottom: 3px solid {% if current_tab == 'subscription_groups' %}#417690{% else %}transparent{% endif %}; color: {% if current_tab == 'subscription_groups' %}#417690{% else %}#666{% endif %};">
📋 Subscription Groups
</a>
<a href="/admin/vpn/usersubscription/"
style="padding: 10px 20px; text-decoration: none; border-bottom: 3px solid {% if current_tab == 'user_subscriptions' %}#417690{% else %}transparent{% endif %}; color: {% if current_tab == 'user_subscriptions' %}#417690{% else %}#666{% endif %};">
👥 User Subscriptions
{% for tab in tabs %}
<a href="{{ tab.url }}"
style="padding: 10px 20px; text-decoration: none; border-bottom: 3px solid {% if tab.active %}#417690{% else %}transparent{% endif %}; color: {% if tab.active %}#417690{% else %}#666{% endif %};">
{% if tab.name == 'subscription_groups' %}📋{% elif tab.name == 'user_subscriptions' %}👥{% elif tab.name == 'certificates' %}🔒{% elif tab.name == 'inbound_templates' %}⚙️{% endif %} {{ tab.label }}
</a>
{% endfor %}
</div>
</div>
{% endif %}

View File

@@ -1,18 +1,18 @@
{% extends "admin/change_form.html" %}
{% block content %}
{% if show_tab_navigation %}
<div class="module" style="margin-bottom: 20px;">
<div style="display: flex; border-bottom: 1px solid #ddd;">
<a href="{% url 'admin:vpn_subscriptiongroup_changelist' %}"
style="padding: 10px 20px; text-decoration: none; border-bottom: 3px solid transparent; color: #666;">
📋 Subscription Groups
</a>
<a href="/admin/vpn/usersubscription/"
style="padding: 10px 20px; text-decoration: none; border-bottom: 3px solid #417690; color: #417690;">
👥 User Subscriptions
{% for tab in tabs %}
<a href="{{ tab.url }}"
style="padding: 10px 20px; text-decoration: none; border-bottom: 3px solid {% if tab.active %}#417690{% else %}transparent{% endif %}; color: {% if tab.active %}#417690{% else %}#666{% endif %};">
{% if tab.name == 'subscription_groups' %}📋{% elif tab.name == 'user_subscriptions' %}👥{% elif tab.name == 'certificates' %}🔒{% elif tab.name == 'inbound_templates' %}⚙️{% endif %} {{ tab.label }}
</a>
{% endfor %}
</div>
</div>
{% endif %}
{{ block.super }}
{% endblock %}

View File

@@ -1,18 +1,18 @@
{% extends "admin/change_list.html" %}
{% block content %}
{% if show_tab_navigation %}
<div class="module" style="margin-bottom: 20px;">
<div style="display: flex; border-bottom: 1px solid #ddd;">
<a href="{% url 'admin:vpn_subscriptiongroup_changelist' %}"
style="padding: 10px 20px; text-decoration: none; border-bottom: 3px solid transparent; color: #666;">
📋 Subscription Groups
</a>
<a href="/admin/vpn/usersubscription/"
style="padding: 10px 20px; text-decoration: none; border-bottom: 3px solid #417690; color: #417690;">
👥 User Subscriptions
{% for tab in tabs %}
<a href="{{ tab.url }}"
style="padding: 10px 20px; text-decoration: none; border-bottom: 3px solid {% if tab.active %}#417690{% else %}transparent{% endif %}; color: {% if tab.active %}#417690{% else %}#666{% endif %};">
{% if tab.name == 'subscription_groups' %}📋{% elif tab.name == 'user_subscriptions' %}👥{% elif tab.name == 'certificates' %}🔒{% elif tab.name == 'inbound_templates' %}⚙️{% endif %} {{ tab.label }}
</a>
{% endfor %}
</div>
</div>
{% endif %}
{{ block.super }}
{% endblock %}