refactor: 🎨 strip legacy features

This commit is contained in:
2026-05-23 16:40:25 +00:00
parent d334dae3d6
commit f01a81e558
15 changed files with 59 additions and 503 deletions
+42 -301
View File
@@ -16,7 +16,6 @@ from ipaddress import ip_network, ip_address, IPv4Address, IPv6Address
import pyotp
import qrcode
import requests
import mysql.connector
from dotenv import load_dotenv
from flask import (
@@ -158,23 +157,6 @@ def permission_required(permission_name):
return decorated_function
return decorator
def is_feature_enabled(feature_key, conn=None):
"""Check if a feature flag is enabled"""
close_conn = False
if conn is None:
from flask import current_app
conn = get_db_connection(current_app)
close_conn = True
try:
cursor = conn.cursor()
cursor.execute('SELECT enabled FROM FeatureFlags WHERE feature_key = %s', (feature_key,))
result = cursor.fetchone()
# Default to True if feature flag doesn't exist (backward compatibility)
return result[0] if result else True
finally:
if close_conn:
conn.close()
def get_user_from_api_key(api_key):
"""Get user from API key"""
@@ -546,7 +528,7 @@ def get_custom_fields_for_entity(entity_type, entity_id, conn=None):
# Get field definitions for this entity type
cursor.execute('''
SELECT id, entity_type, name, field_key, field_type, required,
default_value, help_text, display_order, validation_rules, searchable
default_value, help_text, display_order, validation_rules
FROM CustomFieldDefinition
WHERE entity_type = %s
ORDER BY display_order, name
@@ -1313,13 +1295,12 @@ def devices():
tag_filter = request.args.get('tag')
with get_db_connection(current_app) as conn:
# Check if device tags feature is enabled
tags_enabled = is_feature_enabled('device_tags', conn=conn)
tag_filter = request.args.get('tag')
cursor = conn.cursor()
# Base device query
if tag_filter and tags_enabled:
if tag_filter:
cursor.execute('''
SELECT DISTINCT d.id, d.name, dt.icon_class
FROM Device d
@@ -1341,23 +1322,21 @@ def devices():
for row in cursor.fetchall():
device_ips.setdefault(row[0], []).append((row[1], row[2]))
# Get tags for each device (only if tags feature is enabled)
# Get tags for each device
device_tags = {}
all_tag_names = []
if tags_enabled:
for device in devices:
cursor.execute('''
SELECT t.id, t.name, t.color
FROM DeviceTag dt
JOIN Tag t ON dt.tag_id = t.id
WHERE dt.device_id = %s
ORDER BY t.name
''', (device[0],))
device_tags[device[0]] = [{'id': row[0], 'name': row[1], 'color': row[2]} for row in cursor.fetchall()]
# Get all available tags for filtering
cursor.execute('SELECT DISTINCT name FROM Tag ORDER BY name')
all_tag_names = [row[0] for row in cursor.fetchall()]
for device in devices:
cursor.execute('''
SELECT t.id, t.name, t.color
FROM DeviceTag dt
JOIN Tag t ON dt.tag_id = t.id
WHERE dt.device_id = %s
ORDER BY t.name
''', (device[0],))
device_tags[device[0]] = [{'id': row[0], 'name': row[1], 'color': row[2]} for row in cursor.fetchall()]
cursor.execute('SELECT DISTINCT name FROM Tag ORDER BY name')
all_tag_names = [row[0] for row in cursor.fetchall()]
# Optimize: Get device sites in a single query instead of N+1
sites_devices = {}
@@ -1424,23 +1403,18 @@ def device(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()]
# Get device tags (only if tags feature is enabled)
tags_enabled = is_feature_enabled('device_tags', conn=conn)
device_tags = []
all_tags = []
if tags_enabled:
cursor.execute('''
SELECT t.id, t.name, t.color
FROM DeviceTag dt
JOIN Tag t ON dt.tag_id = t.id
WHERE dt.device_id = %s
ORDER BY t.name
''', (device_id,))
device_tags = [{'id': row[0], 'name': row[1], 'color': row[2]} for row in cursor.fetchall()]
# Get all available tags
cursor.execute('SELECT id, name, color FROM Tag ORDER BY name')
all_tags = [{'id': row[0], 'name': row[1], 'color': row[2]} for row in cursor.fetchall()]
# Get device tags
cursor.execute('''
SELECT t.id, t.name, t.color
FROM DeviceTag dt
JOIN Tag t ON dt.tag_id = t.id
WHERE dt.device_id = %s
ORDER BY t.name
''', (device_id,))
device_tags = [{'id': row[0], 'name': row[1], 'color': row[2]} for row in cursor.fetchall()]
cursor.execute('SELECT id, name, color FROM Tag ORDER BY name')
all_tags = [{'id': row[0], 'name': row[1], 'color': row[2]} for row in cursor.fetchall()]
available_ips_by_subnet = {}
for subnet in subnets:
cursor.execute('''
@@ -1548,8 +1522,6 @@ def device_delete_ip(device_id):
def device_assign_tag(device_id):
tag_id = request.form['tag_id']
with get_db_connection(current_app) as conn:
if not is_feature_enabled('device_tags', conn=conn):
abort(404)
try:
assign_tag_to_device(conn, device_id, tag_id, session['user_id'])
conn.commit()
@@ -1562,8 +1534,6 @@ def device_assign_tag(device_id):
def device_remove_tag(device_id):
tag_id = request.form['tag_id']
with get_db_connection(current_app) as conn:
if not is_feature_enabled('device_tags', conn=conn):
abort(404)
try:
remove_tag_from_device(conn, device_id, tag_id, session['user_id'])
conn.commit()
@@ -1614,15 +1584,11 @@ def subnet(subnet_id):
'vlan_description': subnet[4] if len(subnet) > 4 else None,
'vlan_notes': subnet[5] if len(subnet) > 5 else None
}
# Check if IP address notes feature is enabled
ip_notes_enabled = is_feature_enabled('ip_address_notes', conn=conn)
return render_with_user('subnet.html', subnet=subnet_dict,
ip_addresses=ip_addresses_with_device,
utilization=utilization_stats,
custom_fields=custom_fields,
can_edit_subnet=has_permission('edit_subnet'),
ip_notes_enabled=ip_notes_enabled)
can_edit_subnet=has_permission('edit_subnet'))
@app.route('/add_subnet', methods=['POST'])
@permission_required('add_subnet')
@@ -1749,50 +1715,15 @@ def admin():
'total': util['total']
}
})
# Get feature flags (inside the connection context)
cursor.execute('SELECT feature_key, enabled, description FROM FeatureFlags ORDER BY feature_key')
feature_flags = []
for row in cursor.fetchall():
feature_flags.append({
'key': row[0],
'enabled': bool(row[1]),
'description': row[2]
})
result_data = {
'subnets': subnets,
'can_add_subnet': has_permission('add_subnet'),
'can_edit_subnet': has_permission('edit_subnet'),
'can_delete_subnet': has_permission('delete_subnet'),
'feature_flags': feature_flags
}
return render_with_user('admin.html', **result_data)
@app.route('/admin/feature_flags', methods=['POST'])
@permission_required('manage_users')
def update_feature_flags():
"""Update feature flags"""
user_name = get_current_user_name()
from flask import current_app
with get_db_connection(current_app) as conn:
cursor = conn.cursor()
# Get all feature flags
cursor.execute('SELECT feature_key FROM FeatureFlags')
all_features = [row[0] for row in cursor.fetchall()]
# Update each feature flag based on form data
for feature_key in all_features:
enabled = request.form.get(f'feature_{feature_key}') == 'on'
cursor.execute('UPDATE FeatureFlags SET enabled = %s WHERE feature_key = %s',
(enabled, feature_key))
logging.info(f"User {user_name} {'enabled' if enabled else 'disabled'} feature '{feature_key}'")
conn.commit()
# Clear admin cache to refresh feature flags
return redirect(url_for('admin'))
@app.route('/account', methods=['GET'])
@login_required
@@ -2066,9 +1997,6 @@ def users():
def tags():
from flask import current_app
with get_db_connection(current_app) as conn:
# Check if device tags feature is enabled
if not is_feature_enabled('device_tags', conn=conn):
abort(404)
cursor = conn.cursor()
error = None
@@ -2158,15 +2086,8 @@ def custom_fields():
error = 'You do not have permission to add custom fields.'
else:
entity_type = request.form.get('entity_type', '').strip()
# Debug logging
logging.info(f"Received entity_type: '{entity_type}' (type: {type(entity_type)})")
logging.info(f"Form data keys: {list(request.form.keys())}")
if not entity_type or entity_type not in ['device', 'subnet']:
# Try to get from form data directly
entity_type_raw = request.form.get('entity_type')
logging.error(f"Invalid entity_type received: '{entity_type}' (raw: '{entity_type_raw}')")
# Show more helpful error
error = f'Invalid entity type: "{entity_type}". Must be "device" or "subnet". Please try again.'
error = 'Invalid entity type. Must be "device" or "subnet".'
else:
name = request.form['name'].strip()
field_key = request.form.get('field_key', '').strip()
@@ -2175,7 +2096,6 @@ def custom_fields():
default_value = request.form.get('default_value', '').strip()
help_text = request.form.get('help_text', '').strip()
display_order = int(request.form.get('display_order', 0))
searchable = 'searchable' in request.form
# Generate field_key from name if not provided
if not field_key:
@@ -2193,10 +2113,10 @@ def custom_fields():
cursor.execute('''
INSERT INTO CustomFieldDefinition
(entity_type, name, field_key, field_type, required, default_value,
help_text, display_order, validation_rules, searchable)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
help_text, display_order, validation_rules)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
''', (entity_type, name, field_key, field_type, required, default_value,
help_text, display_order, validation_rules_json, searchable))
help_text, display_order, validation_rules_json))
add_audit_log(session['user_id'], 'add_custom_field',
f"Added custom field '{name}' for {entity_type}", conn=conn)
conn.commit()
@@ -2216,7 +2136,6 @@ def custom_fields():
default_value = request.form.get('default_value', '').strip()
help_text = request.form.get('help_text', '').strip()
display_order = int(request.form.get('display_order', 0))
searchable = 'searchable' in request.form
# Build validation_rules JSON
validation_rules_json = build_validation_rules_from_form(request.form, field_type)
@@ -2232,10 +2151,10 @@ def custom_fields():
cursor.execute('''
UPDATE CustomFieldDefinition
SET name = %s, field_type = %s, required = %s, default_value = %s,
help_text = %s, display_order = %s, validation_rules = %s, searchable = %s
help_text = %s, display_order = %s, validation_rules = %s
WHERE id = %s
''', (name, field_type, required, default_value, help_text,
display_order, validation_rules_json, searchable, field_id))
display_order, validation_rules_json, field_id))
add_audit_log(session['user_id'], 'edit_custom_field',
f"Updated custom field '{name}'", conn=conn)
conn.commit()
@@ -2274,7 +2193,7 @@ def custom_fields():
# Get all custom fields grouped by entity type
cursor.execute('''
SELECT id, entity_type, name, field_key, field_type, required,
default_value, help_text, display_order, validation_rules, searchable
default_value, help_text, display_order, validation_rules
FROM CustomFieldDefinition
ORDER BY entity_type, display_order, name
''')
@@ -2305,26 +2224,6 @@ def custom_fields():
can_manage=has_permission('manage_custom_fields'),
active_tab=active_tab)
@app.route('/custom_fields/<entity_type>')
@permission_required('view_custom_fields')
def custom_fields_by_type(entity_type):
"""Get custom fields for a specific entity type"""
if entity_type not in ['device', 'subnet']:
abort(404)
from flask import current_app
with get_db_connection(current_app) as conn:
cursor = conn.cursor(dictionary=True)
cursor.execute('''
SELECT id, entity_type, name, field_key, field_type, required,
default_value, help_text, display_order, validation_rules, searchable
FROM CustomFieldDefinition
WHERE entity_type = %s
ORDER BY display_order, name
''', (entity_type,))
fields = cursor.fetchall()
return jsonify({'fields': fields})
@app.route('/audit')
@permission_required('view_audit')
@@ -2370,60 +2269,6 @@ def export_audit_csv():
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
return csv_attachment(logs, ['User', 'Action', 'Details', 'Subnet', 'Timestamp'], f'audit_logs_{timestamp}.csv')
@app.route('/check_update')
@login_required
def check_update():
"""Check for available updates from Gitea"""
try:
# Get current version from environment
current_version = os.environ.get('VERSION', 'unknown').lstrip('v')
# Fetch latest release from Gitea
response = requests.get('https://git.jdbnet.co.uk/api/v1/repos/jamie/ipam/releases/latest', timeout=5)
if response.status_code != 200:
return jsonify({'error': 'Failed to fetch release information'}), 500
release_data = response.json()
latest_version = release_data.get('tag_name', '').lstrip('v')
# Compare versions using semantic versioning
result = {'update_available': False}
if latest_version and latest_version != current_version:
# Simple semantic version comparison
def version_tuple(v):
"""Convert version string to tuple for comparison"""
parts = v.split('.')
return tuple(int(x) if x.isdigit() else 0 for x in parts[:3])
try:
current_tuple = version_tuple(current_version)
latest_tuple = version_tuple(latest_version)
# Only show update if latest is actually newer
if latest_tuple > current_tuple:
result = {
'update_available': True,
'current_version': current_version,
'latest_version': latest_version,
'release_url': release_data.get('html_url', '')
}
except (ValueError, AttributeError):
# Fallback to string comparison if parsing fails
if latest_version != current_version:
result = {
'update_available': True,
'current_version': current_version,
'latest_version': latest_version,
'release_url': release_data.get('html_url', '')
}
return jsonify(result)
except requests.RequestException as e:
logging.error(f"Error checking for updates: {e}")
return jsonify({'error': 'Failed to check for updates'}), 500
except Exception as e:
logging.error(f"Unexpected error checking for updates: {e}")
return jsonify({'error': 'Failed to check for updates'}), 500
@app.route('/get_available_ips')
@permission_required('view_device')
@@ -2756,9 +2601,6 @@ def devices_by_tag(tag_id):
def racks():
from flask import current_app
with get_db_connection(current_app) as conn:
# Check if racks feature is enabled
if not is_feature_enabled('racks', conn=conn):
abort(404)
cursor = conn.cursor(dictionary=True)
cursor.execute('SELECT * FROM Rack')
racks = cursor.fetchall()
@@ -2778,10 +2620,6 @@ def racks():
@permission_required('add_rack')
def add_rack():
from flask import current_app
with get_db_connection(current_app) as conn:
# Check if racks feature is enabled
if not is_feature_enabled('racks', conn=conn):
abort(404)
if request.method == 'POST':
name = request.form['name']
site = request.form['site']
@@ -2802,8 +2640,6 @@ def add_rack():
def rack(rack_id):
side = request.args.get('side', 'front')
with get_db_connection(current_app) as conn:
if not is_feature_enabled('racks', conn=conn):
abort(404)
return render_rack_page(conn, rack_id, side)
@app.route('/rack/<int:rack_id>/add_device', methods=['POST'])
@@ -2814,8 +2650,6 @@ def rack_add_device(rack_id):
side = request.form['side']
user_name = get_current_user_name()
with get_db_connection(current_app) as conn:
if not is_feature_enabled('racks', conn=conn):
abort(404)
cursor = conn.cursor(dictionary=True)
_, placement_error = check_rack_placement(cursor, rack_id, position_u, side)
if placement_error == 'Rack not found':
@@ -2848,8 +2682,6 @@ def rack_add_nonnet_device(rack_id):
side = request.form['side']
user_name = get_current_user_name()
with get_db_connection(current_app) as conn:
if not is_feature_enabled('racks', conn=conn):
abort(404)
cursor = conn.cursor(dictionary=True)
_, placement_error = check_rack_placement(cursor, rack_id, position_u, side)
if placement_error == 'Rack not found':
@@ -2876,9 +2708,6 @@ def rack_remove_device(rack_id):
user_name = get_current_user_name()
from flask import current_app
with get_db_connection(current_app) as conn:
# Check if racks feature is enabled
if not is_feature_enabled('racks', conn=conn):
abort(404)
cursor = conn.cursor(dictionary=True)
cursor.execute('SELECT device_id, nonnet_device_name, position_u, side FROM RackDevice WHERE id = %s', (rack_device_id,))
rd = cursor.fetchone()
@@ -2903,9 +2732,6 @@ def delete_rack(rack_id):
user_name = get_current_user_name()
from flask import current_app
with get_db_connection(current_app) as conn:
# Check if racks feature is enabled
if not is_feature_enabled('racks', conn=conn):
abort(404)
cursor = conn.cursor()
cursor.execute('SELECT name FROM Rack WHERE id = %s', (rack_id,))
rack_name = cursor.fetchone()
@@ -2920,9 +2746,6 @@ def delete_rack(rack_id):
def export_rack_csv(rack_id):
from flask import current_app
with get_db_connection(current_app) as conn:
# Check if racks feature is enabled
if not is_feature_enabled('racks', conn=conn):
abort(404)
cursor = conn.cursor(dictionary=True)
cursor.execute('SELECT * FROM Rack WHERE id = %s', (rack_id,))
rack = cursor.fetchone()
@@ -3546,9 +3369,6 @@ def api_racks():
"""Get all racks"""
from flask import current_app
with get_db_connection(current_app) as conn:
# Check if racks feature is enabled
if not is_feature_enabled('racks', conn=conn):
return jsonify({'error': 'Racks feature is disabled'}), 404
cursor = conn.cursor(dictionary=True)
cursor.execute('SELECT id, name, site, height_u FROM Rack ORDER BY site, name')
racks = cursor.fetchall()
@@ -3574,9 +3394,6 @@ def api_rack(rack_id):
"""Get a specific rack"""
from flask import current_app
with get_db_connection(current_app) as conn:
# Check if racks feature is enabled
if not is_feature_enabled('racks', conn=conn):
return jsonify({'error': 'Racks feature is disabled'}), 404
cursor = conn.cursor(dictionary=True)
cursor.execute('SELECT id, name, site, height_u FROM Rack WHERE id = %s', (rack_id,))
rack = cursor.fetchone()
@@ -3613,9 +3430,6 @@ def api_add_rack():
return jsonify({'error': 'height_u must be greater than zero'}), 400
with get_db_connection(current_app) as conn:
# Check if racks feature is enabled
if not is_feature_enabled('racks', conn=conn):
return jsonify({'error': 'Racks feature is disabled'}), 404
cursor = conn.cursor()
cursor.execute('INSERT INTO Rack (name, site, height_u) VALUES (%s, %s, %s)', (name, site, height_u))
rack_id = cursor.lastrowid
@@ -3629,9 +3443,6 @@ def api_delete_rack(rack_id):
"""Delete a rack"""
from flask import current_app
with get_db_connection(current_app) as conn:
# Check if racks feature is enabled
if not is_feature_enabled('racks', conn=conn):
return jsonify({'error': 'Racks feature is disabled'}), 404
cursor = conn.cursor()
cursor.execute('SELECT name FROM Rack WHERE id = %s', (rack_id,))
rack = cursor.fetchone()
@@ -3674,9 +3485,6 @@ def api_add_device_to_rack(rack_id):
return jsonify({'error': 'device_id must be an integer'}), 400
with get_db_connection(current_app) as conn:
# Check if racks feature is enabled
if not is_feature_enabled('racks', conn=conn):
return jsonify({'error': 'Racks feature is disabled'}), 404
cursor = conn.cursor(dictionary=True)
cursor.execute('SELECT name, height_u FROM Rack WHERE id = %s', (rack_id,))
rack = cursor.fetchone()
@@ -3736,9 +3544,6 @@ def api_remove_device_from_rack(rack_id, rack_device_id):
"""Remove a device from a rack"""
from flask import current_app
with get_db_connection(current_app) as conn:
# Check if racks feature is enabled
if not is_feature_enabled('racks', conn=conn):
return jsonify({'error': 'Racks feature is disabled'}), 404
cursor = conn.cursor(dictionary=True)
cursor.execute('''
SELECT rd.device_id, rd.nonnet_device_name, rd.position_u, rd.side,
@@ -3778,7 +3583,7 @@ def api_custom_fields_by_type(entity_type):
cursor = conn.cursor(dictionary=True)
cursor.execute('''
SELECT id, entity_type, name, field_key, field_type, required,
default_value, help_text, display_order, validation_rules, searchable
default_value, help_text, display_order, validation_rules
FROM CustomFieldDefinition
WHERE entity_type = %s
ORDER BY display_order, name
@@ -3820,12 +3625,12 @@ def api_add_custom_field():
cursor.execute('''
INSERT INTO CustomFieldDefinition
(entity_type, name, field_key, field_type, required, default_value,
help_text, display_order, validation_rules, searchable)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
help_text, display_order, validation_rules)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
''', (entity_type, data['name'], data['field_key'], data['field_type'],
data.get('required', False), data.get('default_value'),
data.get('help_text'), data.get('display_order', 0),
validation_rules_json, data.get('searchable', False)))
validation_rules_json))
field_id = cursor.lastrowid
add_audit_log(request.api_user['id'], 'add_custom_field',
f"Added custom field '{data['name']}' for {entity_type}", conn=conn)
@@ -3874,9 +3679,6 @@ def api_update_custom_field(field_id):
validation_rules_json = json.dumps(data['validation_rules']) if data['validation_rules'] else None
updates.append('validation_rules = %s')
values.append(validation_rules_json)
if 'searchable' in data:
updates.append('searchable = %s')
values.append(data['searchable'])
if not updates:
return jsonify({'error': 'No changes to apply'}), 400
@@ -3987,9 +3789,6 @@ def api_tags():
"""Get all tags"""
from flask import current_app
with get_db_connection(current_app) as conn:
# Check if device tags feature is enabled
if not is_feature_enabled('device_tags', conn=conn):
return jsonify({'error': 'Device tags feature is disabled'}), 404
cursor = conn.cursor(dictionary=True)
cursor.execute('SELECT id, name, color, description, created_at FROM Tag ORDER BY name')
tags = cursor.fetchall()
@@ -4002,11 +3801,6 @@ def api_tags():
@api_permission_required('add_tag')
def api_add_tag():
"""Create a new tag"""
from flask import current_app
with get_db_connection(current_app) as conn:
# Check if device tags feature is enabled
if not is_feature_enabled('device_tags', conn=conn):
return jsonify({'error': 'Device tags feature is disabled'}), 404
data = request.get_json()
if not data or 'name' not in data:
return jsonify({'error': 'Tag name is required'}), 400
@@ -4018,7 +3812,6 @@ def api_add_tag():
color = data.get('color', '#6B7280')
description = data.get('description', '')
from flask import current_app
with get_db_connection(current_app) as conn:
cursor = conn.cursor()
try:
@@ -4036,9 +3829,6 @@ def api_tag(tag_id):
"""Get a specific tag"""
from flask import current_app
with get_db_connection(current_app) as conn:
# Check if device tags feature is enabled
if not is_feature_enabled('device_tags', conn=conn):
return jsonify({'error': 'Device tags feature is disabled'}), 404
cursor = conn.cursor(dictionary=True)
cursor.execute('SELECT id, name, color, description, created_at FROM Tag WHERE id = %s', (tag_id,))
tag = cursor.fetchone()
@@ -4059,16 +3849,10 @@ def api_tag(tag_id):
@api_permission_required('edit_tag')
def api_update_tag(tag_id):
"""Update a tag"""
from flask import current_app
with get_db_connection(current_app) as conn:
# Check if device tags feature is enabled
if not is_feature_enabled('device_tags', conn=conn):
return jsonify({'error': 'Device tags feature is disabled'}), 404
data = request.get_json()
if not data:
return jsonify({'error': 'Request body is required'}), 400
from flask import current_app
with get_db_connection(current_app) as conn:
cursor = conn.cursor()
cursor.execute('SELECT name, color, description FROM Tag WHERE id = %s', (tag_id,))
@@ -4113,9 +3897,6 @@ def api_delete_tag(tag_id):
"""Delete a tag"""
from flask import current_app
with get_db_connection(current_app) as conn:
# Check if device tags feature is enabled
if not is_feature_enabled('device_tags', conn=conn):
return jsonify({'error': 'Device tags feature is disabled'}), 404
cursor = conn.cursor()
cursor.execute('SELECT name FROM Tag WHERE id = %s', (tag_id,))
tag = cursor.fetchone()
@@ -4133,9 +3914,6 @@ def api_device_tags(device_id):
"""Get tags for a specific device"""
from flask import current_app
with get_db_connection(current_app) as conn:
# Check if device tags feature is enabled
if not is_feature_enabled('device_tags', conn=conn):
return jsonify({'error': 'Device tags feature is disabled'}), 404
cursor = conn.cursor(dictionary=True)
cursor.execute('SELECT id, name FROM Device WHERE id = %s', (device_id,))
if not cursor.fetchone():
@@ -4154,9 +3932,6 @@ def api_device_tags(device_id):
@api_permission_required('assign_device_tag')
def api_assign_device_tag(device_id):
"""Assign a tag to a device"""
with get_db_connection(current_app) as conn:
if not is_feature_enabled('device_tags', conn=conn):
return jsonify({'error': 'Device tags feature is disabled'}), 404
data = request.get_json()
if not data or 'tag_id' not in data:
return jsonify({'error': 'tag_id is required'}), 400
@@ -4180,8 +3955,6 @@ def api_assign_device_tag(device_id):
def api_remove_device_tag(device_id, tag_id):
"""Remove a tag from a device"""
with get_db_connection(current_app) as conn:
if not is_feature_enabled('device_tags', conn=conn):
return jsonify({'error': 'Device tags feature is disabled'}), 404
try:
remove_tag_from_device(conn, device_id, tag_id, request.api_user['id'])
conn.commit()
@@ -4373,21 +4146,14 @@ def api_roles():
def bulk_operations():
from flask import current_app
with get_db_connection(current_app) as conn:
# Check if bulk operations feature is enabled
if not is_feature_enabled('bulk_operations', conn=conn):
abort(404)
cursor = conn.cursor()
cursor.execute('SELECT id, name FROM Device ORDER BY name')
devices = cursor.fetchall()
cursor.execute('SELECT id, name, cidr, site FROM Subnet ORDER BY site, name')
subnets = cursor.fetchall()
# Get tags only if device tags feature is enabled
tags_enabled = is_feature_enabled('device_tags', conn=conn)
tags = []
if tags_enabled:
cursor.execute('SELECT id, name FROM Tag ORDER BY name')
tags = cursor.fetchall()
cursor.execute('SELECT id, name FROM Tag ORDER BY name')
tags = cursor.fetchall()
cursor.execute('SELECT id, name FROM DeviceType ORDER BY name')
device_types = cursor.fetchall()
@@ -4404,17 +4170,11 @@ def bulk_operations():
@app.route('/bulk/assign_ips', methods=['POST'])
@permission_required('add_device_ip')
def bulk_assign_ips():
from flask import current_app
with get_db_connection(current_app) as conn:
# Check if bulk operations feature is enabled
if not is_feature_enabled('bulk_operations', conn=conn):
abort(404)
device_id = request.form['device_id']
ip_ids = request.form.getlist('ip_ids[]')
user_name = get_current_user_name()
results = {'success': [], 'failed': []}
from flask import current_app
with get_db_connection(current_app) as conn:
cursor = conn.cursor()
if not get_device_name(cursor, device_id):
@@ -4436,17 +4196,11 @@ def bulk_assign_ips():
@app.route('/bulk/create_devices', methods=['POST'])
@permission_required('add_device')
def bulk_create_devices():
from flask import current_app
with get_db_connection(current_app) as conn:
# Check if bulk operations feature is enabled
if not is_feature_enabled('bulk_operations', conn=conn):
abort(404)
device_names = request.form.get('device_names', '').strip().split('\n')
device_type_id = int(request.form.get('device_type', 1))
user_name = get_current_user_name()
results = {'success': [], 'failed': []}
from flask import current_app
with get_db_connection(current_app) as conn:
cursor = conn.cursor()
for name in device_names:
@@ -4467,20 +4221,11 @@ def bulk_create_devices():
@app.route('/bulk/assign_tags', methods=['POST'])
@permission_required('assign_device_tag')
def bulk_assign_tags():
from flask import current_app
with get_db_connection(current_app) as conn:
# Check if bulk operations feature is enabled
if not is_feature_enabled('bulk_operations', conn=conn):
abort(404)
# Check if device tags feature is enabled
if not is_feature_enabled('device_tags', conn=conn):
abort(404)
device_ids = request.form.getlist('device_ids[]')
tag_ids = request.form.getlist('tag_ids[]')
user_name = get_current_user_name()
results = {'success': [], 'failed': []}
from flask import current_app
with get_db_connection(current_app) as conn:
cursor = conn.cursor()
for device_id in device_ids:
@@ -4517,9 +4262,6 @@ def bulk_assign_tags():
@app.route('/bulk/export_subnets', methods=['POST'])
@permission_required('export_subnet_csv')
def bulk_export_subnets():
with get_db_connection(current_app) as conn:
if not is_feature_enabled('bulk_operations', conn=conn):
abort(404)
subnet_ids = request.form.getlist('subnet_ids[]')
output = StringIO()
writer = csv.writer(output)
@@ -4566,7 +4308,6 @@ def inject_env_vars():
'LOGO_PNG': os.environ.get('LOGO_PNG', 'https://assets.jdbnet.co.uk/logo/128x128.png'),
'VERSION': version,
'has_permission': has_permission,
'is_feature_enabled': is_feature_enabled,
}
init_db(app)