|
@@ -1,135 +1,118 @@
|
|
|
-{% block content %}
|
|
|
|
|
-<div class="flex justify-between items-center mb-10">
|
|
|
|
|
- <div>
|
|
|
|
|
- <h2 class="text-4xl font-black text-white tracking-tighter italic">User Management</h2>
|
|
|
|
|
- <p class="text-gray-500 font-bold uppercase text-[10px] tracking-widest mt-2">Manage access and RBAC roles</p>
|
|
|
|
|
- </div>
|
|
|
|
|
- <button onclick="openAddUser()" class="bg-blue-600 hover:bg-blue-500 px-8 py-3 rounded-xl font-black shadow-2xl transition transform hover:scale-105 text-white uppercase text-xs tracking-widest">+ Create User</button>
|
|
|
|
|
-</div>
|
|
|
|
|
|
|
+<!DOCTYPE html>
|
|
|
|
|
+<html lang="en">
|
|
|
|
|
+<head>
|
|
|
|
|
+ <meta charset="UTF-8">
|
|
|
|
|
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
|
|
+ <title>BIND9 Pro Command Center</title>
|
|
|
|
|
+ <script src="https://cdn.tailwindcss.com"></script>
|
|
|
|
|
+ <script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
|
|
|
|
+ <style>
|
|
|
|
|
+ body { background-color: #0b0e14; color: #ffffff; }
|
|
|
|
|
+ .custom-scroll::-webkit-scrollbar { width: 6px; height: 6px; }
|
|
|
|
|
+ .custom-scroll::-webkit-scrollbar-track { background: transparent; }
|
|
|
|
|
+ .custom-scroll::-webkit-scrollbar-thumb { background: #374151; border-radius: 10px; }
|
|
|
|
|
+ .custom-scroll::-webkit-scrollbar-thumb:hover { background: #4b5563; }
|
|
|
|
|
+ /* Αποτρέπει το text selection στα διπλά κλικ */
|
|
|
|
|
+ .no-select { user-select: none; }
|
|
|
|
|
+ </style>
|
|
|
|
|
+</head>
|
|
|
|
|
+<body class="flex h-screen overflow-hidden font-sans">
|
|
|
|
|
|
|
|
-<div class="bg-[#12161f] border border-gray-800 rounded-3xl overflow-hidden shadow-2xl">
|
|
|
|
|
- <table class="w-full text-left">
|
|
|
|
|
- <thead class="bg-black/20 text-[10px] font-black text-gray-500 uppercase tracking-widest border-b border-gray-800">
|
|
|
|
|
- <tr>
|
|
|
|
|
- <th class="p-6">Username</th>
|
|
|
|
|
- <th class="p-6">Role</th>
|
|
|
|
|
- <th class="p-6">Templates</th>
|
|
|
|
|
- <th class="p-6 text-right">Actions</th>
|
|
|
|
|
- </tr>
|
|
|
|
|
- </thead>
|
|
|
|
|
- <tbody class="divide-y divide-gray-800">
|
|
|
|
|
- {% for u, data in users.items() %}
|
|
|
|
|
- <tr class="hover:bg-white/[0.02] transition">
|
|
|
|
|
- <td class="p-6 font-bold text-white">{{ u }}</td>
|
|
|
|
|
- <td class="p-6">
|
|
|
|
|
- <span class="px-3 py-1 rounded-full text-[9px] font-black uppercase border {% if data.role=='admin' %}border-red-500/30 text-red-400 bg-red-500/10{% else %}border-blue-500/30 text-blue-400 bg-blue-500/10{% endif %}">
|
|
|
|
|
- {{ data.role }}
|
|
|
|
|
- </span>
|
|
|
|
|
- </td>
|
|
|
|
|
- <td class="p-6 text-xs text-gray-500">{{ data.assigned_templates|join(', ') or 'None' }}</td>
|
|
|
|
|
- <td class="p-6 text-right">
|
|
|
|
|
-
|
|
|
|
|
- <button type="button"
|
|
|
|
|
- data-user="{{ u }}"
|
|
|
|
|
- data-role="{{ data.role }}"
|
|
|
|
|
- data-templates="{{ data.assigned_templates|join(',') if data.assigned_templates else '' }}"
|
|
|
|
|
- onclick="editUser(this)"
|
|
|
|
|
- class="text-blue-500 hover:text-blue-400 text-[10px] font-black uppercase mr-4">
|
|
|
|
|
- Edit
|
|
|
|
|
- </button>
|
|
|
|
|
-
|
|
|
|
|
- {% if u != 'admin' %}
|
|
|
|
|
- <a href="/users/delete/{{ u|urlencode }}" onclick="return confirm('Are you sure you want to delete this user?')" class="text-red-500 hover:text-red-400 text-[10px] font-black uppercase">Delete</a>
|
|
|
|
|
- {% endif %}
|
|
|
|
|
-
|
|
|
|
|
- </td>
|
|
|
|
|
- </tr>
|
|
|
|
|
- {% endfor %}
|
|
|
|
|
- </tbody>
|
|
|
|
|
- </table>
|
|
|
|
|
-</div>
|
|
|
|
|
-
|
|
|
|
|
-<div id="userModal" class="hidden fixed inset-0 bg-black/90 backdrop-blur-md flex items-center justify-center z-50 p-4">
|
|
|
|
|
- <div class="bg-[#12161f] border border-gray-800 w-full max-w-md rounded-3xl p-8 shadow-2xl">
|
|
|
|
|
- <h3 id="modalTitle" class="text-xl font-black text-white mb-6 uppercase">Add User</h3>
|
|
|
|
|
- <form action="/users/save" method="POST" class="space-y-4">
|
|
|
|
|
- <input type="hidden" name="action" id="userAction" value="add">
|
|
|
|
|
- <input type="hidden" name="old_username" id="oldUsername">
|
|
|
|
|
-
|
|
|
|
|
- <input type="text" name="username" id="userName" placeholder="Username" required class="w-full bg-[#0b0e14] border border-gray-800 rounded-xl p-3 text-sm font-bold text-white outline-none">
|
|
|
|
|
- <input type="password" name="password" id="userPass" placeholder="Password (Leave blank to keep current)" class="w-full bg-[#0b0e14] border border-gray-800 rounded-xl p-3 text-sm font-bold text-white outline-none">
|
|
|
|
|
-
|
|
|
|
|
- <select name="role" id="userRole" class="w-full bg-[#0b0e14] border border-gray-800 rounded-xl p-3 text-sm font-bold text-white outline-none">
|
|
|
|
|
- <option value="user">User (Restricted)</option>
|
|
|
|
|
- <option value="admin">Administrator</option>
|
|
|
|
|
- </select>
|
|
|
|
|
-
|
|
|
|
|
- <div class="bg-black/20 p-4 rounded-xl border border-gray-800">
|
|
|
|
|
- <label class="text-[10px] font-black text-gray-500 uppercase block mb-3">Assigned Templates</label>
|
|
|
|
|
- <div class="grid grid-cols-2 gap-2 max-h-48 overflow-y-auto pr-2">
|
|
|
|
|
- {% for t in all_templates %}
|
|
|
|
|
- <label class="flex items-center gap-2 text-xs text-gray-400 cursor-pointer hover:text-white">
|
|
|
|
|
- <input type="checkbox" name="assigned_templates[]" value="{{ t }}" class="template-check rounded border-gray-700 bg-gray-900 text-blue-600">
|
|
|
|
|
- {{ t }}
|
|
|
|
|
- </label>
|
|
|
|
|
- {% endfor %}
|
|
|
|
|
|
|
+ <aside class="w-64 bg-[#12161f] border-r border-gray-800 flex flex-col shadow-2xl z-20">
|
|
|
|
|
+ <div class="p-6 border-b border-gray-800 flex items-center justify-center">
|
|
|
|
|
+ <h1 class="text-3xl font-black text-blue-500 italic tracking-tighter">BIND<span class="text-white">PRO</span></h1>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="mt-auto pt-6 border-t border-gray-800">
|
|
|
|
|
+ <div class="flex items-center justify-center gap-2 text-gray-500 opacity-50 hover:opacity-100 transition-opacity">
|
|
|
|
|
+ <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path></svg>
|
|
|
|
|
+ <span class="text-[9px] font-mono font-black uppercase tracking-widest">{{ app_version }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <nav class="flex-1 p-4 space-y-2 overflow-y-auto custom-scroll mt-4">
|
|
|
|
|
+
|
|
|
|
|
+ <p class="px-4 text-[10px] font-black text-gray-600 uppercase tracking-widest mb-2">Main Menu</p>
|
|
|
|
|
+ <a href="/" class="flex items-center gap-3 px-4 py-3 text-sm font-bold text-gray-400 rounded-xl hover:bg-blue-600 hover:text-white transition group">
|
|
|
|
|
+ <svg class="w-5 h-5 group-hover:scale-110 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z"></path></svg>
|
|
|
|
|
+ Dashboard
|
|
|
|
|
+ </a>
|
|
|
|
|
+ {% if session.get('role') == 'admin' %}
|
|
|
|
|
+ <a href="/analytics" class="flex items-center gap-3 px-4 py-3 text-sm font-bold text-gray-400 rounded-xl hover:bg-purple-600 hover:text-white transition group">
|
|
|
|
|
+ <svg class="w-5 h-5 group-hover:scale-110 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"></path></svg>
|
|
|
|
|
+ Analytics
|
|
|
|
|
+ </a>
|
|
|
|
|
+ <a href="/settings" class="flex items-center gap-3 px-4 py-3 text-sm font-bold text-gray-400 rounded-xl hover:bg-gray-700 hover:text-white transition group">
|
|
|
|
|
+ <svg class="w-5 h-5 group-hover:rotate-90 transition-transform duration-300" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path></svg>
|
|
|
|
|
+ Settings
|
|
|
|
|
+ </a>
|
|
|
|
|
+ <a href="/users" class="flex items-center gap-3 px-4 py-3 text-sm font-bold text-gray-400 rounded-xl hover:bg-gray-700 hover:text-white transition group">
|
|
|
|
|
+ <svg class="w-5 h-5 group-hover:scale-110 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"></path></svg>
|
|
|
|
|
+ Users
|
|
|
|
|
+ </a>
|
|
|
|
|
+ <a href="/adblock" class="flex items-center gap-3 px-4 py-3 text-sm font-bold text-gray-400 rounded-xl hover:bg-gray-700 hover:text-white transition group">
|
|
|
|
|
+ <svg class="w-5 h-5 group-hover:scale-110 transition-transform group-hover:text-red-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.618 5.984A11.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.016zM12 9v2m0 4h.01"></path>
|
|
|
|
|
+ </svg>
|
|
|
|
|
+ AdBlock Engine
|
|
|
|
|
+ </a>
|
|
|
|
|
+ <a href="/adanalytics" class="flex items-center gap-3 px-4 py-3 text-sm font-bold text-gray-400 rounded-xl hover:bg-gray-700 hover:text-white transition group">
|
|
|
|
|
+ <svg class="w-5 h-5 group-hover:scale-110 transition-transform group-hover:text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"></path>
|
|
|
|
|
+ </svg>
|
|
|
|
|
+ AdBlock Analytics
|
|
|
|
|
+ </a>
|
|
|
|
|
+ <a href="/cluster" class="flex items-center gap-3 px-4 py-3 text-sm font-bold text-gray-400 rounded-xl hover:bg-blue-600 hover:text-white transition group">
|
|
|
|
|
+ <svg class="w-5 h-5 group-hover:scale-110 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"></path></svg>
|
|
|
|
|
+ Cluster Nodes
|
|
|
|
|
+ </a>
|
|
|
|
|
+ <a href="/profile" class="flex items-center gap-3 px-4 py-2 text-gray-400 hover:text-white transition">
|
|
|
|
|
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"></path></svg>
|
|
|
|
|
+ <span class="text-xs font-black uppercase tracking-widest">My API</span>
|
|
|
|
|
+ </a>
|
|
|
|
|
+ {% endif %}
|
|
|
|
|
+ </nav>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="p-6 border-t border-gray-800 bg-gray-900/30">
|
|
|
|
|
+ <div class="flex items-center justify-between">
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <p class="text-[10px] text-gray-500 uppercase font-black tracking-widest">Logged in as</p>
|
|
|
|
|
+ <p class="text-sm font-bold text-white">{{ session.get('username') }}</p>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+ <a href="/logout" title="Logout" class="bg-red-500/10 text-red-500 hover:bg-red-500 hover:text-white p-2 rounded-xl transition">
|
|
|
|
|
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"></path></svg>
|
|
|
|
|
+ </a>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </aside>
|
|
|
|
|
|
|
|
- <div class="flex gap-4 pt-4">
|
|
|
|
|
- <button type="button" onclick="closeUserModal()" class="flex-1 bg-gray-800 text-gray-400 font-bold py-3 rounded-xl uppercase text-xs hover:text-white transition">Cancel</button>
|
|
|
|
|
- <button type="submit" class="flex-1 bg-blue-600 text-white font-black py-3 rounded-xl uppercase text-xs hover:bg-blue-500 transition">Save</button>
|
|
|
|
|
- </div>
|
|
|
|
|
- </form>
|
|
|
|
|
- </div>
|
|
|
|
|
-</div>
|
|
|
|
|
-
|
|
|
|
|
-<script>
|
|
|
|
|
-function openAddUser() {
|
|
|
|
|
- document.getElementById('userModal').classList.remove('hidden');
|
|
|
|
|
- document.getElementById('userAction').value = 'add';
|
|
|
|
|
- document.getElementById('modalTitle').innerText = 'Add User';
|
|
|
|
|
- document.getElementById('userName').value = '';
|
|
|
|
|
- document.getElementById('userPass').value = '';
|
|
|
|
|
- document.getElementById('userPass').placeholder = 'Password (Required)';
|
|
|
|
|
- document.getElementById('userPass').required = true;
|
|
|
|
|
-
|
|
|
|
|
- var checkboxes = document.querySelectorAll('.template-check');
|
|
|
|
|
- for (var i = 0; i < checkboxes.length; i++) {
|
|
|
|
|
- checkboxes[i].checked = false;
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-function editUser(btn) {
|
|
|
|
|
- // Διαβάζουμε τα δεδομένα με ασφάλεια από το κουμπί
|
|
|
|
|
- var u = btn.getAttribute('data-user');
|
|
|
|
|
- var role = btn.getAttribute('data-role');
|
|
|
|
|
- var rawTemplates = btn.getAttribute('data-templates');
|
|
|
|
|
-
|
|
|
|
|
- // Σπάμε το "AWMN,LOCAL" σε λίστα
|
|
|
|
|
- var templates = rawTemplates ? rawTemplates.split(',') : [];
|
|
|
|
|
-
|
|
|
|
|
- // Εμφάνιση Modal και γέμισμα πεδίων
|
|
|
|
|
- document.getElementById('userModal').classList.remove('hidden');
|
|
|
|
|
- document.getElementById('userAction').value = 'edit';
|
|
|
|
|
- document.getElementById('oldUsername').value = u;
|
|
|
|
|
- document.getElementById('userName').value = u;
|
|
|
|
|
- document.getElementById('userRole').value = role;
|
|
|
|
|
- document.getElementById('modalTitle').innerText = 'Edit User';
|
|
|
|
|
-
|
|
|
|
|
- document.getElementById('userPass').value = '';
|
|
|
|
|
- document.getElementById('userPass').placeholder = 'Password (Leave blank to keep current)';
|
|
|
|
|
- document.getElementById('userPass').required = false;
|
|
|
|
|
|
|
+ <main class="flex-1 overflow-y-auto custom-scroll relative bg-[#0b0e14]">
|
|
|
|
|
+ <div class="p-10 max-w-[1600px] mx-auto">
|
|
|
|
|
+
|
|
|
|
|
+ {% with messages = get_flashed_messages(with_categories=true) %}
|
|
|
|
|
+ {% if messages %}
|
|
|
|
|
+ <script>
|
|
|
|
|
+ document.addEventListener('DOMContentLoaded', function() {
|
|
|
|
|
+ const Toast = Swal.mixin({ toast: true, position: 'bottom-end', showConfirmButton: false, timer: 4000, timerProgressBar: true, background: '#1f2937', color: '#fff' });
|
|
|
|
|
+ {% for category, message in messages %}
|
|
|
|
|
+ Toast.fire({ icon: '{{ "success" if category == "success" else "error" }}', title: '{{ message }}' });
|
|
|
|
|
+ {% endfor %}
|
|
|
|
|
+ });
|
|
|
|
|
+ </script>
|
|
|
|
|
+ {% endif %}
|
|
|
|
|
+ {% endwith %}
|
|
|
|
|
|
|
|
- // Τσεκάρισμα των σωστών Templates
|
|
|
|
|
- var checkboxes = document.querySelectorAll('.template-check');
|
|
|
|
|
- for (var i = 0; i < checkboxes.length; i++) {
|
|
|
|
|
- checkboxes[i].checked = templates.includes(checkboxes[i].value);
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
|
|
+ {% block content %}{% endblock %}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </main>
|
|
|
|
|
|
|
|
-function closeUserModal() {
|
|
|
|
|
- document.getElementById('userModal').classList.add('hidden');
|
|
|
|
|
-}
|
|
|
|
|
-</script>
|
|
|
|
|
-{% endblock %}
|
|
|
|
|
|
|
+ <script>
|
|
|
|
|
+ const AppToast = Swal.mixin({
|
|
|
|
|
+ toast: true, position: 'bottom-end', showConfirmButton: false,
|
|
|
|
|
+ timer: 3000, timerProgressBar: true, background: '#12161f', color: '#fff',
|
|
|
|
|
+ customClass: { popup: 'border border-gray-700 rounded-xl shadow-2xl' }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // Loader Spinner SVG
|
|
|
|
|
+ const spinnerSvg = `<svg class="animate-spin -ml-1 mr-2 h-4 w-4 text-white inline" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>`;
|
|
|
|
|
+ </script>
|
|
|
|
|
+</body>
|
|
|
|
|
+</html>
|