Added DHCP Pool

This commit is contained in:
2025-05-28 23:35:42 +00:00
parent 6d4eab5aab
commit 34e1917abd
5 changed files with 231 additions and 3 deletions
+11
View File
@@ -87,6 +87,17 @@ def init_db():
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS DHCPPool (
id INTEGER PRIMARY KEY AUTOINCREMENT,
subnet_id INTEGER NOT NULL,
start_ip TEXT NOT NULL,
end_ip TEXT 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 (?, ?, ?)''',
+160 -2
View File
@@ -114,7 +114,28 @@ def register_routes(app):
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,))
device_ips = [{'device_ip_id': row[0], 'ip': row[1]} for row in cursor.fetchall()]
return render_with_user('device.html', device={'id': device[0], 'name': device[1], 'description': device[2]}, subnets=subnets, device_ips=device_ips)
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'],))
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'],))
dhcp_row = cursor.fetchone()
if dhcp_row:
start_ip, end_ip, excluded_ips = dhcp_row
excluded_list = [ip for ip in (excluded_ips or '').replace(' ', '').split(',') if ip]
in_range = False
filtered_ips = []
for ip_obj in ips:
ip = ip_obj['ip']
if ip == start_ip:
in_range = True
if ip in excluded_list or not (in_range and ip not in excluded_list):
filtered_ips.append(ip_obj)
if ip == end_ip:
in_range = False
ips = filtered_ips
available_ips_by_subnet[subnet['id']] = ips
return render_with_user('device.html', device={'id': device[0], 'name': device[1], 'description': device[2]}, subnets=subnets, device_ips=device_ips, available_ips_by_subnet=available_ips_by_subnet)
@app.route('/device/<int:device_id>/add_ip', methods=['POST'])
@login_required
@@ -124,6 +145,96 @@ def register_routes(app):
user_id = session.get('user_id')
with get_db_connection() 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,))
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,))
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,))
device_name = cursor.fetchone()[0]
@@ -331,7 +442,7 @@ def register_routes(app):
subnet_id = request.args.get('subnet_id')
with get_db_connection() as conn:
cursor = conn.cursor()
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 = ? 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}
@@ -405,6 +516,52 @@ def register_routes(app):
download_name=filename
)
@app.route('/subnet/<int:subnet_id>/dhcp', methods=['GET', 'POST'])
@login_required
def dhcp_pool(subnet_id):
error = None
with get_db_connection() as conn:
cursor = conn.cursor()
cursor.execute('SELECT id, name, cidr FROM Subnet WHERE id = ?', (subnet_id,))
subnet = cursor.fetchone()
dhcp_pool = None
cursor.execute('''SELECT start_ip, end_ip, excluded_ips FROM DHCPPool WHERE subnet_id = ?''', (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':
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,))
conn.commit()
dhcp_pool = None
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,))
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,))
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))
else:
cursor.execute('''INSERT INTO DHCPPool (subnet_id, start_ip, end_ip, excluded_ips) VALUES (?, ?, ?, ?)''', (subnet_id, start_ip, end_ip, 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))
if ip == end_ip:
break
conn.commit()
dhcp_pool = {'start_ip': start_ip, 'end_ip': end_ip, 'excluded_ips': excluded_ips}
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:
@@ -440,3 +597,4 @@ def register_routes(app):
app.add_url_rule('/rename_device', 'rename_device', rename_device, methods=['POST'])
app.add_url_rule('/update_device_description', 'update_device_description', update_device_description, methods=['POST'])
app.add_url_rule('/subnet/<int:subnet_id>/export_csv', 'export_subnet_csv', export_subnet_csv)
app.add_url_rule('/subnet/<int:subnet_id>/dhcp', 'dhcp_pool', dhcp_pool, methods=['GET', 'POST'])
+3
View File
@@ -48,6 +48,9 @@
</select>
<select name="ip_id" id="ip-select" class="border p-3 rounded-lg bg-gray-800 text-gray-100 border-gray-600 w-full" required>
<option value="" disabled selected>Select IP...</option>
{% for ip in available_ips_by_subnet.get(subnets[0].id, []) %}
<option value="{{ ip.id }}">{{ ip.ip }}</option>
{% endfor %}
</select>
<button type="submit" class="bg-gradient-to-r from-gray-500 to-gray-600 hover:from-gray-600 hover:to-gray-500 text-white px-4 py-2 rounded-lg w-full">Add IP</button>
</div>
+49
View File
@@ -0,0 +1,49 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Define DHCP Pool - {{ subnet.name }}</title>
<link rel="icon" type="image/png" href="/static/logo.png">
<link href="/static/css/output.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css" rel="stylesheet">
</head>
<body class="bg-gray-900 text-gray-100 min-h-screen flex flex-col">
{% include 'header.html' %}
<div class="flex-1 flex items-center justify-center mx-4">
<div class="container py-8 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-gradient-to-r from-gray-500 to-gray-600 hover:from-gray-600 hover:to-gray-500 text-white 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-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-900 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-900 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-900 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-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">Save DHCP Pool</button>
{% if dhcp_pool %}
<button type="submit" name="remove" value="1" 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">Remove DHCP Pool</button>
{% endif %}
</div>
</form>
{% if dhcp_pool %}
<div class="mt-8 bg-gray-700 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>
{% if dhcp_pool.excluded_ips %}
<div>Excluded: <span class="font-mono">{{ dhcp_pool.excluded_ips }}</span></div>
{% endif %}
</div>
{% endif %}
</div>
</div>
</body>
</html>
+8 -1
View File
@@ -20,6 +20,11 @@
<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-gradient-to-r from-gray-500 to-gray-700 hover:from-gray-600 hover:to-gray-800 text-white px-4 py-2 rounded-lg shadow items-center gap-2">
<i class="fas fa-network-wired"></i> Define DHCP Pool
</a>
</div>
<button id="toggle-desc" class="sm:hidden bg-gradient-to-r from-gray-500 to-gray-600 hover:from-gray-600 hover:to-gray-500 text-white px-4 py-2 rounded-lg mb-4 w-full">Show Descriptions</button>
<form action="" method="POST">
<table class="table-auto w-full mb-6">
@@ -35,7 +40,9 @@
<tr>
<td class="text-gray-300 font-bold text-center">{{ ip[1] }}</td>
<td class="text-white text-center">
{% if ip[2] and ip[3] %}
{% if ip[2] == 'DHCP' %}
<span class="font-semibold">DHCP</span>
{% elif ip[2] and ip[3] %}
<a href="/device/{{ ip[3] }}" class="hover:text-blue-300">{{ ip[2] }}</a>
{% elif ip[2] %}
{{ ip[2] }}