refactor: Redone dark theme, added logging and gunicorn
This commit is contained in:
+1
-1
@@ -9,4 +9,4 @@ RUN curl -sLO https://github.com/tailwindlabs/tailwindcss/releases/latest/downlo
|
||||
&& mv tailwindcss-linux-x64 tailwindcss \
|
||||
&& ./tailwindcss -i ./static/css/input.css -o ./static/css/output.css --content "./templates/*.html,./static/js/*.js" --minify
|
||||
EXPOSE 5000
|
||||
CMD ["python", "app.py"]
|
||||
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "app:app", "--workers", "3", "--log-level", "warning"]
|
||||
+2
-1
@@ -1,3 +1,4 @@
|
||||
Flask
|
||||
mysql-connector-python
|
||||
dotenv
|
||||
dotenv
|
||||
gunicorn
|
||||
@@ -5,6 +5,7 @@ from functools import wraps
|
||||
import os
|
||||
import csv
|
||||
from io import StringIO, BytesIO
|
||||
import logging
|
||||
|
||||
app = None
|
||||
|
||||
@@ -32,6 +33,8 @@ def add_audit_log(user_id, action, details=None, subnet_id=None, conn=None):
|
||||
conn.close()
|
||||
|
||||
def register_routes(app):
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
@app.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
error = None
|
||||
@@ -46,13 +49,17 @@ def register_routes(app):
|
||||
if user and verify_password(password, user[1]):
|
||||
session['logged_in'] = True
|
||||
session['user_id'] = user[0]
|
||||
logging.info(f"User {email} logged in successfully.")
|
||||
return redirect(url_for('index'))
|
||||
else:
|
||||
logging.info(f"Failed login attempt for email: {email}")
|
||||
error = 'Invalid email or password.'
|
||||
return render_with_user('login.html', error=error)
|
||||
|
||||
@app.route('/logout')
|
||||
def logout():
|
||||
user_name = get_current_user_name()
|
||||
logging.info(f"User {user_name} logged out.")
|
||||
session.clear()
|
||||
return redirect(url_for('login'))
|
||||
|
||||
@@ -107,10 +114,12 @@ def register_routes(app):
|
||||
if request.method == 'POST':
|
||||
name = request.form['device_name']
|
||||
device_type_id = int(request.form['device_type'])
|
||||
user_name = get_current_user_name()
|
||||
with get_db_connection(current_app) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('INSERT INTO Device (name, device_type_id) VALUES (%s, %s)', (name, device_type_id))
|
||||
conn.commit()
|
||||
logging.info(f"User {user_name} added device '{name}' (type {device_type_id}).")
|
||||
return redirect(url_for('devices'))
|
||||
return render_with_user('add_device.html', device_types=device_types)
|
||||
|
||||
@@ -156,11 +165,13 @@ def register_routes(app):
|
||||
def update_device_type():
|
||||
device_id = request.form['device_id']
|
||||
device_type_id = request.form['device_type_id']
|
||||
user_name = get_current_user_name()
|
||||
from flask import current_app
|
||||
with get_db_connection(current_app) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('UPDATE Device SET device_type_id = %s WHERE id = %s', (device_type_id, device_id))
|
||||
conn.commit()
|
||||
logging.info(f"User {user_name} updated device {device_id} to type {device_type_id}.")
|
||||
return redirect(url_for('device', device_id=device_id))
|
||||
|
||||
@app.route('/device/<int:device_id>/add_ip', methods=['POST'])
|
||||
@@ -168,7 +179,7 @@ def register_routes(app):
|
||||
def device_add_ip(device_id):
|
||||
subnet_id = request.form['subnet_id']
|
||||
ip_id = request.form['ip_id']
|
||||
user_id = session.get('user_id')
|
||||
user_name = get_current_user_name()
|
||||
from flask import current_app
|
||||
with get_db_connection(current_app) as conn:
|
||||
cursor = conn.cursor()
|
||||
@@ -216,15 +227,16 @@ def register_routes(app):
|
||||
cursor.execute('SELECT name, cidr FROM Subnet WHERE id = %s', (subnet_id_val,))
|
||||
subnet_name, subnet_cidr = cursor.fetchone()
|
||||
details = f"Assigned IP {ip} ({subnet_name} {subnet_cidr}) to device {device_name}"
|
||||
add_audit_log(user_id, 'device_add_ip', details, subnet_id_val, conn=conn)
|
||||
add_audit_log(session['user_id'], 'device_add_ip', details, subnet_id_val, conn=conn)
|
||||
conn.commit()
|
||||
logging.info(f"User {user_name} assigned IP {ip} to device {device_id}.")
|
||||
return redirect(url_for('device', device_id=device_id))
|
||||
|
||||
@app.route('/device/<int:device_id>/delete_ip', methods=['POST'])
|
||||
@login_required
|
||||
def device_delete_ip(device_id):
|
||||
device_ip_id = request.form['device_ip_id']
|
||||
user_id = session.get('user_id')
|
||||
user_name = get_current_user_name()
|
||||
from flask import current_app
|
||||
with get_db_connection(current_app) as conn:
|
||||
cursor = conn.cursor()
|
||||
@@ -239,17 +251,18 @@ def register_routes(app):
|
||||
cursor.execute('SELECT name FROM Device WHERE id = %s', (device_id_val,))
|
||||
device_name = cursor.fetchone()[0]
|
||||
details = f"Removed IP {ip} ({subnet_name} {subnet_cidr}) from device {device_name}"
|
||||
add_audit_log(user_id, 'device_delete_ip', details, subnet_id_val, conn=conn)
|
||||
add_audit_log(session['user_id'], 'device_delete_ip', details, subnet_id_val, conn=conn)
|
||||
cursor.execute('DELETE FROM DeviceIPAddress WHERE id = %s', (device_ip_id,))
|
||||
cursor.execute('UPDATE IPAddress SET hostname = NULL WHERE id = %s', (ip_id,))
|
||||
conn.commit()
|
||||
logging.info(f"User {user_name} removed IP {ip} from device {device_id}.")
|
||||
return redirect(url_for('device', device_id=device_id))
|
||||
|
||||
@app.route('/delete_device', methods=['POST'])
|
||||
@login_required
|
||||
def delete_device():
|
||||
device_id = request.form['device_id']
|
||||
user_id = session.get('user_id')
|
||||
user_name = get_current_user_name()
|
||||
from flask import current_app
|
||||
with get_db_connection(current_app) as conn:
|
||||
cursor = conn.cursor()
|
||||
@@ -257,7 +270,7 @@ def register_routes(app):
|
||||
device_row = cursor.fetchone()
|
||||
if device_row:
|
||||
device_name = device_row[0]
|
||||
add_audit_log(user_id, 'delete_device', f"Deleted device {device_name}", conn=conn)
|
||||
add_audit_log(session['user_id'], 'delete_device', f"Deleted device {device_name}", conn=conn)
|
||||
cursor.execute('SELECT ip_id FROM DeviceIPAddress WHERE device_id = %s', (device_id,))
|
||||
ip_ids = [row[0] for row in cursor.fetchall()]
|
||||
if ip_ids:
|
||||
@@ -265,6 +278,7 @@ def register_routes(app):
|
||||
cursor.execute('DELETE FROM DeviceIPAddress WHERE device_id = %s', (device_id,))
|
||||
cursor.execute('DELETE FROM Device WHERE id = %s', (device_id,))
|
||||
conn.commit()
|
||||
logging.info(f"User {user_name} deleted device '{device_name}'.")
|
||||
return redirect(url_for('devices'))
|
||||
|
||||
@app.route('/subnet/<int:subnet_id>')
|
||||
@@ -298,7 +312,7 @@ def register_routes(app):
|
||||
name = request.form['name']
|
||||
cidr = request.form['cidr']
|
||||
site = request.form['site']
|
||||
user_id = session.get('user_id')
|
||||
user_name = get_current_user_name()
|
||||
try:
|
||||
network = ip_network(cidr, strict=False)
|
||||
if network.prefixlen < 24:
|
||||
@@ -312,21 +326,22 @@ def register_routes(app):
|
||||
subnet_id = cursor.lastrowid
|
||||
ip_rows = [(str(ip), subnet_id) for ip in network.hosts()]
|
||||
cursor.executemany('INSERT INTO IPAddress (ip, subnet_id) VALUES (%s, %s)', ip_rows)
|
||||
add_audit_log(user_id, 'add_subnet', f"Added subnet {name} ({cidr})", subnet_id, conn=conn)
|
||||
add_audit_log(session['user_id'], 'add_subnet', f"Added subnet {name} ({cidr})", subnet_id, conn=conn)
|
||||
conn.commit()
|
||||
logging.info(f"User {user_name} added subnet '{name}' ({cidr}) at site '{site}'.")
|
||||
return redirect(url_for('admin'))
|
||||
|
||||
@app.route('/delete_subnet', methods=['POST'])
|
||||
@login_required
|
||||
def delete_subnet():
|
||||
subnet_id = request.form['subnet_id']
|
||||
user_id = session.get('user_id')
|
||||
user_name = get_current_user_name()
|
||||
from flask import current_app
|
||||
with get_db_connection(current_app) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('SELECT name, cidr FROM Subnet WHERE id = %s', (subnet_id,))
|
||||
subnet = cursor.fetchone()
|
||||
add_audit_log(user_id, 'delete_subnet', f"Deleted subnet {subnet[0]} ({subnet[1]})", subnet_id, conn=conn)
|
||||
add_audit_log(session['user_id'], 'delete_subnet', f"Deleted subnet {subnet[0]} ({subnet[1]})", subnet_id, conn=conn)
|
||||
cursor.execute('SELECT id FROM IPAddress WHERE subnet_id = %s', (subnet_id,))
|
||||
ip_ids = [row[0] for row in cursor.fetchall()]
|
||||
if ip_ids:
|
||||
@@ -335,6 +350,7 @@ def register_routes(app):
|
||||
cursor.execute('DELETE FROM IPAddress WHERE subnet_id = %s', (subnet_id,))
|
||||
cursor.execute('DELETE FROM Subnet WHERE id = %s', (subnet_id,))
|
||||
conn.commit()
|
||||
logging.info(f"User {user_name} deleted subnet {subnet_id}.")
|
||||
return redirect(url_for('admin'))
|
||||
|
||||
@app.route('/admin', methods=['GET', 'POST'])
|
||||
@@ -355,11 +371,13 @@ def register_routes(app):
|
||||
cursor = conn.cursor()
|
||||
if request.method == 'POST':
|
||||
action = request.form['action']
|
||||
user_name = get_current_user_name()
|
||||
if action == 'add':
|
||||
name = request.form['name']
|
||||
email = request.form['email']
|
||||
password = hash_password(request.form['password'])
|
||||
cursor.execute('INSERT INTO User (name, email, password) VALUES (%s, %s, %s)', (name, email, password))
|
||||
logging.info(f"User {user_name} added user '{name}' ({email}).")
|
||||
elif action == 'edit':
|
||||
user_id = request.form['user_id']
|
||||
name = request.form['name']
|
||||
@@ -370,11 +388,13 @@ def register_routes(app):
|
||||
cursor.execute('UPDATE User SET name=%s, email=%s, password=%s WHERE id=%s', (name, email, password, user_id))
|
||||
else:
|
||||
cursor.execute('UPDATE User SET name=%s, email=%s WHERE id=%s', (name, email, user_id))
|
||||
logging.info(f"User {user_name} edited user {user_id}.")
|
||||
elif action == 'delete':
|
||||
user_id = request.form['user_id']
|
||||
cursor.execute('UPDATE User SET name=%s WHERE id=%s', ('Deleted User', user_id))
|
||||
cursor.execute('UPDATE AuditLog SET user_id=NULL WHERE user_id=%s', (user_id,))
|
||||
cursor.execute('DELETE FROM User WHERE id=%s', (user_id,))
|
||||
logging.info(f"User {user_name} deleted user {user_id}.")
|
||||
conn.commit()
|
||||
cursor.execute('SELECT id, name, email FROM User')
|
||||
users = cursor.fetchall()
|
||||
@@ -440,7 +460,7 @@ def register_routes(app):
|
||||
def rename_device():
|
||||
device_id = request.form['device_id']
|
||||
new_name = request.form['new_name']
|
||||
user_id = session.get('user_id')
|
||||
user_name = get_current_user_name()
|
||||
from flask import current_app
|
||||
with get_db_connection(current_app) as conn:
|
||||
cursor = conn.cursor()
|
||||
@@ -449,7 +469,8 @@ def register_routes(app):
|
||||
cursor.execute('UPDATE Device SET name = %s WHERE id = %s', (new_name, device_id))
|
||||
cursor.execute('UPDATE IPAddress SET hostname = %s WHERE hostname = %s', (new_name, old_name))
|
||||
conn.commit()
|
||||
add_audit_log(user_id, 'rename_device', f"Renamed device '{old_name}' to '{new_name}'", conn=conn)
|
||||
add_audit_log(session['user_id'], 'rename_device', f"Renamed device '{old_name}' to '{new_name}'", conn=conn)
|
||||
logging.info(f"User {user_name} renamed device {device_id} from '{old_name}' to '{new_name}'.")
|
||||
return redirect(url_for('device', device_id=device_id))
|
||||
|
||||
@app.route('/update_device_description', methods=['POST'])
|
||||
@@ -457,11 +478,13 @@ def register_routes(app):
|
||||
def update_device_description():
|
||||
device_id = request.form['device_id']
|
||||
description = request.form['description']
|
||||
user_name = get_current_user_name()
|
||||
from flask import current_app
|
||||
with get_db_connection(current_app) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('UPDATE Device SET description = %s WHERE id = %s', (description, device_id))
|
||||
conn.commit()
|
||||
logging.info(f"User {user_name} updated description for device {device_id}.")
|
||||
return redirect(url_for('device', device_id=device_id))
|
||||
|
||||
@app.route('/subnet/<int:subnet_id>/export_csv')
|
||||
@@ -523,13 +546,13 @@ def register_routes(app):
|
||||
if row:
|
||||
dhcp_pool = {'start_ip': row[0], 'end_ip': row[1], 'excluded_ips': row[2] if len(row) > 2 else ''}
|
||||
if request.method == 'POST':
|
||||
user_id = session.get('user_id')
|
||||
user_name = get_current_user_name()
|
||||
if 'remove' in request.form:
|
||||
cursor.execute('DELETE FROM DHCPPool WHERE subnet_id = %s', (subnet_id,))
|
||||
cursor.execute('UPDATE IPAddress SET hostname=NULL WHERE subnet_id=%s AND hostname="DHCP"', (subnet_id,))
|
||||
conn.commit()
|
||||
dhcp_pool = None
|
||||
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(session['user_id'], 'dhcp_pool_remove', f"Removed DHCP pool for subnet {subnet[1]} ({subnet[2]})", subnet_id, conn=conn)
|
||||
else:
|
||||
start_ip = request.form['start_ip']
|
||||
end_ip = request.form['end_ip']
|
||||
@@ -559,7 +582,7 @@ def register_routes(app):
|
||||
break
|
||||
conn.commit()
|
||||
dhcp_pool = {'start_ip': start_ip, 'end_ip': end_ip, 'excluded_ips': excluded_ips}
|
||||
add_audit_log(user_id, action, details, subnet_id, conn=conn)
|
||||
add_audit_log(session['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)
|
||||
|
||||
@app.route('/device_type_stats')
|
||||
@@ -638,13 +661,14 @@ def register_routes(app):
|
||||
name = request.form['name']
|
||||
site = request.form['site']
|
||||
height_u = int(request.form['height_u'])
|
||||
user_id = session.get('user_id')
|
||||
user_name = get_current_user_name()
|
||||
with get_db_connection(current_app) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('INSERT INTO Rack (name, site, height_u) VALUES (%s, %s, %s)', (name, site, height_u))
|
||||
rack_id = cursor.lastrowid
|
||||
add_audit_log(user_id, 'add_rack', f"Added rack '{name}' at site '{site}' ({height_u}U)", conn=conn)
|
||||
add_audit_log(session['user_id'], 'add_rack', f"Added rack '{name}' at site '{site}' ({height_u}U)", conn=conn)
|
||||
conn.commit()
|
||||
logging.info(f"User {user_name} added rack '{name}' at site '{site}' ({height_u}U).")
|
||||
return redirect(url_for('racks'))
|
||||
return render_with_user('add_rack.html')
|
||||
|
||||
@@ -691,7 +715,7 @@ def register_routes(app):
|
||||
device_id = int(request.form['device_id'])
|
||||
position_u = int(request.form['position_u'])
|
||||
side = request.form['side']
|
||||
user_id = session.get('user_id')
|
||||
user_name = get_current_user_name()
|
||||
from flask import current_app
|
||||
with get_db_connection(current_app) as conn:
|
||||
cursor = conn.cursor(dictionary=True)
|
||||
@@ -740,8 +764,9 @@ def register_routes(app):
|
||||
device_name = cursor2.fetchone()
|
||||
cursor2.execute('SELECT name FROM Rack WHERE id = %s', (rack_id,))
|
||||
rack_name = cursor2.fetchone()
|
||||
add_audit_log(user_id, 'rack_add_device', f"Assigned device '{device_name[0] if device_name else device_id}' to rack '{rack_name[0] if rack_name else rack_id}' U{position_u} ({side})", conn=conn)
|
||||
add_audit_log(session['user_id'], 'rack_add_device', f"Assigned device '{device_name[0] if device_name else device_id}' to rack '{rack_name[0] if rack_name else rack_id}' U{position_u} ({side})", conn=conn)
|
||||
conn.commit()
|
||||
logging.info(f"User {user_name} assigned device {device_id} to rack {rack_id} at U{position_u} ({side}).")
|
||||
return redirect(url_for('rack', rack_id=rack_id))
|
||||
|
||||
@app.route('/rack/<int:rack_id>/add_nonnet_device', methods=['POST'])
|
||||
@@ -750,7 +775,7 @@ def register_routes(app):
|
||||
device_name = request.form['device_name']
|
||||
position_u = int(request.form['position_u'])
|
||||
side = request.form['side']
|
||||
user_id = session.get('user_id')
|
||||
user_name = get_current_user_name()
|
||||
from flask import current_app
|
||||
with get_db_connection(current_app) as conn:
|
||||
cursor = conn.cursor(dictionary=True)
|
||||
@@ -798,15 +823,16 @@ def register_routes(app):
|
||||
site_devices = cursor.fetchall()
|
||||
return render_with_user('rack.html', rack=rack, rack_devices=rack_devices, site_devices=site_devices, current_side=side, error=error)
|
||||
cursor.execute('INSERT INTO RackDevice (rack_id, device_id, position_u, side, nonnet_device_name) VALUES (%s, NULL, %s, %s, %s)', (rack_id, position_u, side, device_name))
|
||||
add_audit_log(user_id, 'rack_add_nonnet_device', f"Added non-networked device '{device_name}' to rack '{rack_id}' U{position_u} ({side})", conn=conn)
|
||||
add_audit_log(session['user_id'], 'rack_add_nonnet_device', f"Added non-networked device '{device_name}' to rack '{rack_id}' U{position_u} ({side})", conn=conn)
|
||||
conn.commit()
|
||||
logging.info(f"User {user_name} added non-networked device '{device_name}' to rack {rack_id} at U{position_u} ({side}).")
|
||||
return redirect(url_for('rack', rack_id=rack_id))
|
||||
|
||||
@app.route('/rack/<int:rack_id>/remove_device', methods=['POST'])
|
||||
@login_required
|
||||
def rack_remove_device(rack_id):
|
||||
rack_device_id = int(request.form['rack_device_id'])
|
||||
user_id = session.get('user_id')
|
||||
user_name = get_current_user_name()
|
||||
from flask import current_app
|
||||
with get_db_connection(current_app) as conn:
|
||||
cursor = conn.cursor(dictionary=True)
|
||||
@@ -821,23 +847,25 @@ def register_routes(app):
|
||||
cursor.execute('SELECT name FROM Rack WHERE id = %s', (rack_id,))
|
||||
rack_name_row = cursor.fetchone()
|
||||
rack_label = rack_name_row['name'] if rack_name_row and 'name' in rack_name_row else rack_id
|
||||
add_audit_log(user_id, 'rack_remove_device', f"Removed device '{device_label}' from rack '{rack_label}' U{rd['position_u']} ({rd['side']})", conn=conn)
|
||||
add_audit_log(session['user_id'], 'rack_remove_device', f"Removed device '{device_label}' from rack '{rack_label}' U{rd['position_u']} ({rd['side']})", conn=conn)
|
||||
cursor.execute('DELETE FROM RackDevice WHERE id = %s', (rack_device_id,))
|
||||
conn.commit()
|
||||
logging.info(f"User {user_name} removed device '{device_label}' from rack {rack_label} at U{rd['position_u']} ({rd['side']}).")
|
||||
return redirect(url_for('rack', rack_id=rack_id))
|
||||
|
||||
@app.route('/rack/<int:rack_id>/delete', methods=['POST'])
|
||||
@login_required
|
||||
def delete_rack(rack_id):
|
||||
user_id = session.get('user_id')
|
||||
user_name = get_current_user_name()
|
||||
from flask import current_app
|
||||
with get_db_connection(current_app) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('SELECT name FROM Rack WHERE id = %s', (rack_id,))
|
||||
rack_name = cursor.fetchone()
|
||||
cursor.execute('DELETE FROM Rack WHERE id = %s', (rack_id,))
|
||||
add_audit_log(user_id, 'delete_rack', f"Deleted rack '{rack_name[0] if rack_name else rack_id}'", conn=conn)
|
||||
add_audit_log(session['user_id'], 'delete_rack', f"Deleted rack '{rack_name[0] if rack_name else rack_id}'", conn=conn)
|
||||
conn.commit()
|
||||
logging.info(f"User {user_name} deleted rack {rack_id}.")
|
||||
return redirect(url_for('racks'))
|
||||
|
||||
@app.route('/rack/<int:rack_id>/export_csv')
|
||||
|
||||
@@ -4,4 +4,4 @@ echo "Generating CSS..."
|
||||
./tailwindcss -i ./static/css/input.css -o ./static/css/output.css --content "./templates/*.html,./static/js/*.js" --minify
|
||||
|
||||
echo "Starting app..."
|
||||
python app.py
|
||||
gunicorn --bind 0.0.0.0:5000 app:app --workers 3 --log-level debug
|
||||
@@ -71,7 +71,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
const scrollToTopButton = document.createElement('button');
|
||||
scrollToTopButton.innerHTML = '<i class="fas fa-arrow-up"></i>';
|
||||
scrollToTopButton.style.fontSize = '26px';
|
||||
scrollToTopButton.className = 'fixed bottom-5 right-5 bg-gray-200 dark:bg-gray-800 text-black dark:text-white p-3 rounded-full shadow-lg hidden';
|
||||
scrollToTopButton.className = 'fixed bottom-5 right-5 bg-gray-200 dark:bg-zinc-800 text-black dark:text-white p-3 rounded-full shadow-lg hidden';
|
||||
scrollToTopButton.style.width = '60px';
|
||||
scrollToTopButton.style.height = '60px';
|
||||
scrollToTopButton.style.borderRadius = '50%';
|
||||
|
||||
+2
-2
@@ -8,7 +8,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const searchInput = document.createElement('input');
|
||||
searchInput.type = 'text';
|
||||
searchInput.placeholder = 'Search by IP or Hostname';
|
||||
searchInput.className = 'p-2 w-full rounded-lg bg-gray-200 dark:bg-gray-800 border border-gray-600 focus:outline-none focus:border-blue-400 mb-4 text-center';
|
||||
searchInput.className = 'p-2 w-full rounded-lg bg-gray-200 dark:bg-zinc-800 border border-gray-600 focus:outline-none focus:border-blue-400 mb-4 text-center';
|
||||
form.insertAdjacentElement('beforebegin', searchInput);
|
||||
|
||||
searchInput.addEventListener('keypress', (event) => {
|
||||
@@ -40,7 +40,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const scrollToTopButton = document.createElement('button');
|
||||
scrollToTopButton.innerHTML = '<i class="fas fa-arrow-up"></i>';
|
||||
scrollToTopButton.style.fontSize = '26px';
|
||||
scrollToTopButton.className = 'fixed bottom-5 right-5 bg-gray-200 dark:bg-gray-800 text-black dark:text-white p-3 rounded-full shadow-lg hidden';
|
||||
scrollToTopButton.className = 'fixed bottom-5 right-5 bg-gray-200 dark:bg-zinc-800 text-black dark:text-white p-3 rounded-full shadow-lg hidden';
|
||||
scrollToTopButton.style.width = '60px';
|
||||
scrollToTopButton.style.height = '60px';
|
||||
scrollToTopButton.style.borderRadius = '50%';
|
||||
|
||||
@@ -8,23 +8,23 @@
|
||||
<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-300 text-gray-900 dark:bg-gray-900 dark:text-gray-100 min-h-screen flex flex-col">
|
||||
<body class="bg-gray-300 text-gray-900 dark:bg-zinc-900 dark: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-md pt-20">
|
||||
<div class="flex items-center mb-6 relative">
|
||||
<a href="javascript:window.history.back()" class="hidden sm:flex absolute left-0 bg-gray-200 hover:bg-gray-400 dark:bg-gray-700 dark:hover:bg-gray-600 items-center justify-center rounded-full w-11 h-11"><i class="fas fa-arrow-left"></i></a>
|
||||
<a href="javascript:window.history.back()" class="hidden sm:flex absolute left-0 bg-gray-200 hover:bg-gray-400 dark:bg-zinc-700 dark:hover:bg-zinc-600 items-center justify-center rounded-full w-11 h-11"><i class="fas fa-arrow-left"></i></a>
|
||||
<h1 class="text-3xl font-bold text-center w-full">Add Device</h1>
|
||||
</div>
|
||||
<form action="/add_device" method="POST" class="flex flex-col space-y-4">
|
||||
<input type="text" name="device_name" placeholder="Device Name" class="p-3 rounded-lg bg-gray-200 dark:bg-gray-800 border border-gray-600" required>
|
||||
<input type="text" name="device_name" placeholder="Device Name" class="p-3 rounded-lg bg-gray-200 dark:bg-zinc-800 border border-gray-600" required>
|
||||
<label for="device_type" class="block mb-2">Device Type</label>
|
||||
<select id="device_type" name="device_type" class="p-2 rounded w-full mb-4 bg-gray-200 dark:bg-gray-800 border border-gray-600" required>
|
||||
<select id="device_type" name="device_type" class="p-2 rounded w-full mb-4 bg-gray-200 dark:bg-zinc-800 border border-gray-600" required>
|
||||
{% for dtype in device_types %}
|
||||
<option value="{{ dtype[0] }}">{{ dtype[1] }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<button type="submit" class="bg-gray-200 hover:bg-gray-400 dark:bg-gray-700 dark:hover:bg-gray-600 px-4 py-2 rounded-lg">Add Device</button>
|
||||
<button type="submit" class="bg-gray-200 hover:bg-gray-400 dark:bg-zinc-700 dark:hover:bg-zinc-600 px-4 py-2 rounded-lg">Add Device</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,28 +8,28 @@
|
||||
<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-300 text-gray-900 dark:bg-gray-900 dark:text-gray-100 min-h-screen flex flex-col">
|
||||
<body class="bg-gray-300 text-gray-900 dark:bg-zinc-900 dark: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-md pt-20">
|
||||
<div class="flex items-center mb-6 relative">
|
||||
<a href="/racks" class="absolute left-0 bg-gray-300 hover:bg-gray-400 dark:bg-gray-700 dark:hover:bg-gray-600 flex items-center justify-center rounded-full w-11 h-11"><i class="fas fa-arrow-left"></i></a>
|
||||
<a href="/racks" class="absolute left-0 bg-gray-300 hover:bg-gray-400 dark:bg-zinc-700 dark:hover:bg-zinc-600 flex items-center justify-center rounded-full w-11 h-11"><i class="fas fa-arrow-left"></i></a>
|
||||
<h1 class="text-3xl font-bold text-center w-full">Add Rack</h1>
|
||||
</div>
|
||||
<form action="/rack/add" method="POST" class="space-y-6 bg-gray-200 dark:bg-gray-800 rounded-lg shadow-md p-6">
|
||||
<form action="/rack/add" method="POST" class="space-y-6 bg-gray-200 dark:bg-zinc-800 rounded-lg shadow-md p-6">
|
||||
<div>
|
||||
<label for="name" class="block font-medium mb-1">Rack Name</label>
|
||||
<input type="text" id="name" name="name" class="p-3 rounded-lg bg-gray-200 dark:bg-gray-800 border border-gray-600 w-full" required>
|
||||
<input type="text" id="name" name="name" class="p-3 rounded-lg bg-gray-200 dark:bg-zinc-800 border border-gray-600 w-full" required>
|
||||
</div>
|
||||
<div>
|
||||
<label for="site" class="block font-medium mb-1">Site</label>
|
||||
<input type="text" id="site" name="site" class="p-3 rounded-lg bg-gray-200 dark:bg-gray-800 border border-gray-600 w-full" required>
|
||||
<input type="text" id="site" name="site" class="p-3 rounded-lg bg-gray-200 dark:bg-zinc-800 border border-gray-600 w-full" required>
|
||||
</div>
|
||||
<div>
|
||||
<label for="height_u" class="block font-medium mb-1">Height (U)</label>
|
||||
<input type="number" id="height_u" name="height_u" min="1" max="60" class="p-3 rounded-lg bg-gray-200 dark:bg-gray-800 border border-gray-600 w-full" required>
|
||||
<input type="number" id="height_u" name="height_u" min="1" max="60" class="p-3 rounded-lg bg-gray-200 dark:bg-zinc-800 border border-gray-600 w-full" required>
|
||||
</div>
|
||||
<button type="submit" class="bg-gray-300 hover:bg-gray-400 dark:bg-gray-700 dark:hover:bg-gray-600 px-4 py-2 rounded-lg w-full">Add Rack</button>
|
||||
<button type="submit" class="bg-gray-300 hover:bg-gray-400 dark:bg-zinc-700 dark:hover:bg-zinc-600 px-4 py-2 rounded-lg w-full">Add Rack</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,15 +8,15 @@
|
||||
<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-300 text-gray-900 dark:bg-gray-900 dark:text-gray-100 min-h-screen flex flex-col">
|
||||
<body class="bg-gray-300 text-gray-900 dark:bg-zinc-900 dark: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-md pt-20">
|
||||
<div class="bg-gray-200 dark:bg-gray-800 rounded-2xl shadow-lg p-8">
|
||||
<div class="bg-gray-200 dark:bg-zinc-800 rounded-2xl shadow-lg p-8">
|
||||
<h1 class="text-3xl font-bold mb-6 text-center">Admin</h1>
|
||||
<div class="flex justify-center gap-4 mb-6">
|
||||
<a href="/audit" class="bg-gray-300 hover:bg-gray-400 dark:bg-gray-700 dark:hover:bg-gray-600 px-4 py-2 rounded-lg shadow text-center w-40">Audit Log</a>
|
||||
<a href="/users" class="bg-gray-300 hover:bg-gray-400 dark:bg-gray-700 dark:hover:bg-gray-600 px-4 py-2 rounded-lg shadow text-center w-40">Users</a>
|
||||
<a href="/audit" class="bg-gray-300 hover:bg-gray-400 dark:bg-zinc-700 dark:hover:bg-zinc-600 px-4 py-2 rounded-lg shadow text-center w-40">Audit Log</a>
|
||||
<a href="/users" class="bg-gray-300 hover:bg-gray-400 dark:bg-zinc-700 dark:hover:bg-zinc-600 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">
|
||||
<h1 class="text-2xl font-bold mb-6 text-center">Add Subnet</h1>
|
||||
@@ -25,17 +25,17 @@
|
||||
{% endif %}
|
||||
<form action="/add_subnet" method="POST" class="mb-6" onsubmit="return validateSubnetForm();">
|
||||
<div class="flex flex-col space-y-4">
|
||||
<input type="text" name="name" placeholder="Subnet Name" class="border p-3 rounded-lg bg-gray-300 dark:bg-gray-900 border-gray-600" required>
|
||||
<input type="text" name="cidr" id="cidr-input" placeholder="CIDR (e.g., 192.168.1.0/24)" class="border p-3 rounded-lg bg-gray-300 dark:bg-gray-900 border-gray-600" required>
|
||||
<input type="text" name="site" placeholder="Site/Location" class="border p-3 rounded-lg bg-gray-300 dark:bg-gray-900 border-gray-600" required>
|
||||
<button type="submit" class="bg-gray-300 hover:bg-gray-400 dark:bg-gray-700 dark:hover:bg-gray-600 px-4 py-2 rounded-lg">Add Subnet</button>
|
||||
<input type="text" name="name" placeholder="Subnet Name" class="border p-3 rounded-lg bg-gray-300 dark:bg-zinc-900 border-gray-600" required>
|
||||
<input type="text" name="cidr" id="cidr-input" placeholder="CIDR (e.g., 192.168.1.0/24)" class="border p-3 rounded-lg bg-gray-300 dark:bg-zinc-900 border-gray-600" required>
|
||||
<input type="text" name="site" placeholder="Site/Location" class="border p-3 rounded-lg bg-gray-300 dark:bg-zinc-900 border-gray-600" required>
|
||||
<button type="submit" class="bg-gray-300 hover:bg-gray-400 dark:bg-zinc-700 dark:hover:bg-zinc-600 px-4 py-2 rounded-lg">Add Subnet</button>
|
||||
<span id="cidr-error" class="text-red-500 text-sm hidden"></span>
|
||||
</div>
|
||||
</form>
|
||||
<hr class="border-t-2 border-gray-600 rounded-lg mb-4">
|
||||
<h1 class="text-2xl font-bold mb-6 text-center">Delete Subnet</h1>
|
||||
<form action="/delete_subnet" method="POST" class="mb-6 flex items-center space-x-4 justify-center" onsubmit="return confirm('Are you sure you want to delete this subnet and all its IPs? This action is irreversible.');">
|
||||
<select name="subnet_id" class="border p-3 rounded-lg bg-gray-300 dark:bg-gray-900 border-gray-600" required>
|
||||
<select name="subnet_id" class="border p-3 rounded-lg bg-gray-300 dark:bg-zinc-900 border-gray-600" required>
|
||||
<option value="" disabled selected>Select Subnet</option>
|
||||
{% for subnet in subnets %}
|
||||
<option value="{{ subnet.id }}">{{ subnet.name }} ({{ subnet.cidr }})</option>
|
||||
|
||||
+11
-11
@@ -8,44 +8,44 @@
|
||||
<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-300 text-gray-900 dark:bg-gray-900 dark:text-gray-100 min-h-screen flex flex-col">
|
||||
<body class="bg-gray-300 text-gray-900 dark:bg-zinc-900 dark: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-8xl pt-20">
|
||||
<h1 class="text-3xl font-bold mb-6 text-center">Audit Log</h1>
|
||||
<form method="GET" class="flex flex-wrap gap-4 mb-6 justify-center">
|
||||
<select name="user_id" class="border p-2 rounded-lg bg-gray-300 dark:bg-gray-900 border-gray-600">
|
||||
<select name="user_id" class="border p-2 rounded-lg bg-gray-300 dark:bg-zinc-900 border-gray-600">
|
||||
<option value="">All Users</option>
|
||||
{% for user in users %}
|
||||
<option value="{{ user[0] }}" {% if request.args.get('user_id') == user[0]|string %}selected{% endif %}>{{ user[1] }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<select name="subnet_id" class="border p-2 rounded-lg bg-gray-300 dark:bg-gray-900 border-gray-600">
|
||||
<select name="subnet_id" class="border p-2 rounded-lg bg-gray-300 dark:bg-zinc-900 border-gray-600">
|
||||
<option value="">All Subnets</option>
|
||||
{% for subnet in subnets %}
|
||||
<option value="{{ subnet[0] }}" {% if request.args.get('subnet_id') == subnet[0]|string %}selected{% endif %}>{{ subnet[1] }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<select name="action" class="border p-2 rounded-lg bg-gray-300 dark:bg-gray-900 border-gray-600">
|
||||
<select name="action" class="border p-2 rounded-lg bg-gray-300 dark:bg-zinc-900 border-gray-600">
|
||||
<option value="">All Actions</option>
|
||||
{% for a in actions %}
|
||||
<option value="{{ a }}" {% if request.args.get('action') == a %}selected{% endif %}>{{ a }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<select name="device_name" class="border p-2 rounded-lg bg-gray-300 dark:bg-gray-900 border-gray-600">
|
||||
<select name="device_name" class="border p-2 rounded-lg bg-gray-300 dark:bg-zinc-900 border-gray-600">
|
||||
<option value="">All Devices</option>
|
||||
{% for device in devices %}
|
||||
<option value="{{ device[0] }}" {% if request.args.get('device_name') == device[0] %}selected{% endif %}>{{ device[0] }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<button type="submit" class="bg-gray-200 hover:bg-gray-400 dark:bg-gray-700 dark:hover:bg-gray-600 px-4 py-2 rounded-lg flex items-center gap-2">
|
||||
<button type="submit" class="bg-gray-200 hover:bg-gray-400 dark:bg-zinc-700 dark:hover:bg-zinc-600 px-4 py-2 rounded-lg flex items-center gap-2">
|
||||
<i class="fas fa-search"></i>
|
||||
<span>Filter</span>
|
||||
</button>
|
||||
</form>
|
||||
<table class="w-full table-auto bg-gray-200 dark:bg-gray-800 rounded-lg overflow-hidden">
|
||||
<table class="w-full table-auto bg-gray-200 dark:bg-zinc-800 rounded-lg overflow-hidden">
|
||||
<thead>
|
||||
<tr class="bg-gray-400 dark:bg-gray-700">
|
||||
<tr class="bg-gray-400 dark:bg-zinc-700">
|
||||
<th class="px-4 py-2 text-center">User</th>
|
||||
<th class="px-4 py-2 text-center">Action</th>
|
||||
<th class="px-4 py-2 text-center">Details</th>
|
||||
@@ -70,7 +70,7 @@
|
||||
{% 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-400 hover:bg-gray-200 dark:bg-gray-700 dark:hover:bg-gray-600 flex items-center gap-2">
|
||||
<a href="{{ url_for('audit', **prev_args) }}" class="px-3 py-1 rounded bg-gray-400 hover:bg-gray-200 dark:bg-zinc-700 dark:hover:bg-zinc-600 flex items-center gap-2">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
<span class="hidden sm:inline">Prev</span>
|
||||
</a>
|
||||
@@ -78,12 +78,12 @@
|
||||
{% 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-200 dark:bg-gray-500' if p == page else 'bg-gray-400 hover:bg-gray-200 dark:bg-gray-700 dark:hover:bg-gray-600' }}">{{ p }}</a>
|
||||
<a href="{{ url_for('audit', **page_args) }}" class="px-3 py-1 rounded {{ 'bg-gray-200 dark:bg-gray-500' if p == page else 'bg-gray-400 hover:bg-gray-200 dark:bg-zinc-700 dark:hover:bg-zinc-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-400 hover:bg-gray-200 dark:bg-gray-700 dark:hover:bg-gray-600 flex items-center gap-2">
|
||||
<a href="{{ url_for('audit', **next_args) }}" class="px-3 py-1 rounded bg-gray-400 hover:bg-gray-200 dark:bg-zinc-700 dark:hover:bg-zinc-600 flex items-center gap-2">
|
||||
<span class="hidden sm:inline">Next</span>
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
|
||||
+11
-11
@@ -8,16 +8,16 @@
|
||||
<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-300 text-gray-900 dark:bg-gray-900 dark:text-gray-100 min-h-screen flex flex-col">
|
||||
<body class="bg-gray-300 text-gray-900 dark:bg-zinc-900 dark: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 w-auto min-w-[20rem] max-w-2xl pt-20">
|
||||
<div class="flex items-center mb-8 relative justify-between gap-4">
|
||||
<a href="javascript:window.history.back()" class="hidden sm:flex bg-gray-200 hover:bg-gray-400 dark:bg-gray-700 dark:hover:bg-gray-600 flex items-center justify-center rounded-full w-11 h-11 shrink-0"><i class="fas fa-arrow-left"></i></a>
|
||||
<a href="javascript:window.history.back()" class="hidden sm:flex bg-gray-200 hover:bg-gray-400 dark:bg-zinc-700 dark:hover:bg-zinc-600 flex items-center justify-center rounded-full w-11 h-11 shrink-0"><i class="fas fa-arrow-left"></i></a>
|
||||
<h1 class="text-3xl font-bold text-center flex-1 min-w-0 truncate">{{ device.name }}</h1>
|
||||
<form action="/update_device_type" method="POST" class="hidden md:inline ml-2">
|
||||
<input type="hidden" name="device_id" value="{{ device.id }}">
|
||||
<select name="device_type_id" class="border p-2 rounded bg-gray-200 dark:bg-gray-800 border-gray-600" onchange="this.form.submit()">
|
||||
<select name="device_type_id" class="border p-2 rounded bg-gray-200 dark:bg-zinc-800 border-gray-600" onchange="this.form.submit()">
|
||||
{% for dtype in device_types %}
|
||||
<option value="{{ dtype[0] }}" {% if device.device_type_id == dtype[0] %}selected{% endif %}>{{ dtype[1] }}</option>
|
||||
{% endfor %}
|
||||
@@ -26,7 +26,7 @@
|
||||
<div class="flex items-center shrink-0">
|
||||
<form action="/rename_device" method="POST" class="inline">
|
||||
<input type="hidden" name="device_id" value="{{ device.id }}">
|
||||
<input type="text" name="new_name" value="{{ device.name }}" class="hidden border p-1 rounded bg-gray-200 dark:bg-gray-800 border-gray-600 w-32 mr-2" style="vertical-align: middle;" required>
|
||||
<input type="text" name="new_name" value="{{ device.name }}" class="hidden border p-1 rounded bg-gray-200 dark:bg-zinc-800 border-gray-600 w-32 mr-2" style="vertical-align: middle;" required>
|
||||
<button type="button" class="text-blue-400 hover:text-blue-600 ml-2 rename-btn" title="Rename Device"><i class="fas fa-pencil-alt"></i></button>
|
||||
<button type="submit" class="text-green-400 hover:text-green-600 ml-2 save-btn hidden" title="Save Name"><i class="fas fa-check"></i></button>
|
||||
<button type="button" class="text-gray-400 hover:text-gray-600 ml-2 cancel-btn hidden" title="Cancel"><i class="fas fa-times"></i></button>
|
||||
@@ -41,30 +41,30 @@
|
||||
</div>
|
||||
<form action="/device/{{ device.id }}/add_ip" method="POST" class="mb-6">
|
||||
<div class="flex flex-col space-y-4">
|
||||
<select name="site" id="site-select" class="border p-3 rounded-lg bg-gray-200 dark:bg-gray-800 border-gray-600 w-full" required>
|
||||
<select name="site" id="site-select" class="border p-3 rounded-lg bg-gray-200 dark:bg-zinc-800 border-gray-600 w-full" required>
|
||||
<option value="" disabled selected>Select Site...</option>
|
||||
{% set sites = subnets | map(attribute='site') | unique | list %}
|
||||
{% for site in sites %}
|
||||
<option value="{{ site }}">{{ site }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<select name="subnet_id" id="subnet-select" class="border p-3 rounded-lg bg-gray-200 dark:bg-gray-800 border-gray-600 w-full" required>
|
||||
<select name="subnet_id" id="subnet-select" class="border p-3 rounded-lg bg-gray-200 dark:bg-zinc-800 border-gray-600 w-full" required>
|
||||
<option value="" disabled selected>Select Subnet...</option>
|
||||
{% for subnet in subnets %}
|
||||
<option value="{{ subnet.id }}" data-site="{{ subnet.site }}">{{ subnet.name }} ({{ subnet.cidr }})</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<select name="ip_id" id="ip-select" class="border p-3 rounded-lg bg-gray-200 dark:bg-gray-800 border-gray-600 w-full" required>
|
||||
<select name="ip_id" id="ip-select" class="border p-3 rounded-lg bg-gray-200 dark:bg-zinc-800 border-gray-600 w-full" required>
|
||||
<option value="" disabled selected>Select IP...</option>
|
||||
</select>
|
||||
<button type="submit" class="bg-gray-200 hover:bg-gray-400 dark:bg-gray-700 dark:hover:bg-gray-600 px-4 py-2 rounded-lg w-full">Add IP</button>
|
||||
<button type="submit" class="bg-gray-200 hover:bg-gray-400 dark:bg-zinc-700 dark:hover:bg-zinc-600 px-4 py-2 rounded-lg w-full">Add IP</button>
|
||||
</div>
|
||||
</form>
|
||||
<div class="allocated-ips">
|
||||
<h3 class="text-lg font-bold mb-2">Allocated IPs:</h3>
|
||||
<ul class="space-y-2">
|
||||
{% for ip in device_ips %}
|
||||
<li class="flex justify-between items-center bg-gray-200 dark:bg-gray-700 p-2 rounded-lg">
|
||||
<li class="flex justify-between items-center bg-gray-200 dark:bg-zinc-700 p-2 rounded-lg">
|
||||
<span class="allocated-ip">{{ ip.ip }}</span>
|
||||
<form action="/device/{{ device.id }}/delete_ip" method="POST" class="inline">
|
||||
<input type="hidden" name="device_ip_id" value="{{ ip.device_ip_id }}">
|
||||
@@ -77,8 +77,8 @@
|
||||
<form action="/update_device_description" method="POST" class="mb-6 mt-4">
|
||||
<input type="hidden" name="device_id" value="{{ device.id }}">
|
||||
<label for="description" class="block mb-2 text-lg font-bold">Description</label>
|
||||
<textarea id="description" name="description" rows="3" class="border p-2 rounded-lg bg-gray-200 dark:bg-gray-800 border-gray-600 w-full resize-y" placeholder="Enter device description...">{{ device.description or '' }}</textarea>
|
||||
<button type="submit" class="bg-gray-200 hover:bg-gray-400 dark:bg-gray-700 dark:hover:bg-gray-600 px-4 py-2 rounded-lg w-full mt-2">Save Description</button>
|
||||
<textarea id="description" name="description" rows="3" class="border p-2 rounded-lg bg-gray-200 dark:bg-zinc-800 border-gray-600 w-full resize-y" placeholder="Enter device description...">{{ device.description or '' }}</textarea>
|
||||
<button type="submit" class="bg-gray-200 hover:bg-gray-400 dark:bg-zinc-700 dark:hover:bg-zinc-600 px-4 py-2 rounded-lg w-full mt-2">Save Description</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,18 +8,18 @@
|
||||
<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-300 text-gray-900 dark:bg-gray-900 dark:text-gray-100 min-h-screen flex flex-col">
|
||||
<body class="bg-gray-300 text-gray-900 dark:bg-zinc-900 dark: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-2xl pt-20">
|
||||
<div class="flex items-center mb-6 relative">
|
||||
<a href="/devices" class="hidden sm:flex absolute left-0 bg-gray-200 hover:bg-gray-400 dark:bg-gray-700 dark:hover:bg-gray-600 items-center justify-center rounded-full w-11 h-11"><i class="fas fa-arrow-left"></i></a>
|
||||
<a href="/devices" class="hidden sm:flex absolute left-0 bg-gray-200 hover:bg-gray-400 dark:bg-zinc-700 dark:hover:bg-zinc-600 items-center justify-center rounded-full w-11 h-11"><i class="fas fa-arrow-left"></i></a>
|
||||
<h1 class="text-3xl font-bold text-center w-full">Device Stats</h1>
|
||||
</div>
|
||||
<div class="bg-gray-200 dark:bg-gray-800 rounded-lg shadow-md p-6">
|
||||
<div class="bg-gray-200 dark:bg-zinc-800 rounded-lg shadow-md p-6">
|
||||
<table class="w-full table-auto">
|
||||
<thead>
|
||||
<tr class="dark:bg-gray-700">
|
||||
<tr class="dark:bg-zinc-700">
|
||||
<th class="px-4 py-2 text-left">Type</th>
|
||||
<th class="px-4 py-2 text-center">Count</th>
|
||||
</tr>
|
||||
@@ -29,7 +29,7 @@
|
||||
<tr class="border-b border-gray-700">
|
||||
<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 }} dark:text-blue-300"></i> {{ name }}
|
||||
<i class="fas {{ icon_class }} dark:text-white"></i> {{ name }}
|
||||
</a>
|
||||
</td>
|
||||
<td class="px-4 py-3 text-center font-bold">{{ count }}</td>
|
||||
|
||||
@@ -9,23 +9,23 @@
|
||||
<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">
|
||||
</head>
|
||||
<body class="bg-gray-300 text-gray-900 dark:bg-gray-900 dark:text-gray-100 min-h-screen flex flex-col">
|
||||
<body class="bg-gray-300 text-gray-900 dark:bg-zinc-900 dark: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-4xl pt-20">
|
||||
<h1 class="text-3xl font-bold mb-6 text-center">Device Manager</h1>
|
||||
<div class="flex flex-row justify-center gap-4 mb-6">
|
||||
<a href="/add_device" class="text-center bg-gray-200 hover:bg-gray-400 dark:bg-gray-700 dark:hover:bg-gray-600 px-4 py-2 rounded-lg">Add New Device</a>
|
||||
<a href="/device_type_stats" class="text-center bg-gray-200 hover:bg-gray-400 dark:bg-gray-700 dark:hover:bg-gray-600 px-4 py-2 rounded-lg">View Device Stats</a>
|
||||
<a href="/add_device" class="text-center bg-gray-200 hover:bg-gray-400 dark:bg-zinc-700 dark:hover:bg-zinc-600 px-4 py-2 rounded-lg">Add New Device</a>
|
||||
<a href="/device_type_stats" class="text-center bg-gray-200 hover:bg-gray-400 dark:bg-zinc-700 dark:hover:bg-zinc-600 px-4 py-2 rounded-lg">View Device Stats</a>
|
||||
</div>
|
||||
<div class="mb-6">
|
||||
<input type="text" id="search" placeholder="Search devices or IPs..." class="border p-3 rounded-lg bg-gray-200 dark:bg-gray-800 border-gray-600 w-full">
|
||||
<input type="text" id="search" placeholder="Search devices or IPs..." class="border p-3 rounded-lg bg-gray-200 dark:bg-zinc-800 border-gray-600 w-full">
|
||||
</div>
|
||||
<div id="site-list" class="space-y-6">
|
||||
{% for site, devices in sites_devices.items() %}
|
||||
<div class="site-group bg-gray-200 dark:bg-gray-800 rounded-lg shadow-md">
|
||||
<div class="site-group bg-gray-200 dark:bg-zinc-800 rounded-lg shadow-md">
|
||||
<div class="flex flex-row items-center justify-between p-4 cursor-pointer site-header">
|
||||
<h2 class="text-xl font-bold mb-0 dark:text-blue-300">{{ site }}</h2>
|
||||
<h2 class="text-xl font-bold mb-0 dark:text-white">{{ site }}</h2>
|
||||
<button type="button" class="expand-btn text-gray-400 hover:text-gray-200 ml-2 flex items-center" aria-label="Expand site">
|
||||
<i class="fas fa-chevron-down"></i>
|
||||
</button>
|
||||
@@ -33,13 +33,13 @@
|
||||
<ul class="device-list hidden px-6 pb-4">
|
||||
{% for device in devices %}
|
||||
<li class="my-2">
|
||||
<a href="/device/{{ device.id }}" class="flex items-center justify-between bg-gray-300 hover:bg-gray-100 dark:bg-gray-900 dark:hover:bg-gray-700 dark:text-gray-200 font-semibold rounded-lg px-4 py-2 shadow transition-colors duration-150">
|
||||
<a href="/device/{{ device.id }}" class="flex items-center justify-between bg-gray-300 hover:bg-gray-100 dark:bg-zinc-900 dark:hover:bg-gray-700 dark:text-gray-200 font-semibold rounded-lg px-4 py-2 shadow transition-colors duration-150">
|
||||
<span><i class="fas {{ device.icon_class or 'fa-server' }} mr-2"></i>{{ device.name }}</span>
|
||||
{% set ips = device_ips.get(device.id, []) %}
|
||||
<span class="flex flex-row flex-wrap justify-end items-center ml-4 text-xs text-blue-300 font-normal align-middle">
|
||||
{% if ips|length > 0 %}
|
||||
{% for ip in ips %}
|
||||
<span class="inline-block bg-gray-200 dark:bg-gray-800 text-gray-900 dark:text-blue-200 rounded px-2 py-1 ml-1 font-mono">{{ ip[1] }}</span>
|
||||
<span class="inline-block bg-gray-200 dark:bg-zinc-800 text-gray-900 dark:text-white rounded px-2 py-1 ml-1 font-mono">{{ ip[1] }}</span>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<span class="text-gray-400">No IPs</span>
|
||||
|
||||
@@ -8,23 +8,23 @@
|
||||
<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-300 text-gray-900 dark:bg-gray-900 dark:text-gray-100 min-h-screen flex flex-col">
|
||||
<body class="bg-gray-300 text-gray-900 dark:bg-zinc-900 dark: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">
|
||||
<div class="flex items-center mb-6 relative">
|
||||
<a href="/device_type_stats" class="absolute left-0 bg-gray-200 hover:bg-gray-400 dark:bg-gray-700 dark:hover:bg-gray-600 flex items-center justify-center rounded-full w-11 h-11"><i class="fas fa-arrow-left"></i></a>
|
||||
<a href="/device_type_stats" class="absolute left-0 bg-gray-200 hover:bg-gray-400 dark:bg-zinc-700 dark:hover:bg-zinc-600 flex items-center justify-center rounded-full w-11 h-11"><i class="fas fa-arrow-left"></i></a>
|
||||
<h1 class="text-3xl font-bold text-center w-full">
|
||||
<i class="fas {{ icon_class }}"></i> {{ device_type }} Devices
|
||||
</h1>
|
||||
</div>
|
||||
{% for site, devices in site_devices.items() %}
|
||||
<div class="mb-8">
|
||||
<h2 class="text-xl font-bold mb-4 dark:text-blue-200">{{ site }}</h2>
|
||||
<div class="bg-gray-200 dark:bg-gray-800 rounded-lg shadow-md p-4">
|
||||
<h2 class="text-xl font-bold mb-4 dark:text-white">{{ site }}</h2>
|
||||
<div class="bg-gray-200 dark:bg-zinc-800 rounded-lg shadow-md p-4">
|
||||
<table class="w-full table-auto mb-2">
|
||||
<thead>
|
||||
<tr class="bg-gray-200 dark:bg-gray-700">
|
||||
<tr class="bg-gray-200 dark:bg-zinc-700">
|
||||
<th class="px-4 py-2 text-left">Device Name</th>
|
||||
<th class="px-4 py-2 text-left">Description</th>
|
||||
</tr>
|
||||
@@ -33,14 +33,14 @@
|
||||
{% 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 dark:text-blue-300">{{ device.name }}</a>
|
||||
<a href="/device/{{ device.id }}" class="hover:underline dark:text-white">{{ device.name }}</a>
|
||||
</td>
|
||||
<td class="px-4 py-3">{{ device.description or '' }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr class="bg-gray-200 dark:bg-gray-900 font-bold">
|
||||
<tr class="bg-gray-200 dark:bg-zinc-900 font-bold">
|
||||
<td class="px-4 py-2 text-right" colspan="2">Total: {{ devices|length }}</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
|
||||
+9
-9
@@ -8,33 +8,33 @@
|
||||
<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-300 text-gray-900 dark:bg-gray-900 dark:text-gray-100 min-h-screen flex flex-col">
|
||||
<body class="bg-gray-300 text-gray-900 dark:bg-zinc-900 dark: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 w-full sm:max-w-3/4 pt-20">
|
||||
<div class="flex items-center mb-6 relative">
|
||||
<a href="/subnet/{{ subnet.id }}" class="absolute left-0 bg-gray-300 hover:bg-gray-400 dark:bg-gray-700 dark:hover:bg-gray-600 flex items-center justify-center rounded-full w-11 h-11"><i class="fas fa-arrow-left"></i></a>
|
||||
<a href="/subnet/{{ subnet.id }}" class="absolute left-0 bg-gray-300 hover:bg-gray-400 dark:bg-zinc-700 dark:hover:bg-zinc-600 flex items-center justify-center rounded-full w-11 h-11"><i class="fas fa-arrow-left"></i></a>
|
||||
<h1 class="text-3xl font-bold text-center w-full">DHCP Pool for {{ subnet.name }}</h1>
|
||||
</div>
|
||||
{% if error %}
|
||||
<div class="text-red-500 text-center mb-4">{{ error }}</div>
|
||||
{% endif %}
|
||||
<form action="" method="POST" class="bg-gray-200 dark:bg-gray-800 p-6 rounded-lg shadow-md flex flex-col gap-4">
|
||||
<form action="" method="POST" class="bg-gray-200 dark:bg-zinc-800 p-6 rounded-lg shadow-md flex flex-col gap-4">
|
||||
<label for="start_ip" class="font-medium">Start IP Address</label>
|
||||
<input type="text" id="start_ip" name="start_ip" class="border p-3 rounded-lg bg-gray-300 text-gray-900 dark:bg-gray-900 dark:text-gray-100 border-gray-600" placeholder="e.g. 192.168.1.100" required value="{{ dhcp_pool.start_ip if dhcp_pool else '' }}">
|
||||
<input type="text" id="start_ip" name="start_ip" class="border p-3 rounded-lg bg-gray-300 text-gray-900 dark:bg-zinc-900 dark:text-gray-100 border-gray-600" placeholder="e.g. 192.168.1.100" required value="{{ dhcp_pool.start_ip if dhcp_pool else '' }}">
|
||||
<label for="end_ip" class="font-medium">End IP Address</label>
|
||||
<input type="text" id="end_ip" name="end_ip" class="border p-3 rounded-lg bg-gray-300 text-gray-900 dark:bg-gray-900 dark:text-gray-100 border-gray-600" placeholder="e.g. 192.168.1.200" required value="{{ dhcp_pool.end_ip if dhcp_pool else '' }}">
|
||||
<input type="text" id="end_ip" name="end_ip" class="border p-3 rounded-lg bg-gray-300 text-gray-900 dark:bg-zinc-900 dark:text-gray-100 border-gray-600" placeholder="e.g. 192.168.1.200" required value="{{ dhcp_pool.end_ip if dhcp_pool else '' }}">
|
||||
<label for="excluded_ips" class="font-medium">Exclude IPs (comma separated)</label>
|
||||
<input type="text" id="excluded_ips" name="excluded_ips" class="border p-3 rounded-lg bg-gray-300 text-gray-900 dark:bg-gray-900 dark:text-gray-100 border-gray-600" placeholder="e.g. 192.168.1.105,192.168.1.110" value="{{ dhcp_pool.excluded_ips if dhcp_pool and dhcp_pool.excluded_ips else '' }}">
|
||||
<input type="text" id="excluded_ips" name="excluded_ips" class="border p-3 rounded-lg bg-gray-300 text-gray-900 dark:bg-zinc-900 dark:text-gray-100 border-gray-600" placeholder="e.g. 192.168.1.105,192.168.1.110" value="{{ dhcp_pool.excluded_ips if dhcp_pool and dhcp_pool.excluded_ips else '' }}">
|
||||
<div class="flex gap-4 mt-4">
|
||||
<button type="submit" class="bg-gray-300 hover:bg-gray-400 dark:bg-gray-700 dark:hover:bg-gray-600 px-4 py-2 rounded-lg">Save DHCP Pool</button>
|
||||
<button type="submit" class="bg-gray-300 hover:bg-gray-400 dark:bg-zinc-700 dark:hover:bg-zinc-600 px-4 py-2 rounded-lg">Save DHCP Pool</button>
|
||||
{% if dhcp_pool %}
|
||||
<button type="submit" name="remove" value="1" class="bg-gray-300 hover:bg-gray-400 dark:bg-gray-700 dark:hover:bg-gray-600 px-4 py-2 rounded-lg">Remove DHCP Pool</button>
|
||||
<button type="submit" name="remove" value="1" class="bg-gray-300 hover:bg-gray-400 dark:bg-zinc-700 dark:hover:bg-zinc-600 px-4 py-2 rounded-lg">Remove DHCP Pool</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
{% if dhcp_pool %}
|
||||
<div class="mt-8 bg-gray-200 dark:bg-gray-800 p-4 rounded-lg">
|
||||
<div class="mt-8 bg-gray-200 dark:bg-zinc-800 p-4 rounded-lg">
|
||||
<h2 class="text-xl font-bold mb-2">Current DHCP Pool</h2>
|
||||
<div>Start: <span class="font-mono">{{ dhcp_pool.start_ip }}</span></div>
|
||||
<div>End: <span class="font-mono">{{ dhcp_pool.end_ip }}</span></div>
|
||||
|
||||
+16
-16
@@ -1,4 +1,4 @@
|
||||
<header class="bg-gray-800 shadow-md py-3 px-6 flex items-center justify-between relative">
|
||||
<header class="bg-zinc-800 shadow-md py-3 px-6 flex items-center justify-between relative">
|
||||
<a href="/" class="flex items-center space-x-3">
|
||||
<img src="{{ LOGO_PNG }}" alt="Logo" class="h-8 rounded">
|
||||
<span class="text-2xl font-bold text-white">{{ NAME }} IPAM</span>
|
||||
@@ -7,14 +7,14 @@
|
||||
{% if current_user_name %}Hello, {{ current_user_name }}{% endif %}
|
||||
</div>
|
||||
<nav class="hidden md:flex items-center space-x-6" id="main-nav">
|
||||
<a href="/" class="text-gray-200 hover:text-blue-400 font-medium">Home</a>
|
||||
<a href="/devices" class="text-gray-200 hover:text-blue-400 font-medium">Devices</a>
|
||||
<a href="/racks" class="text-gray-200 hover:text-blue-400 font-medium">Racks</a>
|
||||
<a href="/admin" class="text-gray-200 hover:text-blue-400 font-medium">Admin</a>
|
||||
<a href="/audit" class="text-gray-200 hover:text-blue-400 font-medium">Audit Log</a>
|
||||
<a href="/help" class="text-gray-200 hover:text-blue-400 font-medium">Help</a>
|
||||
<a href="/" class="text-gray-200 hover:text-gray-400 font-medium">Home</a>
|
||||
<a href="/devices" class="text-gray-200 hover:text-gray-400 font-medium">Devices</a>
|
||||
<a href="/racks" class="text-gray-200 hover:text-gray-400 font-medium">Racks</a>
|
||||
<a href="/admin" class="text-gray-200 hover:text-gray-400 font-medium">Admin</a>
|
||||
<a href="/audit" class="text-gray-200 hover:text-gray-400 font-medium">Audit Log</a>
|
||||
<a href="/help" class="text-gray-200 hover:text-gray-400 font-medium">Help</a>
|
||||
{% if current_user_name %}
|
||||
<a href="/logout" class="text-gray-200 hover:text-blue-400 font-medium">Logout</a>
|
||||
<a href="/logout" class="text-gray-200 hover:text-gray-400 font-medium">Logout</a>
|
||||
{% endif %}
|
||||
</nav>
|
||||
<button class="md:hidden flex items-center text-gray-200 focus:outline-none" id="nav-toggle" aria-label="Open navigation menu">
|
||||
@@ -22,15 +22,15 @@
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="md:hidden absolute top-16 right-6 bg-gray-800 rounded-lg shadow-lg z-50 w-48 hidden flex-col py-2" id="mobile-nav">
|
||||
<a href="/" class="block px-6 py-2 text-gray-200 hover:text-blue-400 font-medium">Home</a>
|
||||
<a href="/devices" class="block px-6 py-2 text-gray-200 hover:text-blue-400 font-medium">Devices</a>
|
||||
<a href="/racks" class="block px-6 py-2 text-gray-200 hover:text-blue-400 font-medium">Racks</a>
|
||||
<a href="/admin" class="block px-6 py-2 text-gray-200 hover:text-blue-400 font-medium">Admin</a>
|
||||
<a href="/audit" class="block px-6 py-2 text-gray-200 hover:text-blue-400 font-medium">Audit Log</a>
|
||||
<a href="/help" class="block px-6 py-2 text-gray-200 hover:text-blue-400 font-medium">Help</a>
|
||||
<div class="md:hidden absolute top-16 right-6 bg-zinc-800 rounded-lg shadow-lg z-50 w-48 hidden flex-col py-2" id="mobile-nav">
|
||||
<a href="/" class="block px-6 py-2 text-gray-200 hover:text-gray-400 font-medium">Home</a>
|
||||
<a href="/devices" class="block px-6 py-2 text-gray-200 hover:text-gray-400 font-medium">Devices</a>
|
||||
<a href="/racks" class="block px-6 py-2 text-gray-200 hover:text-gray-400 font-medium">Racks</a>
|
||||
<a href="/admin" class="block px-6 py-2 text-gray-200 hover:text-gray-400 font-medium">Admin</a>
|
||||
<a href="/audit" class="block px-6 py-2 text-gray-200 hover:text-gray-400 font-medium">Audit Log</a>
|
||||
<a href="/help" class="block px-6 py-2 text-gray-200 hover:text-gray-400 font-medium">Help</a>
|
||||
{% if current_user_name %}
|
||||
<a href="/logout" class="block px-6 py-2 text-gray-200 hover:text-blue-400 font-medium">Logout</a>
|
||||
<a href="/logout" class="block px-6 py-2 text-gray-200 hover:text-gray-400 font-medium">Logout</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<script src="/static/js/header.js"></script>
|
||||
|
||||
+8
-8
@@ -8,26 +8,26 @@
|
||||
<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-300 text-gray-900 dark:bg-gray-900 dark:text-gray-100 min-h-screen flex flex-col">
|
||||
<body class="bg-gray-300 text-gray-900 dark:bg-zinc-900 dark: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-2xl pt-20">
|
||||
<h1 class="text-3xl font-bold mb-8 text-center">Help & User Guide</h1>
|
||||
<div class="space-y-10 text-lg">
|
||||
<div class="bg-gray-200 dark:bg-gray-800 rounded-2xl shadow-lg p-8">
|
||||
<div class="bg-gray-200 dark:bg-zinc-800 rounded-2xl shadow-lg p-8">
|
||||
<h2 class="text-2xl font-semibold mb-4 border-b border-gray-700 pb-2">Subnets & Devices</h2>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<h3 class="text-xl font-semibold mb-1">Managing Subnets</h3>
|
||||
<p>To add or remove subnets, go to the <a href="/admin" class="text-blue-600 dark:text-blue-400 hover:underline">Admin</a> page. Click <span class="bg-gray-300 dark:bg-gray-700 px-2 py-1 rounded">Add Subnet</span> to create a new subnet. Subnets are associated with sites.</p>
|
||||
<p>To add or remove subnets, go to the <a href="/admin" class="text-blue-600 dark:text-blue-400 hover:underline">Admin</a> page. Click <span class="bg-gray-300 dark:bg-zinc-700 px-2 py-1 rounded">Add Subnet</span> to create a new subnet. Subnets are associated with sites.</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-xl font-semibold mb-1">Adding a Device</h3>
|
||||
<p>To add a device, visit the <a href="/devices" class="text-blue-600 dark:text-blue-400 hover:underline">Devices</a> page and click <span class="bg-gray-300 dark:bg-gray-700 px-2 py-1 rounded">Add Device</span>.</p>
|
||||
<p>To add a device, visit the <a href="/devices" class="text-blue-600 dark:text-blue-400 hover:underline">Devices</a> page and click <span class="bg-gray-300 dark:bg-zinc-700 px-2 py-1 rounded">Add Device</span>.</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-xl font-semibold mb-1">Assigning IP Addresses</h3>
|
||||
<p>To assign an IP address, you must first add a device. Then, from the <a href="/devices" class="text-blue-600 dark:text-blue-400 hover:underline">Devices</a> page, click on a device to view its details and use the <span class="bg-gray-300 dark:bg-gray-700 px-2 py-1 rounded">Add IP</span> option to assign an IP address from a subnet.</p>
|
||||
<p>To assign an IP address, you must first add a device. Then, from the <a href="/devices" class="text-blue-600 dark:text-blue-400 hover:underline">Devices</a> page, click on a device to view its details and use the <span class="bg-gray-300 dark:bg-zinc-700 px-2 py-1 rounded">Add IP</span> option to assign an IP address from a subnet.</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-xl font-semibold mb-1">Viewing and Editing Devices</h3>
|
||||
@@ -35,12 +35,12 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-gray-200 dark:bg-gray-800 rounded-2xl shadow-lg p-8">
|
||||
<div class="bg-gray-200 dark:bg-zinc-800 rounded-2xl shadow-lg p-8">
|
||||
<h2 class="text-2xl font-semibold mb-4 border-b border-gray-700 pb-2">Racks</h2>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<h3 class="text-xl font-semibold mb-1">Adding a Rack</h3>
|
||||
<p>To add a new rack, go to the <a href="/racks" class="text-blue-600 dark:text-blue-400 hover:underline">Racks</a> page and click the <span class="bg-gray-300 dark:bg-gray-700 px-2 py-1 rounded">Add Rack</span> button. Fill in the details and submit the form.</p>
|
||||
<p>To add a new rack, go to the <a href="/racks" class="text-blue-600 dark:text-blue-400 hover:underline">Racks</a> page and click the <span class="bg-gray-300 dark:bg-zinc-700 px-2 py-1 rounded">Add Rack</span> button. Fill in the details and submit the form.</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-xl font-semibold mb-1">Assigning Devices to Racks</h3>
|
||||
@@ -52,7 +52,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-gray-200 dark:bg-gray-800 rounded-2xl shadow-lg p-8">
|
||||
<div class="bg-gray-200 dark:bg-zinc-800 rounded-2xl shadow-lg p-8">
|
||||
<h2 class="text-2xl font-semibold mb-4 border-b border-gray-700 pb-2">User Management & Audit</h2>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
|
||||
@@ -8,28 +8,28 @@
|
||||
<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-300 text-gray-900 dark:bg-gray-900 dark:text-gray-100 min-h-screen flex flex-col">
|
||||
<body class="bg-gray-300 text-gray-900 dark:bg-zinc-900 dark: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-md pt-20">
|
||||
<h1 class="text-3xl font-bold mb-6 text-center">{{ NAME }} IPAM</h1>
|
||||
<ul class="space-y-4">
|
||||
{% for site, subnets in sites_subnets.items() %}
|
||||
<li class="site-group bg-gray-200 dark:bg-gray-800 rounded-xl shadow-lg">
|
||||
<li class="site-group bg-gray-200 dark:bg-zinc-800 rounded-xl shadow-lg">
|
||||
<div class="flex flex-row items-center justify-between p-4 cursor-pointer site-header">
|
||||
<h2 class="text-xl font-bold mb-0 text-gray-900 dark:text-blue-300">{{ site }}</h2>
|
||||
<h2 class="text-xl font-bold mb-0 text-gray-900 dark:text-white">{{ site }}</h2>
|
||||
<button type="button" class="expand-btn ml-2 flex items-center" aria-label="Expand site">
|
||||
<i class="fas fa-chevron-down"></i>
|
||||
</button>
|
||||
</div>
|
||||
<ul class="subnet-list hidden space-y-4 px-2 pb-4">
|
||||
{% for subnet in subnets %}
|
||||
<li class="p-4 bg-gray-300 hover:bg-gray-100 dark:bg-gray-900 hover:dark:bg-gray-700 rounded-lg shadow-md flex items-center justify-between">
|
||||
<li class="p-4 bg-gray-300 hover:bg-gray-100 dark:bg-zinc-900 hover:dark:bg-zinc-700 rounded-lg shadow-md flex items-center justify-between">
|
||||
<a href="/subnet/{{ subnet.id }}">
|
||||
<p class="text-gray-900 dark:text-blue-400 text-lg font-medium">{{ subnet.name }}</p>
|
||||
<p class="text-gray-900 dark:text-white text-lg font-medium">{{ subnet.name }}</p>
|
||||
<p class="text-sm text-gray-800 dark:text-gray-400">{{ subnet.cidr }}</p>
|
||||
</a>
|
||||
<button type="button" class="export-csv-btn ml-2 bg-gray-200 hover:bg-gray-400 dark:bg-gray-600 dark:hover:bg-gray-500 flex items-center justify-center rounded-full w-9 h-9" title="Export as CSV" data-subnet-id="{{ subnet.id }}">
|
||||
<button type="button" class="export-csv-btn ml-2 bg-gray-200 hover:bg-gray-400 dark:bg-zinc-600 dark:hover:bg-zinc-500 flex items-center justify-center rounded-full w-9 h-9" title="Export as CSV" data-subnet-id="{{ subnet.id }}">
|
||||
<i class="fas fa-file-csv"></i>
|
||||
</button>
|
||||
</li>
|
||||
|
||||
@@ -8,16 +8,16 @@
|
||||
<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-300 text-gray-900 dark:bg-gray-900 dark:text-gray-100 min-h-screen flex flex-col">
|
||||
<body class="bg-gray-300 text-gray-900 dark:bg-zinc-900 dark: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-md pt-20">
|
||||
<div class="bg-gray-200 dark:bg-gray-800 rounded-2xl shadow-lg p-8">
|
||||
<div class="bg-gray-200 dark:bg-zinc-800 rounded-2xl shadow-lg p-8">
|
||||
<h1 class="text-3xl font-bold mb-6 text-center">{{ NAME }} IPAM</h1>
|
||||
<form action="/login" method="POST" class="flex flex-col space-y-4">
|
||||
<input type="email" name="email" placeholder="Email Address" class="p-3 rounded-lg bg-gray-200 dark:bg-gray-800 border border-gray-600" required>
|
||||
<input type="password" name="password" placeholder="Password" class="p-3 rounded-lg bg-gray-200 dark:bg-gray-800 border border-gray-600" required>
|
||||
<button type="submit" class="bg-gray-300 hover:bg-gray-400 dark:bg-gray-700 dark:hover:bg-gray-600 px-4 py-2 rounded-lg flex items-center gap-2 justify-center w-full">
|
||||
<input type="email" name="email" placeholder="Email Address" class="p-3 rounded-lg bg-gray-200 dark:bg-zinc-800 border border-gray-600" required>
|
||||
<input type="password" name="password" placeholder="Password" class="p-3 rounded-lg bg-gray-200 dark:bg-zinc-800 border border-gray-600" required>
|
||||
<button type="submit" class="bg-gray-300 hover:bg-gray-400 dark:bg-zinc-700 dark:hover:bg-zinc-600 px-4 py-2 rounded-lg flex items-center gap-2 justify-center w-full">
|
||||
<i class="fas fa-sign-in-alt"></i>
|
||||
<span>Login</span>
|
||||
</button>
|
||||
|
||||
+24
-24
@@ -8,20 +8,20 @@
|
||||
<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-300 text-gray-900 dark:bg-gray-900 dark:text-gray-100 min-h-screen flex flex-col">
|
||||
<body class="bg-gray-300 text-gray-900 dark:bg-zinc-900 dark: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-2xl pt-20">
|
||||
<div class="flex flex-col items-center mb-6 relative min-h-[3.5rem]">
|
||||
<a href="/racks" class="hidden sm:flex absolute left-0 bg-gray-200 hover:bg-gray-400 dark:bg-gray-700 dark:hover:bg-gray-600 items-center justify-center rounded-full w-11 h-11"><i class="fas fa-arrow-left"></i></a>
|
||||
<a href="/racks" class="hidden sm:flex absolute left-0 bg-gray-200 hover:bg-gray-400 dark:bg-zinc-700 dark:hover:bg-zinc-600 items-center justify-center rounded-full w-11 h-11"><i class="fas fa-arrow-left"></i></a>
|
||||
<h1 class="text-3xl font-bold text-center w-full mb-0">{{ rack.name }}</h1>
|
||||
<span class="text-base mt-1">({{ rack.height_u }}U, {{ rack.site }})</span>
|
||||
<form action="/rack/{{ rack.id }}/delete" method="POST" onsubmit="return confirm('Delete this rack?');" class="hidden sm:flex absolute right-0 mr-14">
|
||||
<button type="submit" class="bg-gray-200 hover:bg-gray-400 dark:bg-gray-700 dark:hover:bg-gray-600 items-center justify-center rounded-full w-11 h-11 flex" title="Delete Rack">
|
||||
<button type="submit" class="bg-gray-200 hover:bg-gray-400 dark:bg-zinc-700 dark:hover:bg-zinc-600 items-center justify-center rounded-full w-11 h-11 flex" title="Delete Rack">
|
||||
<i class="fas fa-times fa-lg"></i>
|
||||
</button>
|
||||
</form>
|
||||
<button type="button" id="export-csv" class="hidden sm:flex absolute right-0 bg-gray-200 hover:bg-gray-400 dark:bg-gray-700 dark:hover:bg-gray-600 items-center justify-center rounded-full w-11 h-11 export-csv-btn" title="Export as CSV" data-rack-id="{{ rack.id }}">
|
||||
<button type="button" id="export-csv" class="hidden sm:flex absolute right-0 bg-gray-200 hover:bg-gray-400 dark:bg-zinc-700 dark:hover:bg-zinc-600 items-center justify-center rounded-full w-11 h-11 export-csv-btn" title="Export as CSV" data-rack-id="{{ rack.id }}">
|
||||
<i class="fas fa-file-csv fa-lg"></i>
|
||||
</button>
|
||||
<script>
|
||||
@@ -37,17 +37,17 @@
|
||||
</div>
|
||||
<div class="flex flex-col gap-4 mb-6 items-stretch">
|
||||
<div class="flex gap-4 w-full justify-center">
|
||||
<a href="?side=front" id="front-btn" class="rack-side-btn px-4 py-2 rounded-lg font-semibold bg-gray-200 hover:bg-gray-400 dark:bg-gray-700 dark:hover:bg-gray-600 {% if current_side == 'front' %}ring-2 ring-gray-400{% endif %}">Front</a>
|
||||
<a href="?side=back" id="back-btn" class="rack-side-btn px-4 py-2 rounded-lg font-semibold bg-gray-200 hover:bg-gray-400 dark:bg-gray-700 dark:hover:bg-gray-600 {% if current_side == 'back' %}ring-2 ring-gray-400{% endif %}">Back</a>
|
||||
<a href="?side=front" id="front-btn" class="rack-side-btn px-4 py-2 rounded-lg font-semibold bg-gray-200 hover:bg-gray-400 dark:bg-zinc-700 dark:hover:bg-zinc-600 {% if current_side == 'front' %}ring-2 ring-gray-400{% endif %}">Front</a>
|
||||
<a href="?side=back" id="back-btn" class="rack-side-btn px-4 py-2 rounded-lg font-semibold bg-gray-200 hover:bg-gray-400 dark:bg-zinc-700 dark:hover:bg-zinc-600 {% if current_side == 'back' %}ring-2 ring-gray-400{% endif %}">Back</a>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-4 w-full justify-center">
|
||||
<button id="show-add-device-form" type="button" class="flex-1 min-w-[12rem] max-w-[16rem] bg-gray-200 hover:bg-gray-400 dark:bg-gray-700 dark:hover:bg-gray-600 px-4 py-2 rounded-lg flex-shrink-0 whitespace-nowrap"> <i class="fas fa-plus"></i> Add Device</button>
|
||||
<button id="show-nonnet-form" type="button" class="flex-[1.5_1.5_0%] min-w-[16rem] max-w-[28rem] bg-gray-200 hover:bg-gray-400 dark:bg-gray-700 dark:hover:bg-gray-600 px-4 py-2 rounded-lg flex-shrink-0 whitespace-nowrap"> <i class="fas fa-plus"></i> Add Non-Networked Device</button>
|
||||
<button id="show-add-device-form" type="button" class="flex-1 min-w-[12rem] max-w-[16rem] bg-gray-200 hover:bg-gray-400 dark:bg-zinc-700 dark:hover:bg-zinc-600 px-4 py-2 rounded-lg flex-shrink-0 whitespace-nowrap"> <i class="fas fa-plus"></i> Add Device</button>
|
||||
<button id="show-nonnet-form" type="button" class="flex-[1.5_1.5_0%] min-w-[16rem] max-w-[28rem] bg-gray-200 hover:bg-gray-400 dark:bg-zinc-700 dark:hover:bg-zinc-600 px-4 py-2 rounded-lg flex-shrink-0 whitespace-nowrap"> <i class="fas fa-plus"></i> Add Non-Networked Device</button>
|
||||
</div>
|
||||
</div>
|
||||
<form id="add-device-form" action="/rack/{{ rack.id }}/add_device" method="POST" class="hidden mb-6 bg-gray-200 dark:bg-gray-800 rounded-lg p-4">
|
||||
<form id="add-device-form" action="/rack/{{ rack.id }}/add_device" method="POST" class="hidden mb-6 bg-gray-200 dark:bg-zinc-800 rounded-lg p-4">
|
||||
<div class="flex flex-col md:flex-row gap-2 mb-2">
|
||||
<select name="device_id" class="border p-2 rounded bg-gray-300 text-gray-900 dark:bg-gray-900 dark:text-gray-100 border-gray-600 w-full md:flex-1" required>
|
||||
<select name="device_id" class="border p-2 rounded bg-gray-300 text-gray-900 dark:bg-zinc-900 dark:text-gray-100 border-gray-600 w-full md:flex-1" required>
|
||||
<option value="" disabled selected>Select Device...</option>
|
||||
{% for device in site_devices %}
|
||||
{% if device.device_type_id != 2 and device.device_type_id != 6 %}
|
||||
@@ -55,26 +55,26 @@
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
<input type="number" name="position_u" min="1" max="{{ rack.height_u }}" class="border p-2 rounded bg-gray-300 text-gray-900 dark:bg-gray-900 dark:text-gray-100 border-gray-600 w-full md:w-24" placeholder="U" required>
|
||||
<select name="side" class="border p-2 rounded bg-gray-300 text-gray-900 dark:bg-gray-900 dark:text-gray-100 border-gray-600 w-full md:w-28" required>
|
||||
<input type="number" name="position_u" min="1" max="{{ rack.height_u }}" class="border p-2 rounded bg-gray-300 text-gray-900 dark:bg-zinc-900 dark:text-gray-100 border-gray-600 w-full md:w-24" placeholder="U" required>
|
||||
<select name="side" class="border p-2 rounded bg-gray-300 text-gray-900 dark:bg-zinc-900 dark:text-gray-100 border-gray-600 w-full md:w-28" required>
|
||||
<option value="front">Front</option>
|
||||
<option value="back">Back</option>
|
||||
</select>
|
||||
<button type="submit" class="w-full md:w-auto bg-gray-300 hover:bg-gray-400 dark:bg-gray-700 dark:hover:bg-gray-600 px-4 py-2 rounded-lg">Add Device</button>
|
||||
<button id="hide-add-device-form" type="button" class="w-full md:w-auto bg-gray-300 hover:bg-gray-400 dark:bg-gray-700 dark:hover:bg-gray-600 px-4 py-2 rounded-lg md:ml-0.5 mt-2 md:mt-0 flex-shrink-0"><i class="fas fa-times"></i></button>
|
||||
<button type="submit" class="w-full md:w-auto bg-gray-300 hover:bg-gray-400 dark:bg-zinc-700 dark:hover:bg-zinc-600 px-4 py-2 rounded-lg">Add Device</button>
|
||||
<button id="hide-add-device-form" type="button" class="w-full md:w-auto bg-gray-300 hover:bg-gray-400 dark:bg-zinc-700 dark:hover:bg-zinc-600 px-4 py-2 rounded-lg md:ml-0.5 mt-2 md:mt-0 flex-shrink-0"><i class="fas fa-times"></i></button>
|
||||
</div>
|
||||
<div class="text-xs dark:text-gray-400">To add a multi-U device, repeat for each U position.</div>
|
||||
</form>
|
||||
<form id="nonnet-form" action="/rack/{{ rack.id }}/add_nonnet_device" method="POST" class="hidden flex flex-col gap-2 mt-2 mb-6 bg-gray-200 dark:bg-gray-800 rounded-lg p-4">
|
||||
<form id="nonnet-form" action="/rack/{{ rack.id }}/add_nonnet_device" method="POST" class="hidden flex flex-col gap-2 mt-2 mb-6 bg-gray-200 dark:bg-zinc-800 rounded-lg p-4">
|
||||
<div class="flex flex-col md:flex-row gap-2">
|
||||
<input type="text" name="device_name" class="border p-2 rounded bg-gray-300 text-gray-900 dark:bg-gray-900 dark:text-gray-100 border-gray-600 w-full md:flex-1" placeholder="Device Name" required>
|
||||
<input type="number" name="position_u" min="1" max="{{ rack.height_u }}" class="border p-2 rounded bg-gray-300 text-gray-900 dark:bg-gray-900 dark:text-gray-100 border-gray-600 w-full md:w-24" placeholder="U" required>
|
||||
<select name="side" class="border p-2 rounded bg-gray-300 text-gray-900 dark:bg-gray-900 dark:text-gray-100 border-gray-600 w-full md:w-28" required>
|
||||
<input type="text" name="device_name" class="border p-2 rounded bg-gray-300 text-gray-900 dark:bg-zinc-900 dark:text-gray-100 border-gray-600 w-full md:flex-1" placeholder="Device Name" required>
|
||||
<input type="number" name="position_u" min="1" max="{{ rack.height_u }}" class="border p-2 rounded bg-gray-300 text-gray-900 dark:bg-zinc-900 dark:text-gray-100 border-gray-600 w-full md:w-24" placeholder="U" required>
|
||||
<select name="side" class="border p-2 rounded bg-gray-300 text-gray-900 dark:bg-zinc-900 dark:text-gray-100 border-gray-600 w-full md:w-28" required>
|
||||
<option value="front">Front</option>
|
||||
<option value="back">Back</option>
|
||||
</select>
|
||||
<button type="submit" class="w-full md:w-auto bg-gray-300 hover:bg-gray-400 dark:bg-gray-700 dark:hover:bg-gray-600 px-4 py-2 rounded-lg">Add</button>
|
||||
<button id="hide-nonnet-form" type="button" class="w-full md:w-auto bg-gray-300 hover:bg-gray-400 dark:bg-gray-700 dark:hover:bg-gray-600 px-4 py-2 rounded-lg md:ml-0.5 mt-2 md:mt-0 flex-shrink-0"><i class="fas fa-times"></i></button>
|
||||
<button type="submit" class="w-full md:w-auto bg-gray-300 hover:bg-gray-400 dark:bg-zinc-700 dark:hover:bg-zinc-600 px-4 py-2 rounded-lg">Add</button>
|
||||
<button id="hide-nonnet-form" type="button" class="w-full md:w-auto bg-gray-300 hover:bg-gray-400 dark:bg-zinc-700 dark:hover:bg-zinc-600 px-4 py-2 rounded-lg md:ml-0.5 mt-2 md:mt-0 flex-shrink-0"><i class="fas fa-times"></i></button>
|
||||
</div>
|
||||
<div class="text-xs dark:text-gray-400 mt-2">Add a non-networked device.</div>
|
||||
</form>
|
||||
@@ -106,8 +106,8 @@
|
||||
{% if error %}
|
||||
<div class="mb-4 p-3 bg-red-700 text-white rounded-lg text-center font-semibold">{{ error }}</div>
|
||||
{% endif %}
|
||||
<div id="rack-visual" class="bg-gray-200 dark:bg-gray-800 rounded-lg shadow-md p-4">
|
||||
<h2 class="text-xl font-bold mb-2 dark:text-blue-200" id="rack-side-label">{{ current_side|capitalize }}</h2>
|
||||
<div id="rack-visual" class="bg-gray-200 dark:bg-zinc-800 rounded-lg shadow-md p-4">
|
||||
<h2 class="text-xl font-bold mb-2 dark:text-white" id="rack-side-label">{{ current_side|capitalize }}</h2>
|
||||
<div id="rack-units">
|
||||
{% for u in range(rack.height_u, 0, -1) %}
|
||||
<div class="flex items-center h-10 border-b border-gray-700 hover:bg-gray-700/30 transition group">
|
||||
@@ -117,9 +117,9 @@
|
||||
{% for rd in rack_devices %}
|
||||
{% if rd.position_u == u and rd.side == current_side %}
|
||||
{% if rd.device_id %}
|
||||
<a href="/device/{{ rd.device_id }}" class="dark:text-blue-400 hover:underline text-base">{{ rd.device_name }}</a>
|
||||
<a href="/device/{{ rd.device_id }}" class="dark:text-white hover:underline text-base">{{ rd.device_name }}</a>
|
||||
{% else %}
|
||||
<span class="dark:text-blue-200 text-base">{{ rd.nonnet_device_name }}</span>
|
||||
<span class="dark:text-white text-base">{{ rd.nonnet_device_name }}</span>
|
||||
{% endif %}
|
||||
<form action="/rack/{{ rack.id }}/remove_device" method="POST" style="display:inline" onsubmit="return confirm('Are you sure you want to remove this device from the rack?');">
|
||||
<input type="hidden" name="rack_device_id" value="{{ rd.id }}">
|
||||
|
||||
@@ -8,19 +8,19 @@
|
||||
<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-300 text-gray-900 dark:bg-gray-900 dark:text-gray-100 min-h-screen flex flex-col">
|
||||
<body class="bg-gray-300 text-gray-900 dark:bg-zinc-900 dark: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-md pt-20">
|
||||
<h1 class="text-3xl font-bold mb-8 text-center">Racks</h1>
|
||||
<div class="flex justify-center mb-6">
|
||||
<a href="/rack/add" class="bg-gray-200 hover:bg-gray-400 dark:bg-gray-700 dark:hover:bg-gray-600 px-4 py-2 rounded-lg inline-block"><i class="fas fa-plus"></i> Add Rack</a>
|
||||
<a href="/rack/add" class="bg-gray-200 hover:bg-gray-400 dark:bg-zinc-700 dark:hover:bg-zinc-600 px-4 py-2 rounded-lg inline-block"><i class="fas fa-plus"></i> Add Rack</a>
|
||||
</div>
|
||||
<div class="space-y-6">
|
||||
{% for rack in racks %}
|
||||
<a href="/rack/{{ rack.id }}" class="bg-gray-200 hover:bg-gray-100 dark:bg-gray-800 hover:dark:bg-gray-700 rounded-lg shadow-md p-4 flex items-start justify-between hover:ring-2 hover:ring-gray-400 transition group">
|
||||
<a href="/rack/{{ rack.id }}" class="bg-gray-200 hover:bg-gray-100 dark:bg-zinc-800 hover:dark:bg-zinc-700 rounded-lg shadow-md p-4 flex items-start justify-between hover:ring-2 hover:ring-gray-400 transition group">
|
||||
<div>
|
||||
<h2 class="text-xl font-bold dark:text-blue-200 mb-1">{{ rack.name }}</h2>
|
||||
<h2 class="text-xl font-bold dark:text-white mb-1">{{ rack.name }}</h2>
|
||||
<div class="dark:text-gray-400">Site: {{ rack.site }} | Height: {{ rack.height_u }}U</div>
|
||||
</div>
|
||||
<div class="ml-6 flex-shrink-0">
|
||||
|
||||
@@ -9,23 +9,23 @@
|
||||
<script src="/static/js/subnet.js"></script>
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css" rel="stylesheet">
|
||||
</head>
|
||||
<body class="bg-gray-300 text-gray-900 dark:bg-gray-900 dark:text-gray-100 min-h-screen flex flex-col">
|
||||
<body class="bg-gray-300 text-gray-900 dark:bg-zinc-900 dark: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 w-full sm:max-w-3/4 pt-20">
|
||||
<div class="flex items-center mb-6 relative">
|
||||
<a href="javascript:window.history.back()" class="hidden sm:flex absolute left-0 bg-gray-200 hover:bg-gray-400 dark:bg-gray-700 dark:hover:bg-gray-600 items-center justify-center rounded-full w-11 h-11"><i class="fas fa-arrow-left"></i></a>
|
||||
<a href="javascript:window.history.back()" class="hidden sm:flex absolute left-0 bg-gray-200 hover:bg-gray-400 dark:bg-zinc-700 dark:hover:bg-zinc-600 items-center justify-center rounded-full w-11 h-11"><i class="fas fa-arrow-left"></i></a>
|
||||
<h1 class="text-3xl font-bold text-center w-full">{{ subnet.name }} ({{ subnet.cidr }})</h1>
|
||||
<button type="button" id="export-csv" class="hidden sm:flex absolute right-0 bg-gray-200 hover:bg-gray-400 dark:bg-gray-700 dark:hover:bg-gray-600 items-center justify-center rounded-full w-11 h-11 export-csv-btn" title="Export as CSV" data-subnet-id="{{ subnet.id }}">
|
||||
<button type="button" id="export-csv" class="hidden sm:flex absolute right-0 bg-gray-200 hover:bg-gray-400 dark:bg-zinc-700 dark:hover:bg-zinc-600 items-center justify-center rounded-full w-11 h-11 export-csv-btn" title="Export as CSV" data-subnet-id="{{ subnet.id }}">
|
||||
<i class="fas fa-file-csv fa-lg"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex justify-center mb-4">
|
||||
<a href="/subnet/{{ subnet.id }}/dhcp" class="hidden sm:flex bg-gray-200 hover:bg-gray-400 dark:bg-gray-700 dark:hover:bg-gray-600 px-4 py-2 rounded-lg shadow items-center gap-2">
|
||||
<a href="/subnet/{{ subnet.id }}/dhcp" class="hidden sm:flex bg-gray-200 hover:bg-gray-400 dark:bg-zinc-700 dark:hover:bg-zinc-600 px-4 py-2 rounded-lg shadow items-center gap-2">
|
||||
<i class="fas fa-network-wired"></i> Define DHCP Pool
|
||||
</a>
|
||||
</div>
|
||||
<button id="toggle-desc" class="sm:hidden bg-gray-300 hover:bg-gray-400 dark:bg-gray-700 dark:hover:bg-gray-600 px-4 py-2 rounded-lg mb-4 w-full">Show Descriptions</button>
|
||||
<button id="toggle-desc" class="sm:hidden bg-gray-300 hover:bg-gray-400 dark:bg-zinc-700 dark:hover:bg-zinc-600 px-4 py-2 rounded-lg mb-4 w-full">Show Descriptions</button>
|
||||
<form action="" method="POST">
|
||||
<table class="table-auto w-full mb-6">
|
||||
<thead>
|
||||
|
||||
+11
-11
@@ -8,31 +8,31 @@
|
||||
<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-300 text-gray-900 dark:bg-gray-900 dark:text-gray-100 min-h-screen flex flex-col">
|
||||
<body class="bg-gray-300 text-gray-900 dark:bg-zinc-900 dark: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-6xl pt-20">
|
||||
<h1 class="text-3xl font-bold mb-6 text-center">User Management</h1>
|
||||
<form action="/users" method="POST" class="mb-8 bg-gray-200 dark:bg-gray-800 p-6 rounded-lg shadow-md">
|
||||
<form action="/users" method="POST" class="mb-8 bg-gray-200 dark:bg-zinc-800 p-6 rounded-lg shadow-md">
|
||||
<input type="hidden" name="action" value="add">
|
||||
<div class="flex flex-col space-y-4 items-center w-full">
|
||||
<input type="text" name="name" placeholder="Name" class="border p-3 rounded-lg bg-gray-300 text-gray-900 dark:bg-gray-900 dark:text-gray-100 border-gray-600 w-full" required>
|
||||
<input type="email" name="email" placeholder="Email Address" class="border p-3 rounded-lg bg-gray-300 text-gray-900 dark:bg-gray-900 dark:text-gray-100 border-gray-600 w-full" required>
|
||||
<input type="password" name="password" placeholder="Password" class="border p-3 rounded-lg bg-gray-300 text-gray-900 dark:bg-gray-900 dark:text-gray-100 border-gray-600 w-full" required>
|
||||
<button type="submit" class="bg-gray-300 hover:bg-gray-400 dark:bg-gray-700 dark:hover:bg-gray-600 px-4 py-2 rounded-lg max-w-md w-full sm:w-auto">Add User</button>
|
||||
<input type="text" name="name" placeholder="Name" class="border p-3 rounded-lg bg-gray-300 text-gray-900 dark:bg-zinc-900 dark:text-gray-100 border-gray-600 w-full" required>
|
||||
<input type="email" name="email" placeholder="Email Address" class="border p-3 rounded-lg bg-gray-300 text-gray-900 dark:bg-zinc-900 dark:text-gray-100 border-gray-600 w-full" required>
|
||||
<input type="password" name="password" placeholder="Password" class="border p-3 rounded-lg bg-gray-300 text-gray-900 dark:bg-zinc-900 dark:text-gray-100 border-gray-600 w-full" required>
|
||||
<button type="submit" class="bg-gray-300 hover:bg-gray-400 dark:bg-zinc-700 dark:hover:bg-zinc-600 px-4 py-2 rounded-lg max-w-md w-full sm:w-auto">Add User</button>
|
||||
</div>
|
||||
</form>
|
||||
<h2 class="text-xl font-bold mb-4">Existing Users</h2>
|
||||
<ul class="space-y-4">
|
||||
{% for user in users %}
|
||||
<li class="bg-gray-200 dark:bg-gray-800 p-4 rounded-lg flex justify-between items-center">
|
||||
<li class="bg-gray-200 dark:bg-zinc-800 p-4 rounded-lg flex justify-between items-center">
|
||||
<form action="/users" method="POST" class="flex flex-row items-center space-x-2">
|
||||
<input type="hidden" name="action" value="edit">
|
||||
<input type="hidden" name="user_id" value="{{ user[0] }}">
|
||||
<input type="text" name="name" value="{{ user[1] }}" class="border p-2 rounded-lg bg-gray-300 text-gray-900 dark:bg-gray-900 dark:text-gray-100 border-gray-600 w-52">
|
||||
<input type="email" name="email" value="{{ user[2] }}" class="border p-2 rounded-lg bg-gray-300 text-gray-900 dark:bg-gray-900 dark:text-gray-100 border-gray-600 w-80">
|
||||
<input type="password" name="password" placeholder="New Password (leave blank to keep)" class="border p-2 rounded-lg bg-gray-300 text-gray-900 dark:bg-gray-900 dark:text-gray-100 border-gray-600 w-80">
|
||||
<button type="submit" class="bg-gray-300 hover:bg-gray-400 dark:bg-gray-700 dark:hover:bg-gray-600 px-3 py-1 rounded-lg">Save</button>
|
||||
<input type="text" name="name" value="{{ user[1] }}" class="border p-2 rounded-lg bg-gray-300 text-gray-900 dark:bg-zinc-900 dark:text-gray-100 border-gray-600 w-52">
|
||||
<input type="email" name="email" value="{{ user[2] }}" class="border p-2 rounded-lg bg-gray-300 text-gray-900 dark:bg-zinc-900 dark:text-gray-100 border-gray-600 w-80">
|
||||
<input type="password" name="password" placeholder="New Password (leave blank to keep)" class="border p-2 rounded-lg bg-gray-300 text-gray-900 dark:bg-zinc-900 dark:text-gray-100 border-gray-600 w-80">
|
||||
<button type="submit" class="bg-gray-300 hover:bg-gray-400 dark:bg-zinc-700 dark:hover:bg-zinc-600 px-3 py-1 rounded-lg">Save</button>
|
||||
</form>
|
||||
<form action="/users" method="POST" onsubmit="return confirm('Are you sure you want to delete this user?');">
|
||||
<input type="hidden" name="action" value="delete">
|
||||
|
||||
Reference in New Issue
Block a user