Compare commits

..

3 Commits

Author SHA1 Message Date
1206c90eeb remove duplicate user 2025-03-30 17:13:35 -04:00
f370666d79 more changes 2025-03-30 16:30:50 -04:00
a7679663cc add user button fixed 2025-03-28 16:47:54 -04:00
2 changed files with 151 additions and 201 deletions

View File

@@ -90,30 +90,35 @@ def sql():
@app.route('/user_list') @app.route('/user_list')
def user_list(): def user_list():
"""Displays the user list with VLAN IDs.""" """Displays the user list with VLAN IDs and descriptions from rad_description."""
db = get_db() db = get_db()
if db is None: if db is None:
return "Database connection failed", 500 return "Database connection failed", 500
cursor = db.cursor(dictionary=True) cursor = db.cursor(dictionary=True)
try: try:
# Fetch users and their group assignments # Fetch users, their group assignments, and descriptions from rad_description
cursor.execute(""" cursor.execute("""
SELECT r.username AS mac_address, r.value AS description, ug.groupname AS vlan_id SELECT
r.username AS mac_address,
rd.description AS description,
ug.groupname AS vlan_id
FROM radcheck r FROM radcheck r
LEFT JOIN radusergroup ug ON r.username = ug.username LEFT JOIN radusergroup ug ON r.username = ug.username
WHERE r.attribute = 'User-Description' LEFT JOIN rad_description rd ON r.username = rd.username
""") """) #removed WHERE clause
results = cursor.fetchall() results = cursor.fetchall()
print("Results:", results) #added print statement
# Fetch all group names for the dropdown # Fetch all group names for the dropdown
cursor.execute("SELECT groupname FROM radgroupcheck") cursor.execute("SELECT groupname FROM radgroupcheck")
groups = cursor.fetchall() groups = cursor.fetchall()
groups = [{'groupname': row['groupname']} for row in groups] # changed groups = [{'groupname': row['groupname']} for row in groups]
print("Groups:", groups) #added print statement
cursor.close() cursor.close()
db.close() db.close()
return render_template('user_list_inline_edit.html', results=results, groups=groups) # added groups return render_template('user_list_inline_edit.html', results=results, groups=groups)
except mysql.connector.Error as e: except mysql.connector.Error as e:
print(f"Database error: {e}") print(f"Database error: {e}")
cursor.close() cursor.close()
@@ -132,12 +137,14 @@ def update_user():
try: try:
db.autocommit = False db.autocommit = False
# Update rad_description table
cursor.execute(""" cursor.execute("""
UPDATE radcheck UPDATE rad_description
SET value = %s SET description = %s
WHERE username = %s AND attribute = 'User-Description' WHERE username = %s
""", (description, mac_address)) """, (description, mac_address))
# Update radgroupreply table for VLAN ID
cursor.execute(""" cursor.execute("""
UPDATE radgroupreply rgr UPDATE radgroupreply rgr
SET value = %s SET value = %s
@@ -169,65 +176,77 @@ def delete_user(mac_address):
if db: if db:
cursor = db.cursor() cursor = db.cursor()
try: try:
db.autocommit = False #Start transaction
# Delete from rad_description
cursor.execute("DELETE FROM rad_description WHERE username = %s", (mac_address,))
# Delete from radcheck
cursor.execute("DELETE FROM radcheck WHERE username = %s", (mac_address,)) cursor.execute("DELETE FROM radcheck WHERE username = %s", (mac_address,))
db.commit()
#Delete from radusergroup
cursor.execute("DELETE FROM radusergroup WHERE username = %s", (mac_address,))
db.commit() #Commit transaction
db.autocommit = True
cursor.close() cursor.close()
db.close() db.close()
return redirect(url_for('user_list')) return redirect(url_for('user_list'))
except mysql.connector.Error as err: except mysql.connector.Error as err:
print(f"Database Error: {err}") print(f"Database Error: {err}")
db.rollback() db.rollback() #Roll back transaction on error
db.autocommit = True
cursor.close() cursor.close()
db.close() db.close()
return redirect(url_for('user_list')) return redirect(url_for('user_list'))
return "Database Connection Failed" return "Database Connection Failed"
@app.route('/edit_user/<mac_address>', methods=['GET', 'POST']) # @app.route('/edit_user/<mac_address>', methods=['GET', 'POST'])
def edit_user(mac_address): # def edit_user(mac_address):
db = get_db() # db = get_db()
if db: # if db:
cursor = db.cursor(dictionary=True) # cursor = db.cursor(dictionary=True)
if request.method == 'POST': # if request.method == 'POST':
description = request.form['description'] # description = request.form['description']
vlan_id = request.form['vlan_id'] # vlan_id = request.form['vlan_id']
cursor.execute(""" # cursor.execute("""
UPDATE radcheck # UPDATE radcheck
SET value = %s # SET value = %s
WHERE username = %s AND attribute = 'User-Description' # WHERE username = %s AND attribute = 'User-Description'
""", (description, mac_address)) # """, (description, mac_address))
cursor.execute(""" # cursor.execute("""
UPDATE radgroupreply rgr # UPDATE radgroupreply rgr
SET value = %s # SET value = %s
WHERE rgr.groupname = (SELECT groupname FROM radusergroup rug WHERE rug.username = %s LIMIT 1) # WHERE rgr.groupname = (SELECT groupname FROM radusergroup rug WHERE rug.username = %s LIMIT 1)
AND rgr.attribute = 'Tunnel-Private-Group-Id' # AND rgr.attribute = 'Tunnel-Private-Group-Id'
""", (vlan_id, mac_address)) # """, (vlan_id, mac_address))
db.commit() # db.commit()
cursor.close() # cursor.close()
db.close() # db.close()
return redirect(url_for('user_list')) # return redirect(url_for('user_list'))
else: # else:
cursor.execute(""" # cursor.execute("""
SELECT # SELECT
rc.username AS mac_address, # rc.username AS mac_address,
IFNULL((SELECT value FROM radgroupreply rgr # IFNULL((SELECT value FROM radgroupreply rgr
WHERE rgr.groupname = (SELECT groupname FROM radusergroup rug WHERE rug.username = rc.username LIMIT 1) # WHERE rgr.groupname = (SELECT groupname FROM radusergroup rug WHERE rug.username = rc.username LIMIT 1)
AND rgr.attribute = 'Tunnel-Private-Group-Id' LIMIT 1), 'N/A') AS vlan_id, # AND rgr.attribute = 'Tunnel-Private-Group-Id' LIMIT 1), 'N/A') AS vlan_id,
IFNULL((SELECT value FROM radcheck rch # IFNULL((SELECT value FROM radcheck rch
WHERE rch.username = rc.username AND rch.attribute = 'User-Description' LIMIT 1), 'N/A') AS description # WHERE rch.username = rc.username AND rch.attribute = 'User-Description' LIMIT 1), 'N/A') AS description
FROM radcheck rc # FROM radcheck rc
WHERE rc.username = %s # WHERE rc.username = %s
GROUP BY rc.username; # GROUP BY rc.username;
""", (mac_address,)) # """, (mac_address,))
user = cursor.fetchone() # user = cursor.fetchone()
cursor.close() # cursor.close()
db.close() # db.close()
return render_template('edit_user.html', user=user) # return render_template('edit_user.html', user=user)
return "Database Connection Failed" # return "Database Connection Failed"
@app.route('/groups') @app.route('/groups')
def groups(): def groups():
@@ -520,6 +539,8 @@ def add_user():
cursor = db.cursor() cursor = db.cursor()
try: try:
db.autocommit = False #Start Transaction
# Check if user already exists # Check if user already exists
cursor.execute("SELECT username FROM radcheck WHERE username = %s", (mac_address,)) cursor.execute("SELECT username FROM radcheck WHERE username = %s", (mac_address,))
if cursor.fetchone(): if cursor.fetchone():
@@ -530,9 +551,14 @@ def add_user():
# Insert into radcheck, setting password to MAC address # Insert into radcheck, setting password to MAC address
cursor.execute(""" cursor.execute("""
INSERT INTO radcheck (username, attribute, op, value) INSERT INTO radcheck (username, attribute, op, value)
VALUES (%s, 'Cleartext-Password', ':=', %s), VALUES (%s, 'Cleartext-Password', ':=', %s)
(%s, 'User-Description', ':=', %s) """, (mac_address, mac_address)) # Use mac_address for both username and password
""", (mac_address, mac_address, mac_address, description)) # Use mac_address for both username and password
# Insert description into rad_description
cursor.execute("""
INSERT INTO rad_description (username, description)
VALUES (%s, %s)
""", (mac_address, description))
# Insert into radusergroup with the selected group # Insert into radusergroup with the selected group
cursor.execute(""" cursor.execute("""
@@ -540,21 +566,24 @@ def add_user():
VALUES (%s, %s) VALUES (%s, %s)
""", (mac_address, vlan_id)) # Use vlan_id """, (mac_address, vlan_id)) # Use vlan_id
db.commit() db.commit() #Commit transaction
db.autocommit = True
cursor.close() cursor.close()
db.close() db.close()
return jsonify({'success': True, 'message': 'User added successfully'}) return jsonify({'success': True, 'message': 'User added successfully'})
except mysql.connector.Error as err: except mysql.connector.Error as err:
print(f"Database Error: {err}") print(f"Database Error: {err}")
db.rollback() db.rollback() #Rollback transaction on error.
db.autocommit = True
cursor.close() cursor.close()
db.close() db.close()
return jsonify({'success': False, 'message': f"Database error: {err}"}), 500 return jsonify({'success': False, 'message': f"Database error: {err}"}), 500
except Exception as e: except Exception as e:
print(f"Error adding user: {e}") print(f"Error adding user: {e}")
db.rollback() db.rollback() #Rollback transaction on error.
db.autocommit = True
cursor.close() cursor.close()
db.close() db.close()
return jsonify({'success': False, 'message': str(e)}), 500 return jsonify({'success': False, 'message': str(e)}), 500
@@ -563,6 +592,56 @@ def add_user():
except Exception as e: except Exception as e:
return jsonify({'success': False, 'message': 'Unknown error'}), 500 return jsonify({'success': False, 'message': 'Unknown error'}), 500
@app.route('/duplicate_user', methods=['POST'])
def duplicate_user():
"""
Retrieves user data (MAC address, description, VLAN ID) from the database
based on the provided MAC address. This data is intended to be used to
pre-populate a "duplicate user" form in the frontend.
"""
mac_address = request.form['mac_address'] # Get the MAC address from the POST request.
db = get_db() # Get a database connection.
if db:
cursor = db.cursor(dictionary=True) # Create a cursor that returns results as dictionaries.
try:
# Construct the SQL query. This query retrieves the MAC address,
# description, and VLAN ID for the specified user.
cursor.execute("""
SELECT
rc.username AS mac_address,
IFNULL((SELECT value FROM radgroupreply rgr
WHERE rgr.groupname = (SELECT groupname FROM radusergroup rug WHERE rug.username = rc.username LIMIT 1)
AND rgr.attribute = 'Tunnel-Private-Group-Id' LIMIT 1), 'N/A') AS vlan_id,
IFNULL((SELECT value FROM radcheck rch
WHERE rch.username = rc.username AND rch.attribute = 'User-Description' LIMIT 1), 'N/A') AS description
FROM radcheck rc
WHERE rc.username = %s /* %s is a placeholder for the MAC address */
GROUP BY rc.username;
""", (mac_address,)) # Execute the query with the MAC address as a parameter.
user_data = cursor.fetchone() # Fetch the first (and should be only) result.
cursor.close() # Close the cursor.
db.close() # Close the database connection.
if user_data:
# If user data was found, return it as a JSON response.
return jsonify(user_data)
else:
# If no user data was found (e.g., invalid MAC address), return an empty JSON object.
return jsonify({})
except mysql.connector.Error as err:
# Handle database errors. Log the error and return an error message.
print(f"Database Error: {err}")
cursor.close()
db.close()
return jsonify({}) # Return an empty JSON object on error, to avoid crashing.
else:
# Handle the case where the database connection could not be established.
return jsonify({}) # Return empty JSON object
if __name__ == '__main__': if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=8080) app.run(debug=True, host='0.0.0.0', port=8080)

View File

@@ -20,13 +20,17 @@
<td><input type="text" id="mac_address-{{ user.mac_address }}" value="{{ user.mac_address }}"></td> <td><input type="text" id="mac_address-{{ user.mac_address }}" value="{{ user.mac_address }}"></td>
<td><input type="text" id="description-{{ user.mac_address }}" value="{{ user.description }}"></td> <td><input type="text" id="description-{{ user.mac_address }}" value="{{ user.description }}"></td>
<td> <td>
<input type="text" id="vlan_id-{{ user.mac_address }}" value="{{ user.vlan_id }}"> <select id="vlan_id-{{ user.mac_address }}">
{% for group in groups %}
<option value="{{ group.groupname }}" {% if user.vlan_id == group.groupname %} selected {% endif %}>
{{ group.groupname }}
</option>
{% endfor %}
</select>
</td> </td>
<td> <td>
<button onclick="updateUser('{{ user.mac_address }}')"></button> <button onclick="updateUser('{{ user.mac_address }}')"></button>
<button onclick="location.reload()"></button>
<a href="/delete_user/{{ user.mac_address }}" onclick="saveScrollPosition()">🗑️</a> <a href="/delete_user/{{ user.mac_address }}" onclick="saveScrollPosition()">🗑️</a>
<button onclick="duplicateUser('{{ user.mac_address }}')">Duplicate</button>
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
@@ -71,17 +75,7 @@
</div> </div>
</dialog> </dialog>
<dialog id="duplicate-dialog">
<div id="duplicate-dialog-content"></div>
<button id="close-dialog"></button>
<button id="save-duplicated-user">Save</button>
</dialog>
<style> <style>
.merged-cell {
border: none;
}
#cancel-add-user-dialog { #cancel-add-user-dialog {
border-radius: 5px; border-radius: 5px;
padding: 10px; padding: 10px;
@@ -117,17 +111,18 @@
</style> </style>
<script> <script>
const groups = {{ groups | tojson | safe }};
function updateUser(mac_address) { function updateUser(mac_address) {
const description = document.getElementById('description-' + mac_address).value; const description = document.getElementById('description-' + mac_address).value;
const vlan_id = document.getElementById('vlan_id-' + mac_address).value; const vlan_id = document.getElementById('vlan_id-' + mac_address).value;
const mac_address_input = document.getElementById('mac_address-' + mac_address).value;
fetch('/update_user', { fetch('/update_user', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/x-www-form-urlencoded', 'Content-Type': 'application/x-www-form-urlencoded',
}, },
body: `mac_address=${mac_address}&description=${description}&vlan_id=${vlan_id}&new_mac_address=${mac_address_input}` body: `mac_address=${mac_address}&description=${description}&vlan_id=${vlan_id}`
}) })
.then(response => response.text()) .then(response => response.text())
.then(data => { .then(data => {
@@ -151,127 +146,11 @@
} }
} }
function duplicateUser(mac_address) {
fetch('/duplicate_user', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: `mac_address=${mac_address}`
})
.then(response => response.json())
.then(data => {
const userData = data;
let newTable = `<table border="1">
<thead>
<tr>
<th>MAC Address</th>
<th>Description</th>
<th>VLAN ID</th>
</tr>
</thead>
<tbody>
<tr>
<td><input type="text" id="new-mac" value="${userData.mac_address}"></td>
<td><input type="text" class="new-description" value="${userData.description}"></td>
<td>
<select id="new-vlan_id">
{% for group in groups %}
<option value="{{ group.groupname }}" ${userData.vlan_id === group.groupname ? 'selected' : ''}>
{{ group.groupname }}
</option>
{% endfor %}
</select>
</td>
</tr>`;
newTable += `<tr>
<td colspan="3" class="merged-cell">
<button onclick="addDuplicatedUserRow(this)"></button>
</td>
</tr></tbody></table>`;
document.getElementById('duplicate-dialog-content').innerHTML = newTable;
document.getElementById('duplicate-dialog').showModal();
});
}
document.getElementById('close-dialog').addEventListener('click', () => {
document.getElementById('duplicate-dialog').close();
});
document.getElementById('save-duplicated-user').addEventListener('click', () => {
saveDuplicatedUser();
});
function saveDuplicatedUser() {
let rows = document.querySelectorAll('#duplicate-dialog-content table tbody tr');
let new_mac_address = rows[0].querySelector('#new-mac').value;
let attributes = [];
for (let i = 1; i < rows.length - 1; i++) {
const descriptionInput = rows[i].querySelector(`.new-description`);
const vlanIdInput = rows[i].querySelector(`.new-vlan_id`);
if (descriptionInput && vlanIdInput) {
attributes.push({
description: descriptionInput.value,
vlan_id: vlanIdInput.value,
});
} else {
console.warn(`Input elements not found for row ${i}`);
return;
}
}
fetch('/save_duplicated_user', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ mac_address: new_mac_address, attributes: attributes })
})
.then(response => response.text())
.then(data => {
if (data === 'success') {
document.getElementById('duplicate-dialog').close();
location.reload();
} else {
alert('Error saving duplicated user: ' + data);
}
});
}
function addDuplicatedUserRow(button) {
const table = button.parentNode.parentNode.parentNode;
const newRow = table.insertRow(table.rows.length - 1);
const cell1 = newRow.insertCell(0);
const cell2 = newRow.insertCell(1);
const cell3 = newRow.insertCell(2);
const cell4 = newRow.insertCell(3);
cell1.classList.add('merged-cell');
cell2.innerHTML = `<input type="text" class="new-description" value="">`;
cell3.innerHTML = `<select class="new-vlan_id">
{% for group in groups %}
<option value="{{ group.groupname }}">
{{ group.groupname }}
</option>
{% endfor %}
</select>`;
cell4.innerHTML = `<button onclick="removeDuplicatedUserRow(this)">🗑️</button>`;
}
function removeDuplicatedUserRow(button) {
const row = button.parentNode.parentNode;
row.parentNode.removeChild(row);
}
function addNewUserRow() { function addNewUserRow() {
document.getElementById('add-user-dialog').showModal(); document.getElementById('add-user-dialog').showModal();
} }
document.getElementById('close-add-user-dialog').addEventListener('click', () => { document.getElementById('cancel-add-user-dialog').addEventListener('click', () => {
document.getElementById('add-user-dialog').close(); document.getElementById('add-user-dialog').close();
}); });
@@ -289,19 +168,12 @@
return; return;
} }
// Construct the data as an object
const userData = {
mac_address: mac,
description: description,
vlan_id: vlan_id
};
fetch('/add_user', { fetch('/add_user', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },
body: JSON.stringify(userData), body: JSON.stringify({ mac_address: mac, description: description, vlan_id: vlan_id }),
}) })
.then(response => { .then(response => {
if (!response.ok) { if (!response.ok) {
@@ -312,7 +184,6 @@
return response.json(); return response.json();
}) })
.then(data => { .then(data => {
console.log("Server response:", data);
if (data && data.success) { if (data && data.success) {
document.getElementById('add-user-dialog').close(); document.getElementById('add-user-dialog').close();
location.reload(); location.reload();
@@ -326,4 +197,4 @@
}); });
} }
</script> </script>
{% endblock %} {% endblock %}