Fixed delete device and merged changes
This commit is contained in:
@@ -73,6 +73,13 @@ def init_db(app=None):
|
|||||||
)
|
)
|
||||||
''')
|
''')
|
||||||
cursor.execute('''
|
cursor.execute('''
|
||||||
|
CREATE TABLE IF NOT EXISTS DeviceType (
|
||||||
|
id INTEGER PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
name VARCHAR(255) NOT NULL UNIQUE,
|
||||||
|
icon_class VARCHAR(255) NOT NULL
|
||||||
|
)
|
||||||
|
''')
|
||||||
|
cursor.execute('''
|
||||||
CREATE TABLE IF NOT EXISTS Device (
|
CREATE TABLE IF NOT EXISTS Device (
|
||||||
id INTEGER PRIMARY KEY AUTO_INCREMENT,
|
id INTEGER PRIMARY KEY AUTO_INCREMENT,
|
||||||
name VARCHAR(255) NOT NULL,
|
name VARCHAR(255) NOT NULL,
|
||||||
@@ -100,13 +107,6 @@ def init_db(app=None):
|
|||||||
FOREIGN KEY (subnet_id) REFERENCES Subnet(id) ON DELETE CASCADE
|
FOREIGN KEY (subnet_id) REFERENCES Subnet(id) ON DELETE CASCADE
|
||||||
)
|
)
|
||||||
''')
|
''')
|
||||||
cursor.execute('''
|
|
||||||
CREATE TABLE IF NOT EXISTS DeviceType (
|
|
||||||
id INTEGER PRIMARY KEY AUTO_INCREMENT,
|
|
||||||
name VARCHAR(255) NOT NULL UNIQUE,
|
|
||||||
icon_class VARCHAR(255) NOT NULL
|
|
||||||
)
|
|
||||||
''')
|
|
||||||
cursor.execute('SELECT COUNT(*) FROM DeviceType')
|
cursor.execute('SELECT COUNT(*) FROM DeviceType')
|
||||||
if cursor.fetchone()[0] == 0:
|
if cursor.fetchone()[0] == 0:
|
||||||
cursor.executemany('INSERT INTO DeviceType (name, icon_class) VALUES (%s, %s)', [
|
cursor.executemany('INSERT INTO DeviceType (name, icon_class) VALUES (%s, %s)', [
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ def add_audit_log(user_id, action, details=None, subnet_id=None, conn=None):
|
|||||||
conn = get_db_connection(current_app)
|
conn = get_db_connection(current_app)
|
||||||
close_conn = True
|
close_conn = True
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
# Always use UTC for timestamp
|
|
||||||
utc_now = datetime.datetime.now(datetime.timezone.utc).replace(microsecond=0)
|
utc_now = datetime.datetime.now(datetime.timezone.utc).replace(microsecond=0)
|
||||||
cursor.execute('''INSERT INTO AuditLog (user_id, action, details, subnet_id, timestamp) VALUES (%s, %s, %s, %s, %s)''',
|
cursor.execute('''INSERT INTO AuditLog (user_id, action, details, subnet_id, timestamp) VALUES (%s, %s, %s, %s, %s)''',
|
||||||
(user_id, action, details, subnet_id, utc_now))
|
(user_id, action, details, subnet_id, utc_now))
|
||||||
@@ -255,16 +254,17 @@ def register_routes(app):
|
|||||||
with get_db_connection(current_app) as conn:
|
with get_db_connection(current_app) as conn:
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
cursor.execute('SELECT name FROM Device WHERE id = %s', (device_id,))
|
cursor.execute('SELECT name FROM Device WHERE id = %s', (device_id,))
|
||||||
device_name = cursor.fetchone()[0]
|
device_row = cursor.fetchone()
|
||||||
add_audit_log(user_id, 'delete_device', f"Deleted device {device_name}", conn=conn)
|
if device_row:
|
||||||
# Set hostname to NULL for all IPs associated with this device
|
device_name = device_row[0]
|
||||||
cursor.execute('SELECT ip_id FROM DeviceIPAddress WHERE device_id = %s', (device_id,))
|
add_audit_log(user_id, 'delete_device', f"Deleted device {device_name}", conn=conn)
|
||||||
ip_ids = [row[0] for row in cursor.fetchall()]
|
cursor.execute('SELECT ip_id FROM DeviceIPAddress WHERE device_id = %s', (device_id,))
|
||||||
if ip_ids:
|
ip_ids = [row[0] for row in cursor.fetchall()]
|
||||||
cursor.executemany('UPDATE IPAddress SET hostname = NULL WHERE id = %s', [(ip_id,) for ip_id in ip_ids])
|
if ip_ids:
|
||||||
cursor.execute('DELETE FROM DeviceIPAddress WHERE device_id = %s', (device_id,))
|
cursor.executemany('UPDATE IPAddress SET hostname = NULL WHERE id = %s', [(ip_id,) for ip_id in ip_ids])
|
||||||
cursor.execute('DELETE FROM Device WHERE id = %s', (device_id,))
|
cursor.execute('DELETE FROM DeviceIPAddress WHERE device_id = %s', (device_id,))
|
||||||
conn.commit()
|
cursor.execute('DELETE FROM Device WHERE id = %s', (device_id,))
|
||||||
|
conn.commit()
|
||||||
return redirect(url_for('devices'))
|
return redirect(url_for('devices'))
|
||||||
|
|
||||||
@app.route('/subnet/<int:subnet_id>')
|
@app.route('/subnet/<int:subnet_id>')
|
||||||
@@ -383,29 +383,34 @@ def register_routes(app):
|
|||||||
@app.route('/audit')
|
@app.route('/audit')
|
||||||
@login_required
|
@login_required
|
||||||
def audit():
|
def audit():
|
||||||
from flask import current_app
|
PER_PAGE = 25
|
||||||
with get_db_connection(current_app) as conn:
|
page = int(request.args.get('page', 1))
|
||||||
|
offset = (page - 1) * PER_PAGE
|
||||||
|
user_id = request.args.get('user_id')
|
||||||
|
subnet_id = request.args.get('subnet_id')
|
||||||
|
action = request.args.get('action')
|
||||||
|
device_name = request.args.get('device_name')
|
||||||
|
query = '''SELECT AuditLog.id, COALESCE(User.name, 'Deleted User'), AuditLog.action, AuditLog.details, Subnet.name, AuditLog.timestamp FROM AuditLog LEFT JOIN User ON AuditLog.user_id = User.id LEFT JOIN Subnet ON AuditLog.subnet_id = Subnet.id WHERE 1=1'''
|
||||||
|
params = []
|
||||||
|
if user_id:
|
||||||
|
query += ' AND AuditLog.user_id = %s'
|
||||||
|
params.append(user_id)
|
||||||
|
if subnet_id:
|
||||||
|
query += ' AND AuditLog.subnet_id = %s'
|
||||||
|
params.append(subnet_id)
|
||||||
|
if action:
|
||||||
|
query += ' AND AuditLog.action = %s'
|
||||||
|
params.append(action)
|
||||||
|
if device_name:
|
||||||
|
query += ' AND AuditLog.details LIKE %s'
|
||||||
|
params.append(f'%{device_name}%')
|
||||||
|
count_query = 'SELECT COUNT(*) FROM (' + query + ') AS count_subquery'
|
||||||
|
with get_db_connection() as conn:
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
user_id = request.args.get('user_id')
|
cursor.execute(count_query, params)
|
||||||
subnet_id = request.args.get('subnet_id')
|
total_logs = cursor.fetchone()[0]
|
||||||
action = request.args.get('action')
|
query += ' ORDER BY AuditLog.timestamp DESC LIMIT %s OFFSET %s'
|
||||||
device_name = request.args.get('device_name')
|
cursor.execute(query, params + [PER_PAGE, offset])
|
||||||
query = '''SELECT AuditLog.id, COALESCE(User.name, 'Deleted User'), AuditLog.action, AuditLog.details, Subnet.name, AuditLog.timestamp FROM AuditLog LEFT JOIN User ON AuditLog.user_id = User.id LEFT JOIN Subnet ON AuditLog.subnet_id = Subnet.id WHERE 1=1'''
|
|
||||||
params = []
|
|
||||||
if user_id:
|
|
||||||
query += ' AND AuditLog.user_id = %s'
|
|
||||||
params.append(user_id)
|
|
||||||
if subnet_id:
|
|
||||||
query += ' AND AuditLog.subnet_id = %s'
|
|
||||||
params.append(subnet_id)
|
|
||||||
if action:
|
|
||||||
query += ' AND AuditLog.action = %s'
|
|
||||||
params.append(action)
|
|
||||||
if device_name:
|
|
||||||
query += ' AND AuditLog.details LIKE %s'
|
|
||||||
params.append(f'%{device_name}%')
|
|
||||||
query += ' ORDER BY AuditLog.timestamp DESC'
|
|
||||||
cursor.execute(query, params)
|
|
||||||
logs = cursor.fetchall()
|
logs = cursor.fetchall()
|
||||||
cursor.execute('SELECT id, name FROM User')
|
cursor.execute('SELECT id, name FROM User')
|
||||||
users = cursor.fetchall()
|
users = cursor.fetchall()
|
||||||
@@ -415,7 +420,9 @@ def register_routes(app):
|
|||||||
actions = [row[0] for row in cursor.fetchall()]
|
actions = [row[0] for row in cursor.fetchall()]
|
||||||
cursor.execute('SELECT name FROM Device ORDER BY name')
|
cursor.execute('SELECT name FROM Device ORDER BY name')
|
||||||
devices = cursor.fetchall()
|
devices = cursor.fetchall()
|
||||||
return render_with_user('audit.html', logs=logs, users=users, subnets=subnets, actions=actions, devices=devices)
|
query_args = request.args.to_dict()
|
||||||
|
total_pages = (total_logs + PER_PAGE - 1) // PER_PAGE
|
||||||
|
return render_with_user('audit.html', logs=logs, users=users, subnets=subnets, actions=actions, devices=devices, page=page, total_pages=total_pages, query_args=query_args)
|
||||||
|
|
||||||
@app.route('/get_available_ips')
|
@app.route('/get_available_ips')
|
||||||
@login_required
|
@login_required
|
||||||
@@ -522,7 +529,6 @@ def register_routes(app):
|
|||||||
cursor.execute('UPDATE IPAddress SET hostname=NULL WHERE subnet_id=%s AND hostname="DHCP"', (subnet_id,))
|
cursor.execute('UPDATE IPAddress SET hostname=NULL WHERE subnet_id=%s AND hostname="DHCP"', (subnet_id,))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
dhcp_pool = None
|
dhcp_pool = None
|
||||||
# Audit log for DHCP pool removal
|
|
||||||
add_audit_log(user_id, 'dhcp_pool_remove', f"Removed DHCP pool for subnet {subnet[1]} ({subnet[2]})", subnet_id, conn=conn)
|
add_audit_log(user_id, 'dhcp_pool_remove', f"Removed DHCP pool for subnet {subnet[1]} ({subnet[2]})", subnet_id, conn=conn)
|
||||||
else:
|
else:
|
||||||
start_ip = request.form['start_ip']
|
start_ip = request.form['start_ip']
|
||||||
@@ -553,7 +559,6 @@ def register_routes(app):
|
|||||||
break
|
break
|
||||||
conn.commit()
|
conn.commit()
|
||||||
dhcp_pool = {'start_ip': start_ip, 'end_ip': end_ip, 'excluded_ips': excluded_ips}
|
dhcp_pool = {'start_ip': start_ip, 'end_ip': end_ip, 'excluded_ips': excluded_ips}
|
||||||
# Audit log for DHCP pool create/update
|
|
||||||
add_audit_log(user_id, action, details, subnet_id, conn=conn)
|
add_audit_log(user_id, action, details, subnet_id, conn=conn)
|
||||||
return render_with_user('dhcp.html', subnet={'id': subnet[0], 'name': subnet[1]}, dhcp_pool=dhcp_pool, error=error)
|
return render_with_user('dhcp.html', subnet={'id': subnet[0], 'name': subnet[1]}, dhcp_pool=dhcp_pool, error=error)
|
||||||
|
|
||||||
@@ -573,6 +578,38 @@ def register_routes(app):
|
|||||||
stats = cursor.fetchall()
|
stats = cursor.fetchall()
|
||||||
return render_with_user('device_type_stats.html', stats=stats)
|
return render_with_user('device_type_stats.html', stats=stats)
|
||||||
|
|
||||||
|
@app.route('/devices/type/<device_type>')
|
||||||
|
@login_required
|
||||||
|
def devices_by_type(device_type):
|
||||||
|
from flask import current_app
|
||||||
|
with get_db_connection(current_app) as conn:
|
||||||
|
cursor = conn.cursor()
|
||||||
|
cursor.execute('SELECT id, icon_class FROM DeviceType WHERE name = %s', (device_type,))
|
||||||
|
row = cursor.fetchone()
|
||||||
|
if not row:
|
||||||
|
return f"Device type '{device_type}' not found", 404
|
||||||
|
device_type_id, icon_class = row
|
||||||
|
cursor.execute('''
|
||||||
|
SELECT DISTINCT Device.id, Device.name, Device.description, Subnet.site
|
||||||
|
FROM Device
|
||||||
|
LEFT JOIN DeviceIPAddress ON Device.id = DeviceIPAddress.device_id
|
||||||
|
LEFT JOIN IPAddress ON DeviceIPAddress.ip_id = IPAddress.id
|
||||||
|
LEFT JOIN Subnet ON IPAddress.subnet_id = Subnet.id
|
||||||
|
WHERE Device.device_type_id = %s
|
||||||
|
''', (device_type_id,))
|
||||||
|
devices = cursor.fetchall()
|
||||||
|
seen_ids = set()
|
||||||
|
site_devices = {}
|
||||||
|
for device_id, name, description, site in devices:
|
||||||
|
if device_id in seen_ids:
|
||||||
|
continue
|
||||||
|
seen_ids.add(device_id)
|
||||||
|
site = site or 'Unassigned'
|
||||||
|
if site not in site_devices:
|
||||||
|
site_devices[site] = []
|
||||||
|
site_devices[site].append({'id': device_id, 'name': name, 'description': description})
|
||||||
|
return render_with_user('devices_by_type.html', device_type=device_type, icon_class=icon_class, site_devices=site_devices)
|
||||||
|
|
||||||
def get_current_user_name():
|
def get_current_user_name():
|
||||||
user_id = session.get('user_id')
|
user_id = session.get('user_id')
|
||||||
if not user_id:
|
if not user_id:
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Add Device</title>
|
<title>Add Device</title>
|
||||||
<link rel="icon" type="image/png" href="/static/logo.png">
|
<link rel="icon" type="image/x-icon" href="/static/favicon.ico">
|
||||||
<link href="/static/css/output.css" rel="stylesheet">
|
<link href="/static/css/output.css" rel="stylesheet">
|
||||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css" rel="stylesheet">
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css" rel="stylesheet">
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Admin Panel</title>
|
<title>Admin Panel</title>
|
||||||
<link rel="icon" type="image/png" href="/static/logo.png">
|
<link rel="icon" type="image/x-icon" href="/static/favicon.ico">
|
||||||
<link href="/static/css/output.css" rel="stylesheet">
|
<link href="/static/css/output.css" rel="stylesheet">
|
||||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css" rel="stylesheet">
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css" rel="stylesheet">
|
||||||
</head>
|
</head>
|
||||||
@@ -13,6 +13,10 @@
|
|||||||
<div class="flex-1 flex items-center justify-center mx-4">
|
<div class="flex-1 flex items-center justify-center mx-4">
|
||||||
<div class="container py-8 max-w-md pt-20">
|
<div class="container py-8 max-w-md pt-20">
|
||||||
<h1 class="text-3xl font-bold mb-6 text-center">Admin Panel</h1>
|
<h1 class="text-3xl font-bold mb-6 text-center">Admin Panel</h1>
|
||||||
|
<div class="flex justify-center gap-4 mb-6">
|
||||||
|
<a href="/audit" class="bg-gradient-to-r from-gray-500 to-gray-700 hover:from-gray-600 hover:to-gray-800 text-white px-4 py-2 rounded-lg shadow text-center w-40">Audit Log</a>
|
||||||
|
<a href="/users" class="bg-gradient-to-r from-gray-500 to-gray-700 hover:from-gray-600 hover:to-gray-800 text-white px-4 py-2 rounded-lg shadow text-center w-40">Users</a>
|
||||||
|
</div>
|
||||||
<hr class="border-t-2 border-gray-600 rounded-lg mb-4">
|
<hr class="border-t-2 border-gray-600 rounded-lg mb-4">
|
||||||
<h1 class="text-2xl font-bold mb-6 text-center">Add Subnet</h1>
|
<h1 class="text-2xl font-bold mb-6 text-center">Add Subnet</h1>
|
||||||
{% if error %}
|
{% if error %}
|
||||||
|
|||||||
+27
-2
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Audit Log</title>
|
<title>Audit Log</title>
|
||||||
<link rel="icon" type="image/png" href="/static/logo.png">
|
<link rel="icon" type="image/x-icon" href="/static/favicon.ico">
|
||||||
<link href="/static/css/output.css" rel="stylesheet">
|
<link href="/static/css/output.css" rel="stylesheet">
|
||||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css" rel="stylesheet">
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css" rel="stylesheet">
|
||||||
</head>
|
</head>
|
||||||
@@ -55,13 +55,38 @@
|
|||||||
<tr class="border-b border-gray-700">
|
<tr class="border-b border-gray-700">
|
||||||
<td class="px-4 py-2 text-center">{{ log[1] or 'Unknown' }}</td>
|
<td class="px-4 py-2 text-center">{{ log[1] or 'Unknown' }}</td>
|
||||||
<td class="px-4 py-2 text-center">{{ log[2] }}</td>
|
<td class="px-4 py-2 text-center">{{ log[2] }}</td>
|
||||||
<td class="px-4 py-2 text-center">{{ log[3] }}</td>
|
<td class="px-4 py-2 text-center truncate" title="{{ log[3] }}">{{ log[3][:100] ~ ('…' if log[3]|length > 100 else '') }}</td>
|
||||||
<td class="px-4 py-2 text-center">{{ log[4] or 'N/A' }}</td>
|
<td class="px-4 py-2 text-center">{{ log[4] or 'N/A' }}</td>
|
||||||
<td class="px-4 py-2 text-center" data-utc="{{ log[5] }}">{{ log[5] }}</td>
|
<td class="px-4 py-2 text-center" data-utc="{{ log[5] }}">{{ log[5] }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
{% if total_pages > 1 %}
|
||||||
|
<div class="flex justify-center mt-6 space-x-2">
|
||||||
|
{% if page > 1 %}
|
||||||
|
{% set prev_args = query_args.copy() %}
|
||||||
|
{% set _ = prev_args.update({'page': page-1}) %}
|
||||||
|
<a href="{{ url_for('audit', **prev_args) }}" class="px-3 py-1 rounded bg-gray-700 hover:bg-gray-600 flex items-center gap-2">
|
||||||
|
<i class="fa fa-angle-left"></i>
|
||||||
|
<span class="hidden sm:inline">Prev</span>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
{% for p in range(1, total_pages+1) %}
|
||||||
|
{% set page_args = query_args.copy() %}
|
||||||
|
{% set _ = page_args.update({'page': p}) %}
|
||||||
|
<a href="{{ url_for('audit', **page_args) }}" class="px-3 py-1 rounded {{ 'bg-gray-500 text-white' if p == page else 'bg-gray-700 hover:bg-gray-600' }}">{{ p }}</a>
|
||||||
|
{% endfor %}
|
||||||
|
{% if page < total_pages %}
|
||||||
|
{% set next_args = query_args.copy() %}
|
||||||
|
{% set _ = next_args.update({'page': page+1}) %}
|
||||||
|
<a href="{{ url_for('audit', **next_args) }}" class="px-3 py-1 rounded bg-gray-700 hover:bg-gray-600 flex items-center gap-2">
|
||||||
|
<span class="hidden sm:inline">Next</span>
|
||||||
|
<i class="fa fa-angle-right"></i>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>{{ device.name }} - Device Details</title>
|
<title>{{ device.name }} - Device Details</title>
|
||||||
<link rel="icon" type="image/png" href="/static/logo.png">
|
<link rel="icon" type="image/x-icon" href="/static/favicon.ico">
|
||||||
<link href="/static/css/output.css" rel="stylesheet">
|
<link href="/static/css/output.css" rel="stylesheet">
|
||||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css" rel="stylesheet">
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css" rel="stylesheet">
|
||||||
</head>
|
</head>
|
||||||
@@ -56,9 +56,6 @@
|
|||||||
</select>
|
</select>
|
||||||
<select name="ip_id" id="ip-select" class="border p-3 rounded-lg bg-gray-800 text-gray-100 border-gray-600 w-full" required>
|
<select name="ip_id" id="ip-select" class="border p-3 rounded-lg bg-gray-800 text-gray-100 border-gray-600 w-full" required>
|
||||||
<option value="" disabled selected>Select IP...</option>
|
<option value="" disabled selected>Select IP...</option>
|
||||||
{% for ip in available_ips_by_subnet.get(subnets[0].id, []) %}
|
|
||||||
<option value="{{ ip.id }}">{{ ip.ip }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
</select>
|
||||||
<button type="submit" class="bg-gradient-to-r from-gray-500 to-gray-600 hover:from-gray-600 hover:to-gray-500 text-white px-4 py-2 rounded-lg w-full">Add IP</button>
|
<button type="submit" class="bg-gradient-to-r from-gray-500 to-gray-600 hover:from-gray-600 hover:to-gray-500 text-white px-4 py-2 rounded-lg w-full">Add IP</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -24,7 +24,11 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
{% for name, icon_class, count in stats %}
|
{% for name, icon_class, count in stats %}
|
||||||
<tr class="border-b border-gray-700">
|
<tr class="border-b border-gray-700">
|
||||||
<td class="px-4 py-3 flex items-center gap-2"><i class="fas {{ icon_class }} text-blue-300"></i> {{ name }}</td>
|
<td class="px-4 py-3 flex items-center gap-2">
|
||||||
|
<a href="{{ url_for('devices_by_type', device_type=name) }}" class="hover:underline flex items-center gap-2">
|
||||||
|
<i class="fas {{ icon_class }} text-blue-300"></i> {{ name }}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
<td class="px-4 py-3 text-center font-bold">{{ count }}</td>
|
<td class="px-4 py-3 text-center font-bold">{{ count }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@@ -34,4 +38,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Device Manager</title>
|
<title>Device Manager</title>
|
||||||
<link rel="icon" type="image/png" href="/static/logo.png">
|
<link rel="icon" type="image/x-icon" href="/static/favicon.ico">
|
||||||
<link href="/static/css/output.css" rel="stylesheet">
|
<link href="/static/css/output.css" rel="stylesheet">
|
||||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css" rel="stylesheet">
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css" rel="stylesheet">
|
||||||
<link href="/static/css/devices.css" rel="stylesheet">
|
<link href="/static/css/devices.css" rel="stylesheet">
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>{{ device_type }} Devices</title>
|
||||||
|
<link rel="icon" type="image/png" href="/static/logo.png">
|
||||||
|
<link href="/static/css/output.css" rel="stylesheet">
|
||||||
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body class="bg-gray-900 text-gray-100 min-h-screen flex flex-col">
|
||||||
|
{% include 'header.html' %}
|
||||||
|
<div class="flex-1 flex items-center justify-center mx-4">
|
||||||
|
<div class="container py-8 max-w-3xl pt-20">
|
||||||
|
<h1 class="text-3xl font-bold mb-8 text-center">
|
||||||
|
<i class="fas {{ icon_class }} text-blue-300"></i> {{ device_type }} Devices
|
||||||
|
</h1>
|
||||||
|
{% for site, devices in site_devices.items() %}
|
||||||
|
<div class="mb-8">
|
||||||
|
<h2 class="text-xl font-bold mb-4 text-blue-200">{{ site }}</h2>
|
||||||
|
<div class="bg-gray-800 rounded-lg shadow-md p-4">
|
||||||
|
<table class="w-full table-auto mb-2">
|
||||||
|
<thead>
|
||||||
|
<tr class="bg-gray-700">
|
||||||
|
<th class="px-4 py-2 text-left">Device Name</th>
|
||||||
|
<th class="px-4 py-2 text-left">Description</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for device in devices %}
|
||||||
|
<tr class="border-b border-gray-700">
|
||||||
|
<td class="px-4 py-3">
|
||||||
|
<a href="/device/{{ device.id }}" class="hover:underline text-blue-300">{{ device.name }}</a>
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-3">{{ device.description or '' }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
<tfoot>
|
||||||
|
<tr class="bg-gray-900 font-bold">
|
||||||
|
<td class="px-4 py-2 text-right" colspan="2">Total: {{ devices|length }}</td>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
+2
-2
@@ -39,11 +39,11 @@
|
|||||||
<div>Start: <span class="font-mono">{{ dhcp_pool.start_ip }}</span></div>
|
<div>Start: <span class="font-mono">{{ dhcp_pool.start_ip }}</span></div>
|
||||||
<div>End: <span class="font-mono">{{ dhcp_pool.end_ip }}</span></div>
|
<div>End: <span class="font-mono">{{ dhcp_pool.end_ip }}</span></div>
|
||||||
{% if dhcp_pool.excluded_ips %}
|
{% if dhcp_pool.excluded_ips %}
|
||||||
<div>Excluded: <span class="font-mono">{{ dhcp_pool.excluded_ips }}</span></div>
|
<div>Excluded: <span class="font-mono break-words inline-block max-w-full whitespace-normal">{{ dhcp_pool.excluded_ips }}</span></div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>JDB-NET IPAM</title>
|
<title>JDB-NET IPAM</title>
|
||||||
<link rel="icon" type="image/png" href="/static/logo.png">
|
<link rel="icon" type="image/x-icon" href="/static/favicon.ico">
|
||||||
<link href="/static/css/output.css" rel="stylesheet">
|
<link href="/static/css/output.css" rel="stylesheet">
|
||||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css" rel="stylesheet">
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css" rel="stylesheet">
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>JDB-NET IPAM</title>
|
<title>JDB-NET IPAM</title>
|
||||||
<link rel="icon" type="image/png" href="/static/logo.png">
|
<link rel="icon" type="image/x-icon" href="/static/favicon.ico">
|
||||||
<link href="/static/css/output.css" rel="stylesheet">
|
<link href="/static/css/output.css" rel="stylesheet">
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-gray-900 text-gray-100 min-h-screen flex flex-col">
|
<body class="bg-gray-900 text-gray-100 min-h-screen flex flex-col">
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>User Management</title>
|
<title>User Management</title>
|
||||||
<link rel="icon" type="image/png" href="/static/logo.png">
|
<link rel="icon" type="image/x-icon" href="/static/favicon.ico">
|
||||||
<link href="/static/css/output.css" rel="stylesheet">
|
<link href="/static/css/output.css" rel="stylesheet">
|
||||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css" rel="stylesheet">
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css" rel="stylesheet">
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
Reference in New Issue
Block a user