Switched to MySQL database

This commit is contained in:
2025-05-30 22:58:35 +00:00
parent 34e1917abd
commit a04612005f
10 changed files with 195 additions and 239 deletions
-6
View File
@@ -1,11 +1,5 @@
FROM mcr.microsoft.com/devcontainers/python:3.13
# Install system dependencies
RUN apt-get update && apt-get install -y \
sqlite3 \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Set the working directory
WORKDIR /workspace
+1 -1
View File
@@ -9,7 +9,7 @@
"extensions": ["ms-python.python"]
}
},
"postCreateCommand": "pip install Flask; curl -sLO https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-linux-x64; chmod +x tailwindcss-linux-x64; mv tailwindcss-linux-x64 tailwindcss",
"postCreateCommand": "pip install -r requirements.txt; curl -sLO https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-linux-x64; chmod +x tailwindcss-linux-x64; mv tailwindcss-linux-x64 tailwindcss",
"forwardPorts": [5000],
"remoteUser": "vscode"
}
+2 -2
View File
@@ -1,8 +1,8 @@
FROM python:3.13-slim
WORKDIR /app
COPY . /app
RUN pip install Flask
RUN apt-get update && apt-get install -y sqlite3 curl
RUN pip install -r requirements.txt
RUN apt-get update && apt-get install -y curl
RUN rm -rf /var/lib/apt/lists/*
RUN curl -sLO https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-linux-x64 \
&& chmod +x tailwindcss-linux-x64 \
+7 -1
View File
@@ -8,8 +8,14 @@ os.chdir(os.path.dirname(os.path.abspath(__file__)))
app = Flask(__name__)
app.config['SECRET_KEY'] = '41TbN7v5peFLZPrdwSCc64J3mjmiUk5fkVWsmb2m'
app.config['MYSQL_HOST'] = '10.10.2.27'
app.config['MYSQL_USER'] = 'ipam'
app.config['MYSQL_PASSWORD'] = 'WXPmo05sGCfjGe'
app.config['MYSQL_DATABASE'] = 'ipam'
register_routes(app)
if __name__ == '__main__':
init_db()
with app.app_context():
init_db(app)
app.run(host='0.0.0.0', port=5000)
+40 -41
View File
@@ -1,7 +1,8 @@
import os
import sqlite3
import hashlib
import base64
import mysql.connector
from flask import current_app
def hash_password(password, salt=None):
if salt is None:
@@ -17,31 +18,44 @@ def verify_password(password, hashed):
return False
return hash_password(password, salt) == hashed
def get_db_connection():
conn = sqlite3.connect('db/subnets.db')
conn.row_factory = sqlite3.Row
conn.execute('PRAGMA foreign_keys = ON;')
def get_db_connection(app=None):
if app is None:
app = current_app
conn = mysql.connector.connect(
host=app.config['MYSQL_HOST'],
user=app.config['MYSQL_USER'],
password=app.config['MYSQL_PASSWORD'],
database=app.config['MYSQL_DATABASE'],
autocommit=True
)
return conn
def init_db():
os.makedirs('db', exist_ok=True)
conn = sqlite3.connect('db/subnets.db')
def init_db(app=None):
if app is None:
app = current_app
conn = get_db_connection(app)
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS User (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE,
password TEXT NOT NULL
id INTEGER PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS Subnet (
id INTEGER PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(255) NOT NULL,
cidr VARCHAR(255) NOT NULL,
site VARCHAR(255)
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS AuditLog (
id INTEGER PRIMARY KEY AUTOINCREMENT,
id INTEGER PRIMARY KEY AUTO_INCREMENT,
user_id INTEGER,
action TEXT NOT NULL,
action VARCHAR(255) NOT NULL,
details TEXT,
subnet_id INTEGER,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
@@ -49,59 +63,44 @@ def init_db():
FOREIGN KEY (subnet_id) REFERENCES Subnet(id)
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS Subnet (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
cidr TEXT NOT NULL,
site TEXT
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS IPAddress (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ip TEXT NOT NULL,
hostname TEXT,
id INTEGER PRIMARY KEY AUTO_INCREMENT,
ip VARCHAR(255) NOT NULL,
hostname VARCHAR(255),
subnet_id INTEGER NOT NULL,
FOREIGN KEY (subnet_id) REFERENCES Subnet (id)
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS Device (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
id INTEGER PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(255) NOT NULL,
description TEXT
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS DeviceIPAddress (
id INTEGER PRIMARY KEY AUTOINCREMENT,
id INTEGER PRIMARY KEY AUTO_INCREMENT,
device_id INTEGER NOT NULL,
ip_id INTEGER NOT NULL,
FOREIGN KEY (device_id) REFERENCES Device (id),
FOREIGN KEY (ip_id) REFERENCES IPAddress (id)
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS DHCPPool (
id INTEGER PRIMARY KEY AUTOINCREMENT,
id INTEGER PRIMARY KEY AUTO_INCREMENT,
subnet_id INTEGER NOT NULL,
start_ip TEXT NOT NULL,
end_ip TEXT NOT NULL,
start_ip VARCHAR(255) NOT NULL,
end_ip VARCHAR(255) NOT NULL,
excluded_ips TEXT,
FOREIGN KEY (subnet_id) REFERENCES Subnet(id) ON DELETE CASCADE
)
''')
cursor.execute('SELECT COUNT(*) FROM User')
if cursor.fetchone()[0] == 0:
cursor.execute('''INSERT INTO User (name, email, password) VALUES (?, ?, ?)''',
cursor.execute('''INSERT INTO User (name, email, password) VALUES (%s, %s, %s)''',
('Jamie Banks', 'jamie@jdbnet.co.uk', hash_password('Drippy-Cavity-Jawline')))
conn.commit()
conn.close()
-7
View File
@@ -17,9 +17,6 @@ spec:
- name: ipam
image: docker.jdbnet.co.uk/internal/ipam:latest
imagePullPolicy: Always
volumeMounts:
- mountPath: /app/db
name: ipam
ports:
- containerPort: 5000
name: "ipam"
@@ -50,10 +47,6 @@ spec:
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1
volumes:
- name: ipam
persistentVolumeClaim:
claimName: ipam
---
apiVersion: v1
kind: Service
+2
View File
@@ -0,0 +1,2 @@
Flask
mysql-connector-python
+143 -172
View File
@@ -1,6 +1,5 @@
from flask import render_template, request, redirect, url_for, send_from_directory, send_file, session
from db import init_db, hash_password, get_db_connection, verify_password
import sqlite3
from ipaddress import ip_network
from functools import wraps
import os
@@ -18,13 +17,17 @@ def login_required(f):
return decorated_function
def add_audit_log(user_id, action, details=None, subnet_id=None, conn=None):
import datetime
close_conn = False
if conn is None:
conn = get_db_connection()
from flask import current_app
conn = get_db_connection(current_app)
close_conn = True
cursor = conn.cursor()
cursor.execute('''INSERT INTO AuditLog (user_id, action, details, subnet_id) VALUES (?, ?, ?, ?)''',
(user_id, action, details, subnet_id))
# Always use UTC for timestamp
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)''',
(user_id, action, details, subnet_id, utc_now))
if close_conn:
conn.commit()
conn.close()
@@ -36,9 +39,10 @@ def register_routes(app):
if request.method == 'POST':
email = request.form['email']
password = request.form['password']
with get_db_connection() as conn:
from flask import current_app
with get_db_connection(current_app) as conn:
cursor = conn.cursor()
cursor.execute('SELECT id, password FROM User WHERE email = ?', (email,))
cursor.execute('SELECT id, password FROM User WHERE email = %s', (email,))
user = cursor.fetchone()
if user and verify_password(password, user[1]):
session['logged_in'] = True
@@ -56,7 +60,8 @@ def register_routes(app):
@app.route('/')
@login_required
def index():
with get_db_connection() as conn:
from flask import current_app
with get_db_connection(current_app) as conn:
cursor = conn.cursor()
cursor.execute('SELECT id, name, cidr, site FROM Subnet')
subnets = cursor.fetchall()
@@ -71,7 +76,8 @@ def register_routes(app):
@app.route('/devices')
@login_required
def devices():
with get_db_connection() as conn:
from flask import current_app
with get_db_connection(current_app) as conn:
cursor = conn.cursor()
cursor.execute('SELECT id, name FROM Device')
devices = cursor.fetchall()
@@ -83,7 +89,7 @@ def register_routes(app):
device_ips.setdefault(row[0], []).append((row[1], row[2]))
sites_devices = {}
for device in devices:
cursor.execute('''SELECT Subnet.site FROM DeviceIPAddress JOIN IPAddress ON DeviceIPAddress.ip_id = IPAddress.id JOIN Subnet ON IPAddress.subnet_id = Subnet.id WHERE DeviceIPAddress.device_id = ? LIMIT 1''', (device[0],))
cursor.execute('''SELECT Subnet.site FROM DeviceIPAddress JOIN IPAddress ON DeviceIPAddress.ip_id = IPAddress.id JOIN Subnet ON IPAddress.subnet_id = Subnet.id WHERE DeviceIPAddress.device_id = %s LIMIT 1''', (device[0],))
site = cursor.fetchone()
site = site[0] if site else 'Unassigned'
if site not in sites_devices:
@@ -96,9 +102,10 @@ def register_routes(app):
def add_device():
if request.method == 'POST':
name = request.form['device_name']
with get_db_connection() as conn:
from flask import current_app
with get_db_connection(current_app) as conn:
cursor = conn.cursor()
cursor.execute('INSERT INTO Device (name) VALUES (?)', (name,))
cursor.execute('INSERT INTO Device (name) VALUES (%s)', (name,))
conn.commit()
return redirect(url_for('devices'))
return render_with_user('add_device.html')
@@ -106,19 +113,20 @@ def register_routes(app):
@app.route('/device/<int:device_id>')
@login_required
def device(device_id):
with get_db_connection() as conn:
from flask import current_app
with get_db_connection(current_app) as conn:
cursor = conn.cursor()
cursor.execute('SELECT id, name, description FROM Device WHERE id = ?', (device_id,))
cursor.execute('SELECT id, name, description FROM Device WHERE id = %s', (device_id,))
device = cursor.fetchone()
cursor.execute('SELECT id, name, cidr, site FROM Subnet')
subnets = [dict(id=row[0], name=row[1], cidr=row[2], site=row[3]) for row in cursor.fetchall()]
cursor.execute('''SELECT DeviceIPAddress.id as device_ip_id, IPAddress.ip FROM DeviceIPAddress JOIN IPAddress ON DeviceIPAddress.ip_id = IPAddress.id WHERE DeviceIPAddress.device_id = ?''', (device_id,))
cursor.execute('''SELECT DeviceIPAddress.id as device_ip_id, IPAddress.ip FROM DeviceIPAddress JOIN IPAddress ON DeviceIPAddress.ip_id = IPAddress.id WHERE DeviceIPAddress.device_id = %s''', (device_id,))
device_ips = [{'device_ip_id': row[0], 'ip': row[1]} for row in cursor.fetchall()]
available_ips_by_subnet = {}
for subnet in subnets:
cursor.execute('SELECT id, ip FROM IPAddress WHERE subnet_id = ? AND id NOT IN (SELECT ip_id FROM DeviceIPAddress)', (subnet['id'],))
cursor.execute('SELECT id, ip FROM IPAddress WHERE subnet_id = %s AND id NOT IN (SELECT ip_id FROM DeviceIPAddress)', (subnet['id'],))
ips = [{'id': row[0], 'ip': row[1]} for row in cursor.fetchall()]
cursor.execute('SELECT start_ip, end_ip, excluded_ips FROM DHCPPool WHERE subnet_id = ?', (subnet['id'],))
cursor.execute('SELECT start_ip, end_ip, excluded_ips FROM DHCPPool WHERE subnet_id = %s', (subnet['id'],))
dhcp_row = cursor.fetchone()
if dhcp_row:
start_ip, end_ip, excluded_ips = dhcp_row
@@ -143,105 +151,51 @@ def register_routes(app):
subnet_id = request.form['subnet_id']
ip_id = request.form['ip_id']
user_id = session.get('user_id')
with get_db_connection() as conn:
from flask import current_app
with get_db_connection(current_app) as conn:
cursor = conn.cursor()
print(f"Submitted ip_id: {ip_id}")
cursor.execute('SELECT id, ip FROM IPAddress WHERE subnet_id = ?', (subnet_id,))
all_ip_rows = cursor.fetchall()
print(f"All IPs in subnet: {[row for row in all_ip_rows]}")
cursor.execute('SELECT ip_id FROM DeviceIPAddress')
assigned_ip_ids = [row[0] for row in cursor.fetchall()]
print(f"Assigned IP IDs: {assigned_ip_ids}")
cursor.execute('SELECT start_ip, end_ip, excluded_ips FROM DHCPPool WHERE subnet_id = ?', (subnet_id,))
dhcp_row = cursor.fetchone()
if dhcp_row:
start_ip, end_ip, excluded_ips = dhcp_row
excluded_list = [x for x in (excluded_ips or '').replace(' ', '').split(',') if x]
print(f"DHCP Excluded IPs: {excluded_list}")
cursor.execute('SELECT ip, hostname FROM IPAddress WHERE id = ?', (ip_id,))
ip_row = cursor.fetchone()
if not ip_row:
raise Exception("The selected IP address is no longer available. Please refresh and try again.")
ip = ip_row[0]
hostname = ip_row[1]
cursor.execute('SELECT start_ip, end_ip, excluded_ips FROM DHCPPool WHERE subnet_id = ?', (subnet_id,))
dhcp_row = cursor.fetchone()
if dhcp_row:
start_ip, end_ip, excluded_ips = dhcp_row
excluded_list = [x for x in (excluded_ips or '').replace(' ', '').split(',') if x]
print(f"DHCP Excluded IPs: {excluded_list}")
if ip not in excluded_list:
cursor.execute('SELECT ip FROM IPAddress WHERE subnet_id = ?', (subnet_id,))
all_ips = [row[0] for row in cursor.fetchall()]
in_range = False
reserved_for_dhcp = False
for candidate_ip in all_ips:
if candidate_ip == start_ip:
in_range = True
if in_range and candidate_ip == ip:
reserved_for_dhcp = True
break
if candidate_ip == end_ip:
in_range = False
if reserved_for_dhcp:
raise Exception("This IP is reserved for DHCP and cannot be assigned to a device.")
cursor.execute('SELECT id, ip FROM IPAddress WHERE subnet_id = ?', (subnet_id,))
cursor.execute('SELECT id, ip FROM IPAddress WHERE subnet_id = %s', (subnet_id,))
all_ip_rows = cursor.fetchall()
cursor.execute('SELECT ip_id FROM DeviceIPAddress')
assigned_ip_ids = [row[0] for row in cursor.fetchall()]
cursor.execute('SELECT start_ip, end_ip, excluded_ips FROM DHCPPool WHERE subnet_id = ?', (subnet_id,))
cursor.execute('SELECT start_ip, end_ip, excluded_ips FROM DHCPPool WHERE subnet_id = %s', (subnet_id,))
dhcp_row = cursor.fetchone()
if dhcp_row:
start_ip, end_ip, excluded_ips = dhcp_row
excluded_list = [x for x in (excluded_ips or '').replace(' ', '').split(',') if x]
print(f"DHCP Excluded IPs: {excluded_list}")
cursor.execute('SELECT ip, hostname FROM IPAddress WHERE id = ?', (ip_id,))
ip_row = cursor.fetchone()
if not ip_row:
raise Exception("The selected IP address is no longer available. Please refresh and try again.")
ip = ip_row[0]
hostname = ip_row[1]
cursor.execute('SELECT start_ip, end_ip, excluded_ips FROM DHCPPool WHERE subnet_id = ?', (subnet_id,))
dhcp_row = cursor.fetchone()
if dhcp_row:
start_ip, end_ip, excluded_ips = dhcp_row
excluded_list = [x for x in (excluded_ips or '').replace(' ', '').split(',') if x]
print(f"DHCP Excluded IPs: {excluded_list}")
if ip not in excluded_list:
cursor.execute('SELECT ip FROM IPAddress WHERE subnet_id = ?', (subnet_id,))
all_ips = [row[0] for row in cursor.fetchall()]
in_range = False
reserved_for_dhcp = False
for candidate_ip in all_ips:
if candidate_ip == start_ip:
in_range = True
if in_range and candidate_ip == ip:
reserved_for_dhcp = True
break
if candidate_ip == end_ip:
in_range = False
if reserved_for_dhcp:
raise Exception("This IP is reserved for DHCP and cannot be assigned to a device.")
print(f"Submitted ip_id: {ip_id}")
cursor.execute('SELECT id, ip FROM IPAddress WHERE subnet_id = ?', (subnet_id,))
all_ip_rows = cursor.fetchall()
print(f"All IPs in subnet: {[row for row in all_ip_rows]}")
cursor.execute('SELECT ip_id FROM DeviceIPAddress')
assigned_ip_ids = [row[0] for row in cursor.fetchall()]
print(f"Assigned IP IDs: {assigned_ip_ids}")
cursor.execute('SELECT start_ip, end_ip, excluded_ips FROM DHCPPool WHERE subnet_id = ?', (subnet_id,))
dhcp_row = cursor.fetchone()
if dhcp_row:
start_ip, end_ip, excluded_ips = dhcp_row
excluded_list = [x for x in (excluded_ips or '').replace(' ', '').split(',') if x]
print(f"DHCP Excluded IPs: {excluded_list}")
cursor.execute('INSERT INTO DeviceIPAddress (device_id, ip_id) VALUES (?, ?)', (device_id, ip_id))
cursor.execute('SELECT name FROM Device WHERE id = ?', (device_id,))
cursor.execute('SELECT ip, hostname FROM IPAddress WHERE id = %s', (ip_id,))
ip_row = cursor.fetchone()
if not ip_row:
raise Exception("The selected IP address is no longer available. Please refresh and try again.")
ip = ip_row[0]
hostname = ip_row[1]
cursor.execute('SELECT start_ip, end_ip, excluded_ips FROM DHCPPool WHERE subnet_id = %s', (subnet_id,))
dhcp_row = cursor.fetchone()
if dhcp_row:
start_ip, end_ip, excluded_ips = dhcp_row
excluded_list = [x for x in (excluded_ips or '').replace(' ', '').split(',') if x]
if ip not in excluded_list:
cursor.execute('SELECT ip FROM IPAddress WHERE subnet_id = %s', (subnet_id,))
all_ips = [row[0] for row in cursor.fetchall()]
in_range = False
reserved_for_dhcp = False
for candidate_ip in all_ips:
if candidate_ip == start_ip:
in_range = True
if in_range and candidate_ip == ip:
reserved_for_dhcp = True
break
if candidate_ip == end_ip:
in_range = False
if reserved_for_dhcp:
raise Exception("This IP is reserved for DHCP and cannot be assigned to a device.")
cursor.execute('INSERT INTO DeviceIPAddress (device_id, ip_id) VALUES (%s, %s)', (device_id, ip_id))
cursor.execute('SELECT name FROM Device WHERE id = %s', (device_id,))
device_name = cursor.fetchone()[0]
cursor.execute('UPDATE IPAddress SET hostname = ? WHERE id = ?', (device_name, ip_id))
cursor.execute('SELECT ip, subnet_id FROM IPAddress WHERE id = ?', (ip_id,))
cursor.execute('UPDATE IPAddress SET hostname = %s WHERE id = %s', (device_name, ip_id))
cursor.execute('SELECT ip, subnet_id FROM IPAddress WHERE id = %s', (ip_id,))
ip, subnet_id_val = cursor.fetchone()
cursor.execute('SELECT name, cidr FROM Subnet WHERE id = ?', (subnet_id_val,))
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)
@@ -253,22 +207,23 @@ def register_routes(app):
def device_delete_ip(device_id):
device_ip_id = request.form['device_ip_id']
user_id = session.get('user_id')
with get_db_connection() as conn:
from flask import current_app
with get_db_connection(current_app) as conn:
cursor = conn.cursor()
cursor.execute('SELECT ip_id FROM DeviceIPAddress WHERE id = ?', (device_ip_id,))
cursor.execute('SELECT ip_id FROM DeviceIPAddress WHERE id = %s', (device_ip_id,))
ip_id = cursor.fetchone()[0]
cursor.execute('SELECT ip, subnet_id FROM IPAddress WHERE id = ?', (ip_id,))
cursor.execute('SELECT ip, subnet_id FROM IPAddress WHERE id = %s', (ip_id,))
ip, subnet_id_val = cursor.fetchone()
cursor.execute('SELECT name, cidr FROM Subnet WHERE id = ?', (subnet_id_val,))
cursor.execute('SELECT name, cidr FROM Subnet WHERE id = %s', (subnet_id_val,))
subnet_name, subnet_cidr = cursor.fetchone()
cursor.execute('SELECT device_id FROM DeviceIPAddress WHERE id = ?', (device_ip_id,))
cursor.execute('SELECT device_id FROM DeviceIPAddress WHERE id = %s', (device_ip_id,))
device_id_val = cursor.fetchone()[0]
cursor.execute('SELECT name FROM Device WHERE id = ?', (device_id_val,))
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)
cursor.execute('DELETE FROM DeviceIPAddress WHERE id = ?', (device_ip_id,))
cursor.execute('UPDATE IPAddress SET hostname = NULL WHERE id = ?', (ip_id,))
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()
return redirect(url_for('device', device_id=device_id))
@@ -277,24 +232,26 @@ def register_routes(app):
def delete_device():
device_id = request.form['device_id']
user_id = session.get('user_id')
with get_db_connection() as conn:
from flask import current_app
with get_db_connection(current_app) as conn:
cursor = conn.cursor()
cursor.execute('SELECT name FROM Device WHERE id = ?', (device_id,))
cursor.execute('SELECT name FROM Device WHERE id = %s', (device_id,))
device_name = cursor.fetchone()[0]
add_audit_log(user_id, 'delete_device', f"Deleted device {device_name}", conn=conn)
cursor.execute('DELETE FROM DeviceIPAddress WHERE device_id = ?', (device_id,))
cursor.execute('DELETE FROM Device WHERE id = ?', (device_id,))
cursor.execute('DELETE FROM DeviceIPAddress WHERE device_id = %s', (device_id,))
cursor.execute('DELETE FROM Device WHERE id = %s', (device_id,))
conn.commit()
return redirect(url_for('devices'))
@app.route('/subnet/<int:subnet_id>')
@login_required
def subnet(subnet_id):
with get_db_connection() as conn:
from flask import current_app
with get_db_connection(current_app) as conn:
cursor = conn.cursor()
cursor.execute('SELECT id, name, cidr FROM Subnet WHERE id = ?', (subnet_id,))
cursor.execute('SELECT id, name, cidr FROM Subnet WHERE id = %s', (subnet_id,))
subnet = cursor.fetchone()
cursor.execute('SELECT * FROM IPAddress WHERE subnet_id = ?', (subnet_id,))
cursor.execute('SELECT * FROM IPAddress WHERE subnet_id = %s', (subnet_id,))
ip_addresses = cursor.fetchall()
cursor.execute('SELECT id, name, description FROM Device')
devices = cursor.fetchall()
@@ -324,12 +281,13 @@ def register_routes(app):
return render_with_user('admin.html', subnets=[], error='Subnet must be /24 or smaller (e.g., /24, /25, ... /32)')
except Exception as e:
return render_with_user('admin.html', subnets=[], error='Invalid CIDR format.')
with get_db_connection() as conn:
from flask import current_app
with get_db_connection(current_app) as conn:
cursor = conn.cursor()
cursor.execute('INSERT INTO Subnet (name, cidr, site) VALUES (?, ?, ?)', (name, cidr, site))
cursor.execute('INSERT INTO Subnet (name, cidr, site) VALUES (%s, %s, %s)', (name, cidr, site))
subnet_id = cursor.lastrowid
ip_rows = [(str(ip), subnet_id) for ip in network.hosts()]
cursor.executemany('INSERT INTO IPAddress (ip, subnet_id) VALUES (?, ?)', ip_rows)
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)
conn.commit()
return redirect(url_for('admin'))
@@ -339,39 +297,37 @@ def register_routes(app):
def delete_subnet():
subnet_id = request.form['subnet_id']
user_id = session.get('user_id')
with get_db_connection() as conn:
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 = ?', (subnet_id,))
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)
cursor.execute('SELECT id FROM IPAddress WHERE subnet_id = ?', (subnet_id,))
cursor.execute('SELECT id FROM IPAddress WHERE subnet_id = %s', (subnet_id,))
ip_ids = [row[0] for row in cursor.fetchall()]
if ip_ids:
cursor.executemany('DELETE FROM DeviceIPAddress WHERE ip_id = ?', [(ip_id,) for ip_id in ip_ids])
cursor.executemany('UPDATE AuditLog SET subnet_id=NULL WHERE subnet_id = ?', [(subnet_id,) for _ in ip_ids])
cursor.execute('DELETE FROM IPAddress WHERE subnet_id = ?', (subnet_id,))
cursor.execute('DELETE FROM Subnet WHERE id = ?', (subnet_id,))
cursor.executemany('DELETE FROM DeviceIPAddress WHERE ip_id = %s', [(ip_id,) for ip_id in ip_ids])
cursor.executemany('UPDATE AuditLog SET subnet_id=NULL WHERE subnet_id = %s', [(subnet_id,) for _ in ip_ids])
cursor.execute('DELETE FROM IPAddress WHERE subnet_id = %s', (subnet_id,))
cursor.execute('DELETE FROM Subnet WHERE id = %s', (subnet_id,))
conn.commit()
return redirect(url_for('admin'))
@app.route('/admin', methods=['GET', 'POST'])
@login_required
def admin():
with get_db_connection() as conn:
from flask import current_app
with get_db_connection(current_app) as conn:
cursor = conn.cursor()
cursor.execute('SELECT id, name, cidr FROM Subnet')
subnets = [dict(id=row[0], name=row[1], cidr=row[2]) for row in cursor.fetchall()]
return render_with_user('admin.html', subnets=subnets)
@app.route('/download_db')
@login_required
def download_db():
return send_file('db/subnets.db', as_attachment=True)
@app.route('/users', methods=['GET', 'POST'])
@login_required
def users():
with get_db_connection() as conn:
from flask import current_app
with get_db_connection(current_app) as conn:
cursor = conn.cursor()
if request.method == 'POST':
action = request.form['action']
@@ -379,7 +335,7 @@ def register_routes(app):
name = request.form['name']
email = request.form['email']
password = hash_password(request.form['password'])
cursor.execute('INSERT INTO User (name, email, password) VALUES (?, ?, ?)', (name, email, password))
cursor.execute('INSERT INTO User (name, email, password) VALUES (%s, %s, %s)', (name, email, password))
elif action == 'edit':
user_id = request.form['user_id']
name = request.form['name']
@@ -387,14 +343,14 @@ def register_routes(app):
password = request.form['password']
if password:
password = hash_password(password)
cursor.execute('UPDATE User SET name=?, email=?, password=? WHERE id=?', (name, email, password, user_id))
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=?, email=? WHERE id=?', (name, email, user_id))
cursor.execute('UPDATE User SET name=%s, email=%s WHERE id=%s', (name, email, user_id))
elif action == 'delete':
user_id = request.form['user_id']
cursor.execute('UPDATE User SET name=? WHERE id=?', ('Deleted User', user_id))
cursor.execute('UPDATE AuditLog SET user_id=NULL WHERE user_id=?', (user_id,))
cursor.execute('DELETE FROM User WHERE id=?', (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,))
conn.commit()
cursor.execute('SELECT id, name, email FROM User')
users = cursor.fetchall()
@@ -403,7 +359,8 @@ def register_routes(app):
@app.route('/audit')
@login_required
def audit():
with get_db_connection() as conn:
from flask import current_app
with get_db_connection(current_app) as conn:
cursor = conn.cursor()
user_id = request.args.get('user_id')
subnet_id = request.args.get('subnet_id')
@@ -412,16 +369,16 @@ def register_routes(app):
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 = ?'
query += ' AND AuditLog.user_id = %s'
params.append(user_id)
if subnet_id:
query += ' AND AuditLog.subnet_id = ?'
query += ' AND AuditLog.subnet_id = %s'
params.append(subnet_id)
if action:
query += ' AND AuditLog.action = ?'
query += ' AND AuditLog.action = %s'
params.append(action)
if device_name:
query += ' AND AuditLog.details LIKE ?'
query += ' AND AuditLog.details LIKE %s'
params.append(f'%{device_name}%')
query += ' ORDER BY AuditLog.timestamp DESC'
cursor.execute(query, params)
@@ -440,9 +397,10 @@ def register_routes(app):
@login_required
def get_available_ips():
subnet_id = request.args.get('subnet_id')
with get_db_connection() as conn:
from flask import current_app
with get_db_connection(current_app) as conn:
cursor = conn.cursor()
cursor.execute('''SELECT id, ip FROM IPAddress WHERE subnet_id = ? AND id NOT IN (SELECT ip_id FROM DeviceIPAddress) AND (hostname IS NULL OR hostname != 'DHCP')''', (subnet_id,))
cursor.execute('''SELECT id, ip FROM IPAddress WHERE subnet_id = %s AND id NOT IN (SELECT ip_id FROM DeviceIPAddress) AND (hostname IS NULL OR hostname != 'DHCP')''', (subnet_id,))
available_ips = [{'id': row[0], 'ip': row[1]} for row in cursor.fetchall()]
return {'available_ips': available_ips}
@@ -452,12 +410,13 @@ def register_routes(app):
device_id = request.form['device_id']
new_name = request.form['new_name']
user_id = session.get('user_id')
with get_db_connection() as conn:
from flask import current_app
with get_db_connection(current_app) as conn:
cursor = conn.cursor()
cursor.execute('SELECT name FROM Device WHERE id = ?', (device_id,))
cursor.execute('SELECT name FROM Device WHERE id = %s', (device_id,))
old_name = cursor.fetchone()[0]
cursor.execute('UPDATE Device SET name = ? WHERE id = ?', (new_name, device_id))
cursor.execute('UPDATE IPAddress SET hostname = ? WHERE hostname = ?', (new_name, old_name))
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)
return redirect(url_for('device', device_id=device_id))
@@ -467,22 +426,24 @@ def register_routes(app):
def update_device_description():
device_id = request.form['device_id']
description = request.form['description']
with get_db_connection() as conn:
from flask import current_app
with get_db_connection(current_app) as conn:
cursor = conn.cursor()
cursor.execute('UPDATE Device SET description = ? WHERE id = ?', (description, device_id))
cursor.execute('UPDATE Device SET description = %s WHERE id = %s', (description, device_id))
conn.commit()
return redirect(url_for('device', device_id=device_id))
@app.route('/subnet/<int:subnet_id>/export_csv')
@login_required
def export_subnet_csv(subnet_id):
with get_db_connection() as conn:
from flask import current_app
with get_db_connection(current_app) as conn:
cursor = conn.cursor()
cursor.execute('SELECT id, name, cidr FROM Subnet WHERE id = ?', (subnet_id,))
cursor.execute('SELECT id, name, cidr FROM Subnet WHERE id = %s', (subnet_id,))
subnet = cursor.fetchone()
if not subnet:
return 'Subnet not found', 404
cursor.execute('SELECT * FROM IPAddress WHERE subnet_id = ?', (subnet_id,))
cursor.execute('SELECT * FROM IPAddress WHERE subnet_id = %s', (subnet_id,))
ip_addresses = cursor.fetchall()
cursor.execute('SELECT id, name, description FROM Device')
devices = cursor.fetchall()
@@ -520,55 +481,66 @@ def register_routes(app):
@login_required
def dhcp_pool(subnet_id):
error = None
with get_db_connection() as conn:
from flask import current_app
with get_db_connection(current_app) as conn:
cursor = conn.cursor()
cursor.execute('SELECT id, name, cidr FROM Subnet WHERE id = ?', (subnet_id,))
cursor.execute('SELECT id, name, cidr FROM Subnet WHERE id = %s', (subnet_id,))
subnet = cursor.fetchone()
dhcp_pool = None
cursor.execute('''SELECT start_ip, end_ip, excluded_ips FROM DHCPPool WHERE subnet_id = ?''', (subnet_id,))
cursor.execute('''SELECT start_ip, end_ip, excluded_ips FROM DHCPPool WHERE subnet_id = %s''', (subnet_id,))
row = cursor.fetchone()
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')
if 'remove' in request.form:
cursor.execute('DELETE FROM DHCPPool WHERE subnet_id = ?', (subnet_id,))
cursor.execute('UPDATE IPAddress SET hostname=NULL WHERE subnet_id=? AND hostname="DHCP"', (subnet_id,))
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
# 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)
else:
start_ip = request.form['start_ip']
end_ip = request.form['end_ip']
excluded_ips = request.form.get('excluded_ips', '').replace(' ', '')
excluded_list = [ip for ip in excluded_ips.split(',') if ip]
cursor.execute('SELECT ip FROM IPAddress WHERE subnet_id = ?', (subnet_id,))
cursor.execute('SELECT ip FROM IPAddress WHERE subnet_id = %s', (subnet_id,))
all_ips = [row[0] for row in cursor.fetchall()]
if start_ip not in all_ips or end_ip not in all_ips:
error = 'Start and End IP must be within the subnet.'
else:
cursor.execute('UPDATE IPAddress SET hostname=NULL WHERE subnet_id=? AND hostname="DHCP"', (subnet_id,))
cursor.execute('UPDATE IPAddress SET hostname=NULL WHERE subnet_id=%s AND hostname="DHCP"', (subnet_id,))
if dhcp_pool:
cursor.execute('''UPDATE DHCPPool SET start_ip = ?, end_ip = ?, excluded_ips = ? WHERE subnet_id = ?''', (start_ip, end_ip, excluded_ips, subnet_id))
cursor.execute('''UPDATE DHCPPool SET start_ip = %s, end_ip = %s, excluded_ips = %s WHERE subnet_id = %s''', (start_ip, end_ip, excluded_ips, subnet_id))
action = 'dhcp_pool_update'
details = f"Updated DHCP pool for subnet {subnet[1]} ({subnet[2]}): {start_ip} - {end_ip}, excluded: {excluded_ips}"
else:
cursor.execute('''INSERT INTO DHCPPool (subnet_id, start_ip, end_ip, excluded_ips) VALUES (?, ?, ?, ?)''', (subnet_id, start_ip, end_ip, excluded_ips))
cursor.execute('''INSERT INTO DHCPPool (subnet_id, start_ip, end_ip, excluded_ips) VALUES (%s, %s, %s, %s)''', (subnet_id, start_ip, end_ip, excluded_ips))
action = 'dhcp_pool_create'
details = f"Created DHCP pool for subnet {subnet[1]} ({subnet[2]}): {start_ip} - {end_ip}, excluded: {excluded_ips}"
in_range = False
for ip in all_ips:
if ip == start_ip:
in_range = True
if in_range and ip not in excluded_list:
cursor.execute('UPDATE IPAddress SET hostname="DHCP" WHERE subnet_id=? AND ip=?', (subnet_id, ip))
cursor.execute('UPDATE IPAddress SET hostname="DHCP" WHERE subnet_id=%s AND ip=%s', (subnet_id, ip))
if ip == end_ip:
break
conn.commit()
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)
return render_with_user('dhcp.html', subnet={'id': subnet[0], 'name': subnet[1]}, dhcp_pool=dhcp_pool, error=error)
def get_current_user_name():
user_id = session.get('user_id')
if not user_id:
return ''
with get_db_connection() as conn:
from flask import current_app
with get_db_connection(current_app) as conn:
cursor = conn.cursor()
cursor.execute('SELECT name FROM User WHERE id = ?', (user_id,))
cursor.execute('SELECT name FROM User WHERE id = %s', (user_id,))
row = cursor.fetchone()
return row[0] if row else ''
@@ -590,7 +562,6 @@ def register_routes(app):
app.add_url_rule('/add_subnet', 'add_subnet', add_subnet, methods=['POST'])
app.add_url_rule('/delete_subnet', 'delete_subnet', delete_subnet, methods=['POST'])
app.add_url_rule('/admin', 'admin', admin, methods=['GET', 'POST'])
app.add_url_rule('/download_db', 'download_db', download_db)
app.add_url_rule('/users', 'users', users, methods=['GET', 'POST'])
app.add_url_rule('/audit', 'audit', audit)
app.add_url_rule('/get_available_ips', 'get_available_ips', get_available_ips)
Regular → Executable
View File
-9
View File
@@ -40,15 +40,6 @@
<i class="fas fa-trash fa-lg"></i>
</button>
</form>
<hr class="border-t-2 border-gray-600 rounded-lg mb-4">
<h1 class="text-2xl font-bold mb-6 text-center">Database Management</h1>
<div class="mb-6 flex justify-center">
<a href="/download_db" 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">Download Database</a>
</div>
<form action="/admin" method="POST" enctype="multipart/form-data" class="flex flex-col space-y-4">
<input type="file" name="file" class="border p-3 rounded-lg bg-gray-800 text-gray-100 border-gray-600" required>
<button type="submit" 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">Upload Database</button>
</form>
</div>
</div>
<script src="/static/js/add_subnet.js"></script>