lots of changes

This commit is contained in:
2025-03-28 16:13:38 -04:00
parent af1f384383
commit 396fd2f3b4
21 changed files with 1831 additions and 16 deletions

View File

@@ -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
View 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>

View 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>

View 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>

View 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>

View File

@@ -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>

View 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>

View 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
View 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 %}

View File

@@ -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 %}

View 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 %}