root 6 kuukautta sitten
vanhempi
commit
ecede8fcfe
2 muutettua tiedostoa jossa 131 lisäystä ja 113 poistoa
  1. 130 112
      templates/layout.html
  2. 1 1
      templates/users.html

+ 130 - 112
templates/layout.html

@@ -1,118 +1,136 @@
-<!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">
+{% extends "layout.html" %}
+{% 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>
 
-    <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>
-                <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>
-    </aside>
+<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>
 
-    <main class="flex-1 overflow-y-auto custom-scroll relative bg-[#0b0e14]">
-        <div class="p-10 max-w-[1600px] mx-auto">
+<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">
             
-            {% 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 }}' });
+            <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 %}
-                  });
-                </script>
-              {% endif %}
-            {% endwith %}
+                </div>
+            </div>
+
+            <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; 
 
-            {% block content %}{% endblock %}
-        </div>
-    </main>
+    // Τσεκάρισμα των σωστών Templates
+    var checkboxes = document.querySelectorAll('.template-check');
+    for (var i = 0; i < checkboxes.length; i++) {
+        checkboxes[i].checked = templates.includes(checkboxes[i].value);
+    }
+}
 
-    <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>
+function closeUserModal() { 
+    document.getElementById('userModal').classList.add('hidden'); 
+}
+</script>
+{% endblock %}

+ 1 - 1
templates/users.html

@@ -122,7 +122,7 @@ function editUser(u, role, rawTemplates) {
     var checkboxes = document.querySelectorAll('.template-check');
     for (var i = 0; i < checkboxes.length; i++) {
         checkboxes[i].checked = templates.includes(checkboxes[i].value);
-    }
+    } 
 }
 
 function closeUserModal() {