lots of changes
This commit is contained in:
@@ -3,10 +3,11 @@ FROM python:3.9-slim
|
||||
WORKDIR /app
|
||||
|
||||
COPY requirements.txt .
|
||||
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
RUN apt-get update && apt-get install -y iputils-ping telnet # Add these lines
|
||||
|
||||
COPY . .
|
||||
|
||||
EXPOSE 5000
|
||||
|
||||
CMD ["python", "app.py"]
|
||||
558
app/app.py
558
app/app.py
@@ -0,0 +1,558 @@
|
||||
from flask import Flask, render_template, request, redirect, url_for, jsonify
|
||||
import mysql.connector
|
||||
import json
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
DB_CONFIG = {
|
||||
'host': '192.168.60.150',
|
||||
'user': 'user_92z0Kj',
|
||||
'password': '5B3UXZV8vyrB',
|
||||
'database': 'radius_NIaIuT'
|
||||
}
|
||||
|
||||
def get_db():
|
||||
try:
|
||||
db = mysql.connector.connect(**DB_CONFIG)
|
||||
return db
|
||||
except mysql.connector.Error as err:
|
||||
print(f"Database Connection Error: {err}")
|
||||
return None
|
||||
|
||||
@app.route('/', methods=['GET', 'POST'])
|
||||
def index():
|
||||
sql_results = None
|
||||
sql_error = None
|
||||
total_users = 0
|
||||
total_groups = 0
|
||||
|
||||
db = get_db()
|
||||
if db:
|
||||
cursor = db.cursor(dictionary=True)
|
||||
try:
|
||||
# Count total users
|
||||
cursor.execute("SELECT COUNT(DISTINCT username) as total FROM radcheck;")
|
||||
total_users = cursor.fetchone()['total']
|
||||
|
||||
# Count total groups
|
||||
cursor.execute("SELECT COUNT(DISTINCT groupname) as total FROM radgroupreply;")
|
||||
total_groups = cursor.fetchone()['total']
|
||||
|
||||
except mysql.connector.Error as err:
|
||||
print(f"Error fetching counts: {err}")
|
||||
|
||||
cursor.close()
|
||||
db.close()
|
||||
|
||||
return render_template('index.html', total_users=total_users, total_groups=total_groups, sql_results=sql_results, sql_error=sql_error)
|
||||
|
||||
@app.route('/sql', methods=['POST'])
|
||||
def sql():
|
||||
sql_results = None
|
||||
sql_error = None
|
||||
sql_query = request.form['query']
|
||||
|
||||
db = get_db()
|
||||
if db:
|
||||
try:
|
||||
cursor = db.cursor(dictionary=True)
|
||||
cursor.execute(sql_query)
|
||||
sql_results = cursor.fetchall()
|
||||
cursor.close()
|
||||
db.close()
|
||||
except mysql.connector.Error as err:
|
||||
sql_error = str(err)
|
||||
except Exception as e:
|
||||
sql_error = str(e)
|
||||
|
||||
total_users = 0
|
||||
total_groups = 0
|
||||
|
||||
db = get_db()
|
||||
if db:
|
||||
cursor = db.cursor(dictionary=True)
|
||||
try:
|
||||
# Count total users
|
||||
cursor.execute("SELECT COUNT(DISTINCT username) as total FROM radcheck;")
|
||||
total_users = cursor.fetchone()['total']
|
||||
|
||||
# Count total groups
|
||||
cursor.execute("SELECT COUNT(DISTINCT groupname) as total FROM radgroupreply;")
|
||||
total_groups = cursor.fetchone()['total']
|
||||
|
||||
except mysql.connector.Error as err:
|
||||
print(f"Error fetching counts: {err}")
|
||||
|
||||
cursor.close()
|
||||
db.close()
|
||||
|
||||
return render_template('index.html', total_users=total_users, total_groups=total_groups, sql_results=sql_results, sql_error=sql_error)
|
||||
|
||||
@app.route('/user_list')
|
||||
def user_list():
|
||||
db = get_db()
|
||||
if db:
|
||||
cursor = db.cursor(dictionary=True)
|
||||
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
|
||||
GROUP BY rc.username;
|
||||
""")
|
||||
results = cursor.fetchall()
|
||||
cursor.close()
|
||||
db.close()
|
||||
return render_template('user_list_inline_edit.html', results=results)
|
||||
return "Database Connection Failed"
|
||||
|
||||
@app.route('/update_user', methods=['POST'])
|
||||
def update_user():
|
||||
mac_address = request.form['mac_address']
|
||||
description = request.form['description']
|
||||
vlan_id = request.form['vlan_id']
|
||||
|
||||
db = get_db()
|
||||
if db:
|
||||
cursor = db.cursor()
|
||||
try:
|
||||
db.autocommit = False
|
||||
|
||||
cursor.execute("""
|
||||
UPDATE radcheck
|
||||
SET value = %s
|
||||
WHERE username = %s AND attribute = 'User-Description'
|
||||
""", (description, mac_address))
|
||||
|
||||
cursor.execute("""
|
||||
UPDATE radgroupreply rgr
|
||||
SET value = %s
|
||||
WHERE rgr.groupname = (SELECT groupname FROM radusergroup rug WHERE rug.username = %s LIMIT 1)
|
||||
AND rgr.attribute = 'Tunnel-Private-Group-Id'
|
||||
""", (vlan_id, mac_address))
|
||||
|
||||
db.commit()
|
||||
db.autocommit = True
|
||||
cursor.close()
|
||||
return "success"
|
||||
except mysql.connector.Error as err:
|
||||
db.rollback()
|
||||
db.autocommit = True
|
||||
cursor.close()
|
||||
return str(err)
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
db.autocommit = True
|
||||
cursor.close()
|
||||
return str(e)
|
||||
finally:
|
||||
db.close()
|
||||
return "Database Connection Failed"
|
||||
|
||||
@app.route('/delete_user/<mac_address>')
|
||||
def delete_user(mac_address):
|
||||
db = get_db()
|
||||
if db:
|
||||
cursor = db.cursor()
|
||||
try:
|
||||
cursor.execute("DELETE FROM radcheck WHERE username = %s", (mac_address,))
|
||||
db.commit()
|
||||
cursor.close()
|
||||
db.close()
|
||||
return redirect(url_for('user_list'))
|
||||
except mysql.connector.Error as err:
|
||||
print(f"Database Error: {err}")
|
||||
db.rollback()
|
||||
cursor.close()
|
||||
db.close()
|
||||
return redirect(url_for('user_list'))
|
||||
return "Database Connection Failed"
|
||||
|
||||
@app.route('/edit_user/<mac_address>', methods=['GET', 'POST'])
|
||||
def edit_user(mac_address):
|
||||
db = get_db()
|
||||
if db:
|
||||
cursor = db.cursor(dictionary=True)
|
||||
|
||||
if request.method == 'POST':
|
||||
description = request.form['description']
|
||||
vlan_id = request.form['vlan_id']
|
||||
|
||||
cursor.execute("""
|
||||
UPDATE radcheck
|
||||
SET value = %s
|
||||
WHERE username = %s AND attribute = 'User-Description'
|
||||
""", (description, mac_address))
|
||||
|
||||
cursor.execute("""
|
||||
UPDATE radgroupreply rgr
|
||||
SET value = %s
|
||||
WHERE rgr.groupname = (SELECT groupname FROM radusergroup rug WHERE rug.username = %s LIMIT 1)
|
||||
AND rgr.attribute = 'Tunnel-Private-Group-Id'
|
||||
""", (vlan_id, mac_address))
|
||||
|
||||
db.commit()
|
||||
cursor.close()
|
||||
db.close()
|
||||
return redirect(url_for('user_list'))
|
||||
|
||||
else:
|
||||
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
|
||||
GROUP BY rc.username;
|
||||
""", (mac_address,))
|
||||
user = cursor.fetchone()
|
||||
cursor.close()
|
||||
db.close()
|
||||
return render_template('edit_user.html', user=user)
|
||||
return "Database Connection Failed"
|
||||
|
||||
@app.route('/groups')
|
||||
def groups():
|
||||
db = get_db()
|
||||
if db:
|
||||
cursor = db.cursor()
|
||||
try:
|
||||
# Fetch group names from radgroupcheck
|
||||
cursor.execute("SELECT DISTINCT groupname FROM radgroupcheck")
|
||||
group_names = [row[0] for row in cursor.fetchall()]
|
||||
|
||||
grouped_results = {}
|
||||
for groupname in group_names:
|
||||
# Fetch attributes for each group from radgroupreply
|
||||
cursor.execute("SELECT id, attribute, op, value FROM radgroupreply WHERE groupname = %s", (groupname,))
|
||||
attributes = cursor.fetchall()
|
||||
grouped_results[groupname] = [{'id': row[0], 'attribute': row[1], 'op': row[2], 'value': row[3]} for row in attributes]
|
||||
|
||||
cursor.close()
|
||||
db.close()
|
||||
return render_template('group_list_nested.html', grouped_results=grouped_results)
|
||||
except mysql.connector.Error as err:
|
||||
print(f"Database Error: {err}")
|
||||
cursor.close()
|
||||
db.close()
|
||||
return render_template('group_list_nested.html', grouped_results={})
|
||||
return "Database Connection Failed"
|
||||
|
||||
@app.route('/edit_groupname/<old_groupname>', methods=['GET', 'POST'])
|
||||
def edit_groupname(old_groupname):
|
||||
db = get_db()
|
||||
if db:
|
||||
cursor = db.cursor(dictionary=True)
|
||||
|
||||
if request.method == 'POST':
|
||||
new_groupname = request.form['groupname']
|
||||
try:
|
||||
db.autocommit = False
|
||||
cursor.execute("""
|
||||
UPDATE radgroupreply
|
||||
SET groupname = %s
|
||||
WHERE groupname = %s
|
||||
""", (new_groupname, old_groupname))
|
||||
|
||||
cursor.execute("""
|
||||
UPDATE radusergroup
|
||||
SET groupname = %s
|
||||
WHERE groupname = %s
|
||||
""", (new_groupname, old_groupname))
|
||||
|
||||
db.commit()
|
||||
db.autocommit = True
|
||||
cursor.close()
|
||||
db.close()
|
||||
return redirect(url_for('groups'))
|
||||
except mysql.connector.Error as err:
|
||||
db.rollback()
|
||||
db.autocommit = True
|
||||
cursor.close()
|
||||
db.close()
|
||||
return f"Database Error: {err}"
|
||||
else:
|
||||
return render_template('edit_groupname.html', old_groupname=old_groupname)
|
||||
return "Database Connection Failed"
|
||||
|
||||
@app.route('/update_attribute', methods=['POST'])
|
||||
def update_attribute():
|
||||
group_id = request.form['group_id']
|
||||
attribute = request.form['attribute']
|
||||
op = request.form['op']
|
||||
value = request.form['value']
|
||||
|
||||
db = get_db()
|
||||
if db:
|
||||
cursor = db.cursor()
|
||||
try:
|
||||
db.autocommit = False
|
||||
cursor.execute("""
|
||||
UPDATE radgroupreply
|
||||
SET attribute = %s, op = %s, value = %s
|
||||
WHERE id = %s
|
||||
""", (attribute, op, value, group_id))
|
||||
db.commit()
|
||||
db.autocommit = True
|
||||
cursor.close()
|
||||
return "success"
|
||||
except mysql.connector.Error as err:
|
||||
db.rollback()
|
||||
db.autocommit = True
|
||||
cursor.close()
|
||||
return str(err)
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
db.autocommit = True
|
||||
cursor.close()
|
||||
return str(e)
|
||||
finally:
|
||||
db.close()
|
||||
return "Database Connection Failed"
|
||||
|
||||
@app.route('/add_attribute', methods=['POST'])
|
||||
def add_attribute():
|
||||
groupname = request.form['groupname']
|
||||
attribute = request.form['attribute']
|
||||
op = request.form['op']
|
||||
value = request.form['value']
|
||||
|
||||
db = get_db()
|
||||
if db:
|
||||
cursor = db.cursor()
|
||||
try:
|
||||
cursor.execute("""
|
||||
INSERT INTO radgroupreply (groupname, attribute, op, value)
|
||||
VALUES (%s, %s, %s, %s)
|
||||
""", (groupname, attribute, op, value))
|
||||
db.commit()
|
||||
cursor.close()
|
||||
db.close()
|
||||
return "success"
|
||||
except mysql.connector.Error as err:
|
||||
print(f"Database Error: {err}")
|
||||
db.rollback()
|
||||
cursor.close()
|
||||
db.close()
|
||||
return str(err)
|
||||
return "Database Connection Failed"
|
||||
|
||||
@app.route('/edit_attribute/<group_id>', methods=['GET', 'POST'])
|
||||
def edit_attribute(group_id):
|
||||
db = get_db()
|
||||
if db:
|
||||
cursor = db.cursor(dictionary=True)
|
||||
|
||||
if request.method == 'POST':
|
||||
attribute = request.form['attribute']
|
||||
op = request.form['op']
|
||||
value = request.form['value']
|
||||
|
||||
try:
|
||||
db.autocommit = False
|
||||
cursor.execute("""
|
||||
UPDATE radgroupreply
|
||||
SET attribute = %s, op = %s, value = %s
|
||||
WHERE id = %s
|
||||
""", (attribute, op, value, group_id))
|
||||
db.commit()
|
||||
db.autocommit = True
|
||||
cursor.close()
|
||||
db.close()
|
||||
return redirect(url_for('groups'))
|
||||
except mysql.connector.Error as err:
|
||||
db.rollback()
|
||||
db.autocommit = True
|
||||
cursor.close()
|
||||
db.close()
|
||||
return f"Database Error: {err}"
|
||||
|
||||
else:
|
||||
cursor.execute("SELECT * FROM radgroupreply WHERE id = %s", (group_id,))
|
||||
attribute_data = cursor.fetchone()
|
||||
cursor.close()
|
||||
db.close()
|
||||
return render_template('edit_attribute.html', attribute_data=attribute_data)
|
||||
return "Database Connection Failed"
|
||||
|
||||
@app.route('/add_group', methods=['POST'])
|
||||
def add_group():
|
||||
groupname = request.form['groupname']
|
||||
|
||||
db = get_db()
|
||||
if db:
|
||||
cursor = db.cursor()
|
||||
try:
|
||||
cursor.execute("INSERT INTO radgroupreply (groupname, attribute, op, value) VALUES (%s, '', '', '')", (groupname,))
|
||||
cursor.execute("INSERT INTO radusergroup (groupname, username) VALUES (%s, '')", (groupname,))
|
||||
# Add default values for radgroupcheck
|
||||
cursor.execute("INSERT INTO radgroupcheck (groupname, attribute, op, value) VALUES (%s, 'Auth-Type', ':=', 'Accept')", (groupname,))
|
||||
db.commit()
|
||||
cursor.close()
|
||||
db.close()
|
||||
return "success"
|
||||
except mysql.connector.Error as err:
|
||||
print(f"Database Error: {err}")
|
||||
db.rollback()
|
||||
cursor.close()
|
||||
db.close()
|
||||
return str(err)
|
||||
return "Database Connection Failed"
|
||||
|
||||
@app.route('/delete_group_rows/<groupname>')
|
||||
def delete_group_rows(groupname):
|
||||
db = get_db()
|
||||
if db:
|
||||
cursor = db.cursor()
|
||||
try:
|
||||
cursor.execute("DELETE FROM radgroupreply WHERE groupname = %s", (groupname,))
|
||||
cursor.execute("DELETE FROM radusergroup WHERE groupname = %s", (groupname,))
|
||||
cursor.execute("DELETE FROM radgroupcheck WHERE groupname = %s", (groupname,))
|
||||
db.commit()
|
||||
cursor.close()
|
||||
db.close()
|
||||
return redirect(url_for('groups'))
|
||||
except mysql.connector.Error as err:
|
||||
print(f"Database Error: {err}")
|
||||
db.rollback()
|
||||
cursor.close()
|
||||
db.close()
|
||||
return redirect(url_for('groups'))
|
||||
return "Database Connection Failed"
|
||||
|
||||
@app.route('/delete_group/<int:group_id>')
|
||||
def delete_group(group_id):
|
||||
db = get_db()
|
||||
if db:
|
||||
cursor = db.cursor()
|
||||
try:
|
||||
cursor.execute("DELETE FROM radgroupreply WHERE id = %s", (group_id,))
|
||||
cursor.execute("DELETE FROM radgroupcheck WHERE id = %s", (group_id,)) # Delete from radgroupcheck
|
||||
db.commit()
|
||||
cursor.close()
|
||||
db.close()
|
||||
return redirect(url_for('groups'))
|
||||
except mysql.connector.Error as err:
|
||||
print(f"Database Error: {err}")
|
||||
db.rollback()
|
||||
cursor.close()
|
||||
db.close()
|
||||
return redirect(url_for('groups'))
|
||||
return "Database Connection Failed"
|
||||
|
||||
@app.route('/duplicate_group', methods=['POST'])
|
||||
def duplicate_group():
|
||||
groupname = request.form['groupname']
|
||||
db = get_db()
|
||||
if db:
|
||||
cursor = db.cursor()
|
||||
try:
|
||||
cursor.execute("SELECT attribute, op, value FROM radgroupreply WHERE groupname = %s", (groupname,))
|
||||
attributes = [{'attribute': row[0], 'op': row[1], 'value': row[2]} for row in cursor.fetchall()]
|
||||
cursor.close()
|
||||
db.close()
|
||||
return jsonify(attributes)
|
||||
except mysql.connector.Error as err:
|
||||
print(f"Database Error: {err}")
|
||||
cursor.close()
|
||||
db.close()
|
||||
return jsonify([])
|
||||
return jsonify([])
|
||||
|
||||
@app.route('/save_duplicated_group', methods=['POST'])
|
||||
def save_duplicated_group():
|
||||
data = json.loads(request.data)
|
||||
groupname = data['groupname']
|
||||
attributes = data['attributes']
|
||||
db = get_db()
|
||||
if db:
|
||||
cursor = db.cursor()
|
||||
try:
|
||||
cursor.execute("INSERT INTO radgroupcheck (groupname, attribute, op, value) VALUES (%s, 'Auth-Type', ':=', 'Accept')", (groupname,))
|
||||
cursor.execute("INSERT INTO radusergroup (groupname, username) VALUES (%s, '')", (groupname,))
|
||||
for attribute in attributes:
|
||||
cursor.execute("INSERT INTO radgroupreply (groupname, attribute, op, value) VALUES (%s, %s, %s, %s)", (groupname, attribute['attribute'], attribute['op'], attribute['value']))
|
||||
db.commit()
|
||||
cursor.close()
|
||||
db.close()
|
||||
return "success"
|
||||
except mysql.connector.Error as err:
|
||||
print(f"Database Error: {err}")
|
||||
db.rollback()
|
||||
cursor.close()
|
||||
db.close()
|
||||
return str(err)
|
||||
return "Database Connection Failed"
|
||||
|
||||
@app.route('/add_user', methods=['POST'])
|
||||
def add_user():
|
||||
"""Adds a new user to the database."""
|
||||
try:
|
||||
data = request.get_json() # Get the JSON data from the request
|
||||
mac_address = data.get('mac_address')
|
||||
description = data.get('description')
|
||||
vlan_id = data.get('vlan_id')
|
||||
|
||||
if not mac_address:
|
||||
return jsonify({'success': False, 'message': 'MAC Address is required'}), 400
|
||||
|
||||
db = get_db()
|
||||
if db is None:
|
||||
return jsonify({'success': False, 'message': 'Database connection failed'}), 500
|
||||
|
||||
cursor = db.cursor()
|
||||
try:
|
||||
# Check if user already exists
|
||||
cursor.execute("SELECT username FROM radcheck WHERE username = %s", (mac_address,))
|
||||
if cursor.fetchone():
|
||||
cursor.close()
|
||||
db.close()
|
||||
return jsonify({'success': False, 'message': 'User with this MAC Address already exists'}), 400
|
||||
|
||||
# Insert into radcheck, setting password to MAC address
|
||||
cursor.execute("""
|
||||
INSERT INTO radcheck (username, attribute, op, value)
|
||||
VALUES (%s, 'Cleartext-Password', ':=', %s),
|
||||
(%s, 'User-Description', ':=', %s)
|
||||
""", (mac_address, mac_address, mac_address, description)) # Use mac_address for both username and password
|
||||
|
||||
# Insert into radusergroup with the selected group
|
||||
cursor.execute("""
|
||||
INSERT INTO radusergroup (username, groupname)
|
||||
VALUES (%s, %s)
|
||||
""", (mac_address, vlan_id)) # Use vlan_id
|
||||
|
||||
db.commit()
|
||||
cursor.close()
|
||||
db.close()
|
||||
return jsonify({'success': True, 'message': 'User added successfully'})
|
||||
|
||||
except mysql.connector.Error as err:
|
||||
print(f"Database Error: {err}")
|
||||
db.rollback()
|
||||
cursor.close()
|
||||
db.close()
|
||||
return jsonify({'success': False, 'message': f"Database error: {err}"}), 500
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error adding user: {e}")
|
||||
db.rollback()
|
||||
cursor.close()
|
||||
db.close()
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
finally:
|
||||
db.close()
|
||||
except Exception as e:
|
||||
return jsonify({'success': False, 'message': 'Unknown error'}), 500
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(debug=True, host='0.0.0.0', port=8080)
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Add User</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Add User</h1>
|
||||
<form method="POST">
|
||||
<label for="mac_address">MAC Address:</label>
|
||||
<input type="text" name="mac_address" required><br><br>
|
||||
<label for="vlan_id">VLAN ID:</label>
|
||||
<input type="text" name="vlan_id" required><br><br>
|
||||
<label for="description">Description:</label>
|
||||
<input type="text" name="description"><br><br>
|
||||
<input type="submit" value="Add User">
|
||||
</form>
|
||||
<a href="{{ url_for('user_list') }}">Back to User List</a>
|
||||
</body>
|
||||
</html>
|
||||
35
app/templates/base.html
Normal file
35
app/templates/base.html
Normal file
@@ -0,0 +1,35 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>{% block title %}{% endblock %}</title>
|
||||
<style>
|
||||
nav {
|
||||
background-color: #f0f0f0;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
nav a {
|
||||
margin-right: 10px;
|
||||
text-decoration: none;
|
||||
padding: 5px 10px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
nav a.active {
|
||||
background-color: #ddd;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<nav>
|
||||
<a href="/" {% if request.path == '/' %}class="active"{% endif %}>Home</a>
|
||||
<a href="/user_list" {% if request.path == '/user_list' %}class="active"{% endif %}>User List</a>
|
||||
<a href="/groups" {% if request.path == '/groups' %}class="active"{% endif %}>Group List</a>
|
||||
</nav>
|
||||
|
||||
<div class="content">
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
22
app/templates/edit_attribute.html
Normal file
22
app/templates/edit_attribute.html
Normal file
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Edit Attribute</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Edit Attribute</h1>
|
||||
|
||||
<form method="POST">
|
||||
<label for="attribute">Attribute:</label><br>
|
||||
<input type="text" id="attribute" name="attribute" value="{{ attribute_data.attribute }}"><br><br>
|
||||
|
||||
<label for="op">Op:</label><br>
|
||||
<input type="text" id="op" name="op" value="{{ attribute_data.op }}"><br><br>
|
||||
|
||||
<label for="value">Value:</label><br>
|
||||
<input type="text" id="value" name="value" value="{{ attribute_data.value }}"><br><br>
|
||||
|
||||
<input type="submit" value="Save Changes">
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
26
app/templates/edit_group.html
Normal file
26
app/templates/edit_group.html
Normal file
@@ -0,0 +1,26 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Edit Group</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Edit Group: {{ group.id }}</h1>
|
||||
|
||||
<form method="POST">
|
||||
<label for="groupname">Group Name:</label><br>
|
||||
<input type="text" id="groupname" name="groupname" value="{{ group.groupname }}"><br><br>
|
||||
|
||||
<label for="attribute">Attribute:</label><br>
|
||||
<input type="text" id="attribute" name="attribute" value="{{ group.attribute }}"><br><br>
|
||||
|
||||
<label for="op">Op:</label><br>
|
||||
<input type="text" id="op" name="op" value="{{ group.op }}"><br><br>
|
||||
|
||||
<label for="value">Value:</label><br>
|
||||
<input type="text" id="value" name="value" value="{{ group.value }}"><br><br>
|
||||
|
||||
<input type="submit" value="Save Changes">
|
||||
</form>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
16
app/templates/edit_groupname.html
Normal file
16
app/templates/edit_groupname.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Edit Group Name</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Edit Group Name: {{ old_groupname }}</h1>
|
||||
|
||||
<form method="POST">
|
||||
<label for="groupname">New Group Name:</label><br>
|
||||
<input type="text" id="groupname" name="groupname" value="{{ old_groupname }}"><br><br>
|
||||
|
||||
<input type="submit" value="Save Changes">
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Edit User</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Edit User: {{ user.mac_address }}</h1>
|
||||
|
||||
<form method="POST">
|
||||
<label for="description">Description:</label><br>
|
||||
<input type="text" id="description" name="description" value="{{ user.description }}"><br><br>
|
||||
|
||||
<label for="vlan_id">VLAN ID:</label><br>
|
||||
<input type="text" id="vlan_id" name="vlan_id" value="{{ user.vlan_id }}"><br><br>
|
||||
|
||||
<input type="submit" value="Save Changes">
|
||||
</form>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
38
app/templates/group_list.html
Normal file
38
app/templates/group_list.html
Normal file
@@ -0,0 +1,38 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Group List</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Group List</h1>
|
||||
|
||||
<table border="1">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Group Name</th>
|
||||
<th>Attribute</th>
|
||||
<th>Op</th>
|
||||
<th>Value</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for group in results %}
|
||||
<tr>
|
||||
<td>{{ group.id }}</td>
|
||||
<td>{{ group.groupname }}</td>
|
||||
<td>{{ group.attribute }}</td>
|
||||
<td>{{ group.op }}</td>
|
||||
<td>{{ group.value }}</td>
|
||||
<td>
|
||||
<a href="/edit_group/{{ group.id }}">Edit</a> |
|
||||
<a href="/delete_group/{{ group.id }}">Delete</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
364
app/templates/group_list_nested.html
Normal file
364
app/templates/group_list_nested.html
Normal file
@@ -0,0 +1,364 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}Group List{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Group List</h1>
|
||||
|
||||
{% for groupname, attributes in grouped_results.items() %}
|
||||
<table border="1">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Group Name</th>
|
||||
<th>Attributes</th>
|
||||
<th>Op</th>
|
||||
<th>Value</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<input type="text" id="groupname-{{ groupname }}" value="{{ groupname }}">
|
||||
</td>
|
||||
<td colspan="3" class="merged-cell">
|
||||
<button onclick="addRow('{{ groupname }}')">➕</button>
|
||||
</td>
|
||||
<td>
|
||||
<button onclick="updateGroupName('{{ groupname }}')">✅ Rename Group</button>
|
||||
<button onclick="location.reload()">❌</button>
|
||||
<a href="/delete_group_rows/{{ groupname }}" onclick="saveScrollPosition()">🗑️</a>
|
||||
<button onclick="duplicateGroup('{{ groupname }}')">Duplicate</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% for attribute in attributes %}
|
||||
<tr>
|
||||
<td class="merged-cell"></td>
|
||||
<td><input type="text" id="attribute-{{ attribute.id }}" value="{{ attribute.attribute }}"></td>
|
||||
<td>
|
||||
<select id="op-{{ attribute.id }}">
|
||||
<option value="=" ${attribute.op === '=' ? 'selected' : ''}>=</option>
|
||||
<option value="!=" ${attribute.op === '!=' ? 'selected' : ''}>!=</option>
|
||||
<option value=">" ${attribute.op === '>' ? 'selected' : ''}>></option>
|
||||
<option value="<" ${attribute.op === '<' ? 'selected' : ''}><</option>
|
||||
<option value=">=" ${attribute.op === '>=' ? 'selected' : ''}>>=</option>
|
||||
<option value="<=" ${attribute.op === '<=' ? 'selected' : ''}><=</option>
|
||||
</select>
|
||||
</td>
|
||||
<td><input type="text" id="value-{{ attribute.id }}" value="{{ attribute.value }}"></td>
|
||||
<td>
|
||||
<button onclick="updateAttribute('{{ attribute.id }}')">✅</button>
|
||||
<button onclick="location.reload()">❌</button>
|
||||
<a href="/delete_group/{{ attribute.id }}" onclick="saveScrollPosition()">🗑️</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endfor %}
|
||||
|
||||
<table border="1">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Group Name</th>
|
||||
<th>Attributes</th>
|
||||
<th>Op</th>
|
||||
<th>Value</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<input type="text" id="new-groupname" value="">
|
||||
</td>
|
||||
<td colspan="3" class="merged-cell"></td>
|
||||
<td>
|
||||
<button onclick="addNewGroup()">Add New Group</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<dialog id="duplicate-dialog">
|
||||
<div id="duplicate-dialog-content"></div>
|
||||
<button id="close-dialog">❌</button>
|
||||
<button id="save-duplicated-group">Save</button>
|
||||
</dialog>
|
||||
|
||||
<style>
|
||||
.merged-cell {
|
||||
border: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
function updateAttribute(attributeId) {
|
||||
const attribute = document.getElementById(`attribute-${attributeId}`).value;
|
||||
const op = document.getElementById(`op-${attributeId}`).value;
|
||||
const value = document.getElementById(`value-${attributeId}`).value;
|
||||
|
||||
fetch('/update_attribute', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: `attributeId=${attributeId}&attribute=${attribute}&op=${op}&value=${value}`
|
||||
})
|
||||
.then(response => response.text())
|
||||
.then(data => {
|
||||
if (data === 'success') {
|
||||
location.reload();
|
||||
} else {
|
||||
alert('Error updating attribute: ' + data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateGroupName(oldGroupName) {
|
||||
const newGroupName = document.getElementById(`groupname-${oldGroupName}`).value;
|
||||
|
||||
fetch('/update_group_name', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: `oldGroupName=${oldGroupName}&newGroupName=${newGroupName}`
|
||||
})
|
||||
.then(response => response.text())
|
||||
.then(data => {
|
||||
if (data === 'success') {
|
||||
location.reload();
|
||||
} else {
|
||||
alert('Error updating group name: ' + data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function addRow(groupName) {
|
||||
const table = event.target.closest('table').querySelector('tbody');
|
||||
const newRow = table.insertRow(table.rows.length);
|
||||
|
||||
const cell1 = newRow.insertCell(0);
|
||||
const cell2 = newRow.insertCell(1);
|
||||
const cell3 = newRow.insertCell(2);
|
||||
const cell4 = newRow.insertCell(3);
|
||||
const cell5 = newRow.insertCell(4);
|
||||
|
||||
cell1.classList.add('merged-cell');
|
||||
cell2.innerHTML = '<input type="text" id="new-attribute" value="">';
|
||||
cell3.innerHTML = `
|
||||
<select id="new-op">
|
||||
<option value="=">=</option>
|
||||
<option value="!=">!=</option>
|
||||
<option value=">">></option>
|
||||
<option value="<"><</option>
|
||||
<option value=">=">>=</option>
|
||||
<option value="<="><=</option>
|
||||
</select>
|
||||
`;
|
||||
cell4.innerHTML = '<input type="text" id="new-value" value="">';
|
||||
cell5.innerHTML = '<button onclick="saveNewRow(\'' + groupName + '\', this)">✅</button> <button onclick="removeRow(this)">❌</button>';
|
||||
}
|
||||
|
||||
function saveNewRow(groupName, button) {
|
||||
const row = button.parentNode.parentNode;
|
||||
const attribute = row.querySelector('#new-attribute').value;
|
||||
const op = row.querySelector('#new-op').value;
|
||||
const value = row.querySelector('#new-value').value;
|
||||
|
||||
fetch('/add_attribute', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: `groupname=${groupName}&attribute=${attribute}&op=${op}&value=${value}`
|
||||
})
|
||||
.then(response => response.text())
|
||||
.then(data => {
|
||||
if (data === 'success') {
|
||||
location.reload();
|
||||
} else {
|
||||
alert('Error adding attribute: ' + data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function removeRow(button) {
|
||||
const row = button.parentNode.parentNode;
|
||||
row.parentNode.removeChild(row);
|
||||
}
|
||||
|
||||
function addNewGroup() {
|
||||
const newGroupName = document.getElementById('new-groupname').value;
|
||||
|
||||
fetch('/add_group', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: `groupname=${newGroupName}`
|
||||
})
|
||||
.then(response => response.text())
|
||||
.then(data => {
|
||||
if (data === 'success') {
|
||||
location.reload();
|
||||
} else {
|
||||
alert('Error adding group: ' + data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function saveScrollPosition() {
|
||||
sessionStorage.setItem('scrollPosition', window.scrollY);
|
||||
}
|
||||
|
||||
window.onload = function() {
|
||||
const scrollPosition = sessionStorage.getItem('scrollPosition');
|
||||
if (scrollPosition) {
|
||||
window.scrollTo(0, scrollPosition);
|
||||
sessionStorage.removeItem('scrollPosition');
|
||||
}
|
||||
}
|
||||
|
||||
function duplicateGroup(groupName) {
|
||||
fetch('/duplicate_group', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: `groupname=${groupName}`
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const newGroupName = 'Copy of ' + groupName;
|
||||
let newTable = `<table border="1">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Group Name</th>
|
||||
<th>Attributes</th>
|
||||
<th>Op</th>
|
||||
<th>Value</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<input type="text" id="new-groupname" value="${newGroupName}">
|
||||
</td>
|
||||
<td colspan="3" class="merged-cell"></td>
|
||||
<td></td>
|
||||
</tr>`;
|
||||
|
||||
data.forEach((attribute, index) => {
|
||||
newTable += `<tr>
|
||||
<td class="merged-cell"></td>
|
||||
<td><input type="text" class="new-attribute" value="${attribute.attribute}"></td>
|
||||
<td>
|
||||
<select class="new-op">
|
||||
<option value="=" ${attribute.op === '=' ? 'selected' : ''}>=</option>
|
||||
<option value="!=" ${attribute.op === '!=' ? 'selected' : ''}>!=</option>
|
||||
<option value=">" ${attribute.op === '>' ? 'selected' : ''}>></option>
|
||||
<option value="<" ${attribute.op === '<' ? 'selected' : ''}><</option>
|
||||
<option value=">=" ${attribute.op === '>=' ? 'selected' : ''}>>=</option>
|
||||
<option value="<=" ${attribute.op === '<=' ? 'selected' : ''}><=</option>
|
||||
</select>
|
||||
</td>
|
||||
<td><input type="text" class="new-value" value="${attribute.value}"></td>
|
||||
<td><button onclick="removeDuplicatedRow(this)">🗑️</button></td>
|
||||
</tr>`;
|
||||
});
|
||||
|
||||
newTable += `<tr>
|
||||
<td class="merged-cell"></td>
|
||||
<td colspan="3">
|
||||
<button onclick="addDuplicatedRow()">➕</button>
|
||||
</td>
|
||||
<td></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-group').addEventListener('click', () => {
|
||||
saveDuplicatedGroup();
|
||||
});
|
||||
|
||||
function saveDuplicatedGroup() {
|
||||
let rows = document.querySelectorAll('#duplicate-dialog-content table tbody tr');
|
||||
let groupname = rows[0].querySelector('#new-groupname').value;
|
||||
let attributes = [];
|
||||
for (let i = 1; i < rows.length - 1; i++) {
|
||||
const attributeInput = rows[i].querySelector(`.new-attribute`);
|
||||
const opInput = rows[i].querySelector(`.new-op`);
|
||||
const valueInput = rows[i].querySelector(`.new-value`);
|
||||
|
||||
if (attributeInput && opInput && valueInput) {
|
||||
attributes.push({
|
||||
attribute: attributeInput.value,
|
||||
op: opInput.value,
|
||||
value: valueInput.value
|
||||
});
|
||||
} else {
|
||||
console.warn(`Input elements not found for row ${i}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
fetch('/save_duplicated_group', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ groupname: groupname, attributes: attributes })
|
||||
})
|
||||
.then(response => response.text())
|
||||
.then(data => {
|
||||
if (data === 'success') {
|
||||
document.getElementById('duplicate-dialog').close();
|
||||
location.reload();
|
||||
} else {
|
||||
alert('Error saving duplicated group: ' + data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function addDuplicatedRow() {
|
||||
const table = document.querySelector('#duplicate-dialog-content table tbody');
|
||||
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);
|
||||
const cell5 = newRow.insertCell(4);
|
||||
|
||||
cell1.classList.add('merged-cell');
|
||||
cell2.innerHTML = `<input type="text" class="new-attribute" value="">`;
|
||||
cell3.innerHTML = `
|
||||
<select class="new-op">
|
||||
<option value="=">=</option>
|
||||
<option value="!=">!=</option>
|
||||
<option value=">">></option>
|
||||
<option value="<"><</option>
|
||||
<option value=">=">>=</option>
|
||||
<option value="<="><=</option>
|
||||
</select>
|
||||
`;
|
||||
cell4.innerHTML = `<input type="text" class="new-value" value="">`;
|
||||
cell5.innerHTML = `<button onclick="removeDuplicatedRow(this)">🗑️</button>`;
|
||||
}
|
||||
|
||||
function removeDuplicatedRow(button) {
|
||||
const row = button.parentNode.parentNode;
|
||||
row.parentNode.removeChild(row);
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
43
app/templates/index.html
Normal file
43
app/templates/index.html
Normal file
@@ -0,0 +1,43 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}FreeRADIUS Manager{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>FreeRADIUS Manager</h1>
|
||||
|
||||
<h2>Statistics:</h2>
|
||||
<p>Total Users: {{ total_users }}</p>
|
||||
<p>Total Groups: {{ total_groups }}</p>
|
||||
|
||||
<h2>SQL Query Tool:</h2>
|
||||
<form method="POST" action="/sql">
|
||||
<textarea name="query" rows="5" cols="50"></textarea><br>
|
||||
<button type="submit">Execute Query</button>
|
||||
</form>
|
||||
|
||||
{% if sql_results %}
|
||||
<h2>Query Results:</h2>
|
||||
<table border="1">
|
||||
<thead>
|
||||
<tr>
|
||||
{% for key in sql_results[0].keys() %}
|
||||
<th>{{ key }}</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for row in sql_results %}
|
||||
<tr>
|
||||
{% for value in row.values() %}
|
||||
<td>{{ value }}</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
|
||||
{% if sql_error %}
|
||||
<p style="color: red;">{{ sql_error }}</p>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,364 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}User List{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>User List</h1>
|
||||
|
||||
{% for username, attributes in grouped_users.items() %}
|
||||
<table border="1">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>User Name</th>
|
||||
<th>Attributes</th>
|
||||
<th>Op</th>
|
||||
<th>Value</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<input type="text" id="username-{{ username }}" value="{{ username }}">
|
||||
</td>
|
||||
<td colspan="3" class="merged-cell">
|
||||
<button onclick="addUserRow('{{ username }}')">➕</button>
|
||||
</td>
|
||||
<td>
|
||||
<button onclick="updateUserName('{{ username }}')">✅ Rename User</button>
|
||||
<button onclick="location.reload()">❌</button>
|
||||
<a href="/delete_user_rows/{{ username }}" onclick="saveScrollPosition()">🗑️</a>
|
||||
<button onclick="duplicateUser('{{ username }}')">Duplicate</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% for attribute in attributes %}
|
||||
<tr>
|
||||
<td class="merged-cell"></td>
|
||||
<td><input type="text" id="attribute-{{ attribute.id }}" value="{{ attribute.attribute }}"></td>
|
||||
<td>
|
||||
<select id="op-{{ attribute.id }}">
|
||||
<option value="=" ${attribute.op === '=' ? 'selected' : ''}>=</option>
|
||||
<option value="!=" ${attribute.op === '!=' ? 'selected' : ''}>!=</option>
|
||||
<option value=">" ${attribute.op === '>' ? 'selected' : ''}>></option>
|
||||
<option value="<" ${attribute.op === '<' ? 'selected' : ''}><</option>
|
||||
<option value=">=" ${attribute.op === '>=' ? 'selected' : ''}>>=</option>
|
||||
<option value="<=" ${attribute.op === '<=' ? 'selected' : ''}><=</option>
|
||||
</select>
|
||||
</td>
|
||||
<td><input type="text" id="value-{{ attribute.id }}" value="{{ attribute.value }}"></td>
|
||||
<td>
|
||||
<button onclick="updateAttribute('{{ attribute.id }}')">✅</button>
|
||||
<button onclick="location.reload()">❌</button>
|
||||
<a href="/delete_user/{{ attribute.id }}" onclick="saveScrollPosition()">🗑️</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endfor %}
|
||||
|
||||
<table border="1">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>User Name</th>
|
||||
<th>Attributes</th>
|
||||
<th>Op</th>
|
||||
<th>Value</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<input type="text" id="new-username" value="">
|
||||
</td>
|
||||
<td colspan="3" class="merged-cell"></td>
|
||||
<td>
|
||||
<button onclick="addNewUser()">Add New User</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<dialog id="duplicate-dialog">
|
||||
<div id="duplicate-dialog-content"></div>
|
||||
<button id="close-dialog">❌</button>
|
||||
<button id="save-duplicated-user">Save</button>
|
||||
</dialog>
|
||||
|
||||
<style>
|
||||
.merged-cell {
|
||||
border: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
function updateAttribute(attributeId) {
|
||||
const attribute = document.getElementById(`attribute-${attributeId}`).value;
|
||||
const op = document.getElementById(`op-${attributeId}`).value;
|
||||
const value = document.getElementById(`value-${attributeId}`).value;
|
||||
|
||||
fetch('/update_user_attribute', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: `attributeId=${attributeId}&attribute=${attribute}&op=${op}&value=${value}`
|
||||
})
|
||||
.then(response => response.text())
|
||||
.then(data => {
|
||||
if (data === 'success') {
|
||||
location.reload();
|
||||
} else {
|
||||
alert('Error updating attribute: ' + data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateUserName(oldUserName) {
|
||||
const newUserName = document.getElementById(`username-${oldUserName}`).value;
|
||||
|
||||
fetch('/update_user_name', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: `oldUserName=${oldUserName}&newUserName=${newUserName}`
|
||||
})
|
||||
.then(response => response.text())
|
||||
.then(data => {
|
||||
if (data === 'success') {
|
||||
location.reload();
|
||||
} else {
|
||||
alert('Error updating user name: ' + data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function addUserRow(userName) {
|
||||
const table = event.target.closest('table').querySelector('tbody');
|
||||
const newRow = table.insertRow(table.rows.length);
|
||||
|
||||
const cell1 = newRow.insertCell(0);
|
||||
const cell2 = newRow.insertCell(1);
|
||||
const cell3 = newRow.insertCell(2);
|
||||
const cell4 = newRow.insertCell(3);
|
||||
const cell5 = newRow.insertCell(4);
|
||||
|
||||
cell1.classList.add('merged-cell');
|
||||
cell2.innerHTML = '<input type="text" id="new-attribute" value="">';
|
||||
cell3.innerHTML = `
|
||||
<select id="new-op">
|
||||
<option value="=">=</option>
|
||||
<option value="!=">!=</option>
|
||||
<option value=">">></option>
|
||||
<option value="<"><</option>
|
||||
<option value=">=">>=</option>
|
||||
<option value="<="><=</option>
|
||||
</select>
|
||||
`;
|
||||
cell4.innerHTML = '<input type="text" id="new-value" value="">';
|
||||
cell5.innerHTML = '<button onclick="saveNewUserRow(\'' + userName + '\', this)">✅</button> <button onclick="removeUserRow(this)">❌</button>';
|
||||
}
|
||||
|
||||
function saveNewUserRow(userName, button) {
|
||||
const row = button.parentNode.parentNode;
|
||||
const attribute = row.querySelector('#new-attribute').value;
|
||||
const op = row.querySelector('#new-op').value;
|
||||
const value = row.querySelector('#new-value').value;
|
||||
|
||||
fetch('/add_user_attribute', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: `username=${userName}&attribute=${attribute}&op=${op}&value=${value}`
|
||||
})
|
||||
.then(response => response.text())
|
||||
.then(data => {
|
||||
if (data === 'success') {
|
||||
location.reload();
|
||||
} else {
|
||||
alert('Error adding attribute: ' + data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function removeUserRow(button) {
|
||||
const row = button.parentNode.parentNode;
|
||||
row.parentNode.removeChild(row);
|
||||
}
|
||||
|
||||
function addNewUser() {
|
||||
const newUserName = document.getElementById('new-username').value;
|
||||
|
||||
fetch('/add_user', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: `username=${newUserName}`
|
||||
})
|
||||
.then(response => response.text())
|
||||
.then(data => {
|
||||
if (data === 'success') {
|
||||
location.reload();
|
||||
} else {
|
||||
alert('Error adding user: ' + data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function saveScrollPosition() {
|
||||
sessionStorage.setItem('scrollPosition', window.scrollY);
|
||||
}
|
||||
|
||||
window.onload = function() {
|
||||
const scrollPosition = sessionStorage.getItem('scrollPosition');
|
||||
if (scrollPosition) {
|
||||
window.scrollTo(0, scrollPosition);
|
||||
sessionStorage.removeItem('scrollPosition');
|
||||
}
|
||||
}
|
||||
|
||||
function duplicateUser(userName) {
|
||||
fetch('/duplicate_user', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: `username=${userName}`
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const newUserName = 'Copy of ' + userName;
|
||||
let newTable = `<table border="1">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>User Name</th>
|
||||
<th>Attributes</th>
|
||||
<th>Op</th>
|
||||
<th>Value</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<input type="text" id="new-username" value="${newUserName}">
|
||||
</td>
|
||||
<td colspan="3" class="merged-cell"></td>
|
||||
<td></td>
|
||||
</tr>`;
|
||||
|
||||
data.forEach((attribute, index) => {
|
||||
newTable += `<tr>
|
||||
<td class="merged-cell"></td>
|
||||
<td><input type="text" class="new-attribute" value="${attribute.attribute}"></td>
|
||||
<td>
|
||||
<select class="new-op">
|
||||
<option value="=" ${attribute.op === '=' ? 'selected' : ''}>=</option>
|
||||
<option value="!=" ${attribute.op === '!=' ? 'selected' : ''}>!=</option>
|
||||
<option value=">" ${attribute.op === '>' ? 'selected' : ''}>></option>
|
||||
<option value="<" ${attribute.op === '<' ? 'selected' : ''}><</option>
|
||||
<option value=">=" ${attribute.op === '>=' ? 'selected' : ''}>>=</option>
|
||||
<option value="<=" ${attribute.op === '<=' ? 'selected' : ''}><=</option>
|
||||
</select>
|
||||
</td>
|
||||
<td><input type="text" class="new-value" value="${attribute.value}"></td>
|
||||
<td><button onclick="removeDuplicatedUserRow(this)">🗑️</button></td>
|
||||
</tr>`;
|
||||
});
|
||||
|
||||
newTable += `<tr>
|
||||
<td class="merged-cell"></td>
|
||||
<td colspan="3">
|
||||
<button onclick="addDuplicatedUserRow()">➕</button>
|
||||
</td>
|
||||
<td></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 username = rows[0].querySelector('#new-username').value;
|
||||
let attributes = [];
|
||||
for (let i = 1; i < rows.length - 1; i++) {
|
||||
const attributeInput = rows[i].querySelector(`.new-attribute`);
|
||||
const opInput = rows[i].querySelector(`.new-op`);
|
||||
const valueInput = rows[i].querySelector(`.new-value`);
|
||||
|
||||
if (attributeInput && opInput && valueInput) {
|
||||
attributes.push({
|
||||
attribute: attributeInput.value,
|
||||
op: opInput.value,
|
||||
value: valueInput.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({ username: username, 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() {
|
||||
const table = document.querySelector('#duplicate-dialog-content table tbody');
|
||||
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);
|
||||
const cell5 = newRow.insertCell(4);
|
||||
|
||||
cell1.classList.add('merged-cell');
|
||||
cell2.innerHTML = `<input type="text" class="new-attribute" value="">`;
|
||||
cell3.innerHTML = `
|
||||
<select class="new-op">
|
||||
<option value="=">=</option>
|
||||
<option value="!=">!=</option>
|
||||
<option value=">">></option>
|
||||
<option value="<"><</option>
|
||||
<option value=">=">>=</option>
|
||||
<option value="<="><=</option>
|
||||
</select>
|
||||
`;
|
||||
cell4.innerHTML = `<input type="text" class="new-value" value="">`;
|
||||
cell5.innerHTML = `<button onclick="removeDuplicatedUserRow(this)">🗑️</button>`;
|
||||
}
|
||||
|
||||
function removeDuplicatedUserRow(button) {
|
||||
const row = button.parentNode.parentNode;
|
||||
row.parentNode.removeChild(row);
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
316
app/templates/user_list_inline_edit.html
Normal file
316
app/templates/user_list_inline_edit.html
Normal file
@@ -0,0 +1,316 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}User List{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>User List</h1>
|
||||
|
||||
<table border="1">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>MAC Address</th>
|
||||
<th>Description</th>
|
||||
<th>VLAN ID</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for user in results %}
|
||||
<tr>
|
||||
<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="vlan_id-{{ user.mac_address }}" value="{{ user.vlan_id }}"></td>
|
||||
<td>
|
||||
<button onclick="updateUser('{{ user.mac_address }}')">✅</button>
|
||||
<button onclick="location.reload()">❌</button>
|
||||
<a href="/delete_user/{{ user.mac_address }}" onclick="saveScrollPosition()">🗑️</a>
|
||||
<button onclick="duplicateUser('{{ user.mac_address }}')">Duplicate</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
<tr>
|
||||
<td colspan="4">
|
||||
<button onclick="addNewUserRow()">➕ Add User</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<dialog id="add-user-dialog">
|
||||
<div id="add-user-dialog-content">
|
||||
<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"></td>
|
||||
<td><input type="text" id="new-description"></td>
|
||||
<td><input type="text" id="new-vlan_id"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: flex-end; margin-top: 10px;">
|
||||
<button id="cancel-add-user-dialog">Cancel</button>
|
||||
<button id="save-new-user">Save</button>
|
||||
</div>
|
||||
</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>
|
||||
.merged-cell {
|
||||
border: none;
|
||||
}
|
||||
|
||||
#cancel-add-user-dialog {
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
background-color: #f44336;
|
||||
color: white;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
#cancel-add-user-dialog:hover {
|
||||
background-color: #d32f2f;
|
||||
}
|
||||
|
||||
#save-new-user {
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#save-new-user:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
|
||||
#add-user-dialog-content + div {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
function updateUser(mac_address) {
|
||||
const description = document.getElementById('description-' + mac_address).value;
|
||||
const vlan_id = document.getElementById('vlan_id-' + mac_address).value;
|
||||
const mac_address_input = document.getElementById('mac_address-' + mac_address).value; //added
|
||||
|
||||
fetch('/update_user', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: `mac_address=${mac_address}&description=${description}&vlan_id=${vlan_id}&new_mac_address=${mac_address_input}` //added new param
|
||||
})
|
||||
.then(response => response.text())
|
||||
.then(data => {
|
||||
if (data === 'success') {
|
||||
location.reload();
|
||||
} else {
|
||||
alert('Error updating user: ' + data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function saveScrollPosition() {
|
||||
sessionStorage.setItem('scrollPosition', window.scrollY);
|
||||
}
|
||||
|
||||
window.onload = function () {
|
||||
const scrollPosition = sessionStorage.getItem('scrollPosition');
|
||||
if (scrollPosition) {
|
||||
window.scrollTo(0, scrollPosition);
|
||||
sessionStorage.removeItem('scrollPosition');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
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><input type="text" class="new-vlan_id" value="${userData.vlan_id}"></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; //changed
|
||||
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 }) //changed
|
||||
})
|
||||
.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; //get the table
|
||||
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 = `<input type="text" class="new-vlan_id" value="">`;
|
||||
cell4.innerHTML = `<button onclick="removeDuplicatedUserRow(this)">🗑️</button>`;
|
||||
}
|
||||
|
||||
function removeDuplicatedUserRow(button) {
|
||||
const row = button.parentNode.parentNode;
|
||||
row.parentNode.removeChild(row);
|
||||
}
|
||||
|
||||
function addNewUserRow() {
|
||||
document.getElementById('add-user-dialog').showModal();
|
||||
}
|
||||
|
||||
document.getElementById('close-add-user-dialog').addEventListener('click', () => {
|
||||
document.getElementById('add-user-dialog').close();
|
||||
});
|
||||
|
||||
document.getElementById('save-new-user').addEventListener('click', () => {
|
||||
saveNewUser();
|
||||
});
|
||||
|
||||
function saveNewUser() {
|
||||
const mac = document.getElementById('new-mac').value;
|
||||
const description = document.getElementById('new-description').value;
|
||||
const vlan_id = document.getElementById('new-vlan_id').value;
|
||||
|
||||
if (!mac) {
|
||||
alert('MAC Address cannot be empty.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Construct the data as an object
|
||||
const userData = {
|
||||
mac_address: mac,
|
||||
description: description,
|
||||
vlan_id: vlan_id
|
||||
};
|
||||
|
||||
fetch('/add_user', { // Make sure this URL is correct
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json', // Set the content type to JSON
|
||||
},
|
||||
body: JSON.stringify(userData), // Send the data as a JSON string
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
// Handle HTTP errors (e.g., 400, 500)
|
||||
return response.text().then(text => {
|
||||
throw new Error(`HTTP error! status: ${response.status}, body: ${text}`);
|
||||
});
|
||||
}
|
||||
return response.json(); // Expect JSON response from server
|
||||
})
|
||||
.then(data => {
|
||||
console.log("Server response:", data);
|
||||
if (data && data.success) { // Check for success property in JSON response
|
||||
document.getElementById('add-user-dialog').close();
|
||||
location.reload();
|
||||
} else {
|
||||
alert('Error adding user: ' + (data && data.message ? data.message : 'Unknown error')); // Show error from server or a generic message
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Fetch error:', error); // Log the error for debugging
|
||||
alert('Error adding user: ' + error.message); // Show a user-friendly error message
|
||||
});
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user