mirror of
https://github.com/caiwx86/small-packages.git
synced 2026-07-29 18:01:55 +08:00
769 lines
27 KiB
HTML
769 lines
27 KiB
HTML
<link rel="stylesheet" href="<%=resource%>/css/common.css">
|
|
<style>
|
|
/* 固定按钮样式,不受主题影响 - 蓝色背景,白色文字 */
|
|
.cbi-button.cbi-button-add {
|
|
background-color: #1a73e8 !important;
|
|
color: #ffffff !important;
|
|
border: none !important;
|
|
padding: 6px 12px !important;
|
|
border-radius: 4px !important;
|
|
cursor: pointer !important;
|
|
font-size: 13px !important;
|
|
font-weight: 500 !important;
|
|
transition: background-color 0.2s ease !important;
|
|
}
|
|
.cbi-button.cbi-button-add:hover {
|
|
background-color: #1557b0 !important;
|
|
color: #ffffff !important;
|
|
}
|
|
.cbi-button.cbi-button-add:active {
|
|
background-color: #0d47a1 !important;
|
|
color: #ffffff !important;
|
|
}
|
|
|
|
/* 页面卡片边框 */
|
|
.cbi-section.cbi-tblsection {
|
|
border: 1px solid var(--border-color-low, #e5e7eb);
|
|
border-radius: 8px;
|
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
|
margin-top: 20px;
|
|
padding: 4px;
|
|
overflow-x: auto;
|
|
}
|
|
.cbi-section.cbi-tblsection > div {
|
|
min-width: 600px;
|
|
}
|
|
|
|
/* 确保表格在手机端也保持正常表格布局 */
|
|
.cbi-section-table .tr {
|
|
display: table-row !important;
|
|
}
|
|
.cbi-section-table .th,
|
|
.cbi-section-table .td {
|
|
display: table-cell !important;
|
|
}
|
|
.cbi-section-table thead {
|
|
display: table-header-group !important;
|
|
}
|
|
.cbi-section-table tbody {
|
|
display: table-row-group !important;
|
|
}
|
|
|
|
/* Table styles - 表格美化 */
|
|
.cbi-section-table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
margin: 15px 0;
|
|
border: none;
|
|
display: table;
|
|
}
|
|
|
|
.cbi-section-table th, .cbi-section-table td {
|
|
padding: 10px 16px;
|
|
text-align: left;
|
|
border-bottom: 1px solid var(--border-color-low, #e5e7eb);
|
|
}
|
|
|
|
.cbi-section-table th {
|
|
background: var(--background-color-low, #f5f5f5);
|
|
color: var(--text-color-medium, #6b7280);
|
|
font-weight: 600;
|
|
font-size: 12.5px;
|
|
letter-spacing: 0.2px;
|
|
border-bottom: 1px solid var(--border-color-medium, #d1d5db);
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.cbi-section-table tr:hover td {
|
|
background-color: var(--background-color-low, #f5f5f5);
|
|
}
|
|
|
|
.cbi-section-table tr:last-child td {
|
|
border-bottom: none;
|
|
}
|
|
|
|
/* 单元格内容样式 */
|
|
.cbi-section-table td {
|
|
font-size: 13px;
|
|
vertical-align: middle;
|
|
line-height: 1.5;
|
|
}
|
|
|
|
.mode-selection {
|
|
margin-bottom: 20px;
|
|
margin-top: 10px;
|
|
display: flex !important;
|
|
align-items: center !important;
|
|
gap: 16px;
|
|
}
|
|
.mode-selection label {
|
|
display: inline-flex !important;
|
|
align-items: center !important;
|
|
gap: 6px;
|
|
cursor: pointer;
|
|
font-size: 14px;
|
|
line-height: 1.4;
|
|
margin: 0 !important;
|
|
padding: 0 !important;
|
|
}
|
|
.mode-selection label input[type="radio"] {
|
|
margin: 0 !important;
|
|
vertical-align: middle !important;
|
|
flex-shrink: 0;
|
|
position: relative !important;
|
|
top: 0 !important;
|
|
transform: none !important;
|
|
}
|
|
.mode-selection label span,
|
|
.mode-selection label {
|
|
vertical-align: middle !important;
|
|
}
|
|
|
|
/* 按钮样式优化 */
|
|
.delete-button {
|
|
padding: 5px 12px;
|
|
color: #d93025;
|
|
border: 1px solid var(--border-color-medium, #d1d5db);
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
font-size: 12px;
|
|
font-weight: 500;
|
|
transition: all 0.2s;
|
|
background: transparent;
|
|
}
|
|
|
|
.delete-button:hover {
|
|
background-color: #fce8e6;
|
|
border-color: #d93025;
|
|
color: #c5221f;
|
|
}
|
|
|
|
/* 表格内操作按钮 */
|
|
.cbi-section-table .cbi-button,
|
|
.cbi-section-table .cbi-button-add {
|
|
padding: 5px 12px;
|
|
font-size: 12px;
|
|
border-radius: 4px;
|
|
border: 1px solid var(--border-color-medium, #d1d5db);
|
|
background: transparent;
|
|
color: #1a73e8;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.cbi-section-table .cbi-button:hover,
|
|
.cbi-section-table .cbi-button-add:hover {
|
|
background-color: #e8f0fe;
|
|
border-color: #1a73e8;
|
|
}
|
|
|
|
/* 独立的添加按钮 - 蓝底白字 */
|
|
.cbi-section-create .cbi-button-add,
|
|
.cbi-section-create input.cbi-button-add {
|
|
padding: 8px 16px !important;
|
|
background-color: #1a73e8 !important;
|
|
color: white !important;
|
|
border: none !important;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
transition: background-color 0.2s;
|
|
}
|
|
|
|
.cbi-section-create .cbi-button-add:hover,
|
|
.cbi-section-create input.cbi-button-add:hover {
|
|
background-color: #1557b0 !important;
|
|
}
|
|
|
|
/* Modal styles - 与 user_status 完全一致 */
|
|
.oaf-modal-overlay {
|
|
display: none;
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background-color: rgba(0, 0, 0, 0.45);
|
|
backdrop-filter: blur(3px);
|
|
z-index: 1000;
|
|
justify-content: center;
|
|
align-items: center;
|
|
}
|
|
|
|
.oaf-modal-box {
|
|
background-color: var(--background-color-high, #fff);
|
|
padding: 28px;
|
|
border-radius: 12px;
|
|
text-align: left;
|
|
display: flex;
|
|
flex-direction: column;
|
|
position: relative;
|
|
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.18);
|
|
width: 450px;
|
|
max-width: 90%;
|
|
}
|
|
|
|
.user-selection {
|
|
max-height: 300px;
|
|
overflow-y: auto;
|
|
margin: 15px 0;
|
|
border: 1px solid var(--border-color-low, #e5e7eb);
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.user-item {
|
|
padding: 12px;
|
|
border-bottom: 1px solid var(--border-color-low, #e5e7eb);
|
|
display: flex;
|
|
align-items: center;
|
|
transition: background-color 0.2s;
|
|
}
|
|
|
|
.user-item:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.user-item:hover {
|
|
background-color: var(--background-color-low, #f5f5f5);
|
|
}
|
|
|
|
h3 {
|
|
font-weight: 600;
|
|
font-size: 16px;
|
|
}
|
|
|
|
/* Keyframes */
|
|
@keyframes fadeIn {
|
|
from { opacity: 0; }
|
|
to { opacity: 1; }
|
|
}
|
|
|
|
@keyframes slideUp {
|
|
from { transform: translateY(20px); opacity: 0; }
|
|
to { transform: translateY(0); opacity: 1; }
|
|
}
|
|
</style>
|
|
|
|
<script type="text/javascript">//<![CDATA[
|
|
let userData = {
|
|
users: [],
|
|
mode: 1
|
|
};
|
|
|
|
let whitelistData = {
|
|
users: []
|
|
};
|
|
|
|
let allUsers = [];
|
|
function initData() {
|
|
userData.list = [];
|
|
getAppFilterUserData();
|
|
getAllUsersData();
|
|
getWhitelistData();
|
|
}
|
|
|
|
|
|
function getAllUsersData() {
|
|
new XHR().get('<%=url('admin/network/get_all_users')%>', {flag: 2, page: 0},
|
|
function (x, data) {
|
|
if (Array.isArray(data.data.list))
|
|
allUsers = data.data.list;
|
|
renderAutoUserData();
|
|
}
|
|
);
|
|
}
|
|
|
|
|
|
function getAppFilterUserData() {
|
|
new XHR().get('<%=url('admin/network/get_app_filter_user')%>', null,
|
|
function (x, data) {
|
|
|
|
userData = data.data;
|
|
if (!Array.isArray(userData.list))
|
|
userData.list=[]
|
|
renderUserData();
|
|
render_mode_description(userData.mode);
|
|
}
|
|
);
|
|
}
|
|
|
|
|
|
function renderUserData() {
|
|
const table = document.getElementById('user_table');
|
|
let tbody = table.querySelector('tbody');
|
|
if (!tbody) {
|
|
tbody = document.createElement('tbody');
|
|
table.appendChild(tbody);
|
|
}
|
|
tbody.innerHTML = '';
|
|
|
|
|
|
const modeRadios = document.querySelectorAll('input[name="mode"]');
|
|
modeRadios.forEach(radio => {
|
|
radio.checked = parseInt(radio.value) === userData.mode;
|
|
});
|
|
if (userData.mode === 1) {
|
|
document.getElementById('user_table').style.display = 'table';
|
|
document.getElementById('auto_user_section').style.display = 'none';
|
|
document.getElementById('whitelist_section').style.display = 'none';
|
|
} else {
|
|
document.getElementById('user_table').style.display = 'none';
|
|
document.getElementById('auto_user_section').style.display = 'block';
|
|
document.getElementById('whitelist_section').style.display = 'block';
|
|
}
|
|
|
|
// Hide add button and actions column header in manual mode
|
|
const manualAddButton = document.getElementById('manualAddUserBtn');
|
|
const whitelistAddButton = document.getElementById('whitelistAddUserBtn');
|
|
const actionsHeader = document.querySelector('#user_table .th:last-child');
|
|
|
|
if (manualAddButton) {
|
|
manualAddButton.style.display = userData.mode === 1 ? 'inline-block' : 'none';
|
|
}
|
|
if (whitelistAddButton) {
|
|
whitelistAddButton.style.display = userData.mode === 0 ? 'inline-block' : 'none';
|
|
}
|
|
if (actionsHeader) {
|
|
actionsHeader.style.display = userData.mode === 1 ? '' : 'none';
|
|
}
|
|
|
|
const displayList = userData.mode === 1 ? userData.list : allUsers;
|
|
|
|
let html = '';
|
|
displayList.forEach((user, index) => {
|
|
const hostname = user.hostname || '--';
|
|
const nickname = user.nickname || '--';
|
|
html += `<tr class="tr">
|
|
<td class="td">${user.mac}</td>
|
|
<td class="td">${hostname}</td>
|
|
<td class="td">${nickname}</td>
|
|
<td class="td" style="${userData.mode === 1 ? '' : 'display: none;'}">
|
|
<div style="display: flex; white-space: nowrap;">
|
|
${userData.mode === 1 ? `<button type="button" class="delete-button" onclick="removeUser(${index})"><%:Delete%></button>` : ''}
|
|
</div>
|
|
</td>
|
|
</tr>`;
|
|
});
|
|
tbody.innerHTML = html;
|
|
}
|
|
|
|
|
|
function renderAutoUserData() {
|
|
const table = document.getElementById('auto_user_table');
|
|
let tbody = table.querySelector('tbody');
|
|
if (!tbody) {
|
|
tbody = document.createElement('tbody');
|
|
table.appendChild(tbody);
|
|
}
|
|
tbody.innerHTML = '';
|
|
|
|
// Get whitelist MAC addresses
|
|
const whitelistMacs = whitelistData.users.map(user => user.mac || user);
|
|
|
|
// Filter out users already in whitelist and users with is_whitelist flag
|
|
const displayList = allUsers.filter(user => {
|
|
return user.is_whitelist !== 1 && !whitelistMacs.includes(user.mac);
|
|
});
|
|
|
|
if (displayList.length === 0) {
|
|
|
|
document.getElementById('no_users_message').style.display = 'block';
|
|
} else {
|
|
|
|
document.getElementById('no_users_message').style.display = 'none';
|
|
|
|
|
|
let html = '';
|
|
displayList.forEach((user, index) => {
|
|
const hostname = user.hostname || '--';
|
|
const nickname = user.nickname || '--';
|
|
|
|
html += `<tr class="tr">
|
|
<td class="td">${user.mac}</td>
|
|
<td class="td">${hostname}</td>
|
|
<td class="td">${nickname}</td>
|
|
<td class="td">
|
|
<div style="display: flex; white-space: nowrap;">
|
|
<button type="button" class="cbi-button cbi-button-add" onclick="addUserToWhitelist('${user.mac}')"><%:Add to Whitelist%></button>
|
|
</div>
|
|
</td>
|
|
</tr>`;
|
|
});
|
|
tbody.innerHTML = html;
|
|
}
|
|
}
|
|
|
|
function addUserToWhitelist(mac) {
|
|
// Check if user is already in whitelist
|
|
const whitelistMacs = whitelistData.users.map(user => user.mac || user);
|
|
if (whitelistMacs.includes(mac)) {
|
|
showSuccessMessage('<%:User already in whitelist%>');
|
|
return;
|
|
}
|
|
|
|
const data = {
|
|
mac_list: [mac]
|
|
};
|
|
const data_str = JSON.stringify(data);
|
|
|
|
new XHR().post('<%=url('admin/network/add_whitelist_user')%>',
|
|
{ data: data_str },
|
|
function(x, data) {
|
|
setTimeout(() => {
|
|
getWhitelistData();
|
|
getAllUsersData();
|
|
renderAutoUserData();
|
|
}, 500);
|
|
showSuccessMessage('<%:Add to whitelist successful%>');
|
|
}
|
|
);
|
|
}
|
|
|
|
function showSuccessMessage(message = '<%:Settings saved successfully%>') {
|
|
const modal = document.getElementById('modal');
|
|
const messageElement = modal.querySelector('p');
|
|
messageElement.textContent = message;
|
|
modal.style.display = 'flex';
|
|
setTimeout(() => {
|
|
modal.style.display = 'none';
|
|
}, 1000);
|
|
}
|
|
|
|
function removeUser(index) {
|
|
const userToRemove = userData.list[index];
|
|
new XHR().post('<%=url('admin/network/del_app_filter_user')%>',
|
|
{ mac: userToRemove.mac },
|
|
function(x, data) {
|
|
getAppFilterUserData();
|
|
showSuccessMessage('<%:Delete successful%>');
|
|
}
|
|
);
|
|
}
|
|
|
|
|
|
function showUserModal() {
|
|
console.log("showUserModal");
|
|
const modal = document.getElementById('userModal');
|
|
modal.style.display = 'flex';
|
|
|
|
const userList = document.getElementById('userList');
|
|
userList.innerHTML = '';
|
|
|
|
allUsers.forEach(user => {
|
|
const userElement = document.createElement('div');
|
|
userElement.className = 'user-item';
|
|
|
|
// Determine the display name
|
|
const displayName = user.nickname || user.hostname || '';
|
|
|
|
userElement.innerHTML = `
|
|
<label style="display: flex; align-items: center;">
|
|
<input type="checkbox" value="${user.mac}" style="margin-right: 8px;">
|
|
<span>${user.mac}</span>
|
|
<span style="opacity: 0.6; margin-left: 8px;">${displayName ? `(${displayName})` : ''}</span>
|
|
</label>
|
|
`;
|
|
userList.appendChild(userElement);
|
|
});
|
|
}
|
|
|
|
function closeUserModal() {
|
|
const modal = document.getElementById('userModal');
|
|
modal.style.display = 'none';
|
|
}
|
|
|
|
function addSelectedUsers() {
|
|
const userList = document.getElementById('userList');
|
|
const checkboxes = userList.querySelectorAll('input[type="checkbox"]:checked');
|
|
|
|
const selectedMacs = Array.from(checkboxes).map(checkbox => checkbox.value);
|
|
|
|
var data = {
|
|
mac_list: selectedMacs
|
|
}
|
|
var data_str = JSON.stringify(data);
|
|
|
|
console.log(selectedMacs);
|
|
new XHR().post('<%=url('admin/network/add_app_filter_user')%>',
|
|
{ data: data_str },
|
|
function(x, data) {
|
|
getAppFilterUserData();
|
|
closeUserModal();
|
|
showSuccessMessage('<%:Add successful%>');
|
|
}
|
|
);
|
|
}
|
|
|
|
function render_mode_description(newMode) {
|
|
const modeDescription = document.getElementById('modeDescription');
|
|
if (newMode === 0) {
|
|
modeDescription.textContent = '<%:In automatic mode, all newly joined terminals will be controlled, and random MAC addresses on mobile phones are also applicable%>';
|
|
} else {
|
|
modeDescription.textContent = '<%:In manual mode, only specified terminals are controlled%>';
|
|
}
|
|
}
|
|
|
|
function switchMode(newMode) {
|
|
userData.mode = newMode;
|
|
renderUserData();
|
|
render_mode_description(newMode);
|
|
|
|
new XHR().post('<%=url('admin/network/set_app_filter_user')%>',
|
|
{ mode: newMode },
|
|
function(x, data) {
|
|
console.log('Mode updated successfully');
|
|
showSuccessMessage('<%:Mode switch successful%>');
|
|
});
|
|
}
|
|
|
|
function getWhitelistData() {
|
|
new XHR().get('<%=url('admin/network/get_whitelist_user')%>', null,
|
|
function (x, data) {
|
|
if (data.data && Array.isArray(data.data.list)) {
|
|
whitelistData.users = data.data.list;
|
|
} else {
|
|
whitelistData.users = [];
|
|
}
|
|
renderWhitelistData();
|
|
}
|
|
);
|
|
}
|
|
|
|
function renderWhitelistData() {
|
|
const table = document.getElementById('whitelist_table');
|
|
if (!table) return;
|
|
|
|
let tbody = table.querySelector('tbody');
|
|
if (!tbody) {
|
|
tbody = document.createElement('tbody');
|
|
table.appendChild(tbody);
|
|
}
|
|
tbody.innerHTML = '';
|
|
|
|
let html = '';
|
|
whitelistData.users.forEach((user, index) => {
|
|
const hostname = user.hostname || '--';
|
|
const nickname = user.nickname || '--';
|
|
html += `<tr class="tr">
|
|
<td class="td">${user.mac}</td>
|
|
<td class="td">${hostname}</td>
|
|
<td class="td">${nickname}</td>
|
|
<td class="td">
|
|
<div style="display: flex; white-space: nowrap;">
|
|
<button type="button" class="delete-button" onclick="removeWhitelistUser(${index})"><%:Delete%></button>
|
|
</div>
|
|
</td>
|
|
</tr>`;
|
|
});
|
|
tbody.innerHTML = html;
|
|
}
|
|
|
|
function showWhitelistModal() {
|
|
console.log("showWhitelistModal");
|
|
const modal = document.getElementById('whitelistModal');
|
|
modal.style.display = 'flex';
|
|
|
|
const userList = document.getElementById('whitelistUserList');
|
|
userList.innerHTML = '';
|
|
|
|
// Get whitelist MAC addresses
|
|
const whitelistMacs = whitelistData.users.map(user => user.mac || user);
|
|
|
|
// Filter out users already in whitelist
|
|
const availableUsers = allUsers.filter(user => {
|
|
return !whitelistMacs.includes(user.mac);
|
|
});
|
|
|
|
if (availableUsers.length === 0) {
|
|
userList.innerHTML = '<div style="text-align: center; padding: 20px; opacity: 0.6;"><%:No available users to add%></div>';
|
|
return;
|
|
}
|
|
|
|
availableUsers.forEach(user => {
|
|
const userElement = document.createElement('div');
|
|
userElement.className = 'user-item';
|
|
|
|
// Determine the display name
|
|
const displayName = user.nickname || user.hostname || '';
|
|
|
|
userElement.innerHTML = `
|
|
<label style="display: flex; align-items: center;">
|
|
<input type="checkbox" value="${user.mac}" style="margin-right: 8px;">
|
|
<span>${user.mac}</span>
|
|
<span style="opacity: 0.6; margin-left: 8px;">${displayName ? `(${displayName})` : ''}</span>
|
|
</label>
|
|
`;
|
|
userList.appendChild(userElement);
|
|
});
|
|
}
|
|
|
|
function closeWhitelistModal() {
|
|
const modal = document.getElementById('whitelistModal');
|
|
modal.style.display = 'none';
|
|
}
|
|
|
|
function addSelectedWhitelistUsers() {
|
|
console.log("addSelectedWhitelistUsers");
|
|
const userList = document.getElementById('whitelistUserList');
|
|
const checkboxes = userList.querySelectorAll('input[type="checkbox"]:checked');
|
|
|
|
const selectedMacs = Array.from(checkboxes).map(checkbox => checkbox.value);
|
|
|
|
if (selectedMacs.length === 0) {
|
|
showSuccessMessage('<%:Please select at least one user%>');
|
|
return;
|
|
}
|
|
|
|
// Check if any selected user is already in whitelist
|
|
const whitelistMacs = whitelistData.users.map(user => user.mac || user);
|
|
const alreadyInWhitelist = selectedMacs.filter(mac => whitelistMacs.includes(mac));
|
|
|
|
if (alreadyInWhitelist.length > 0) {
|
|
showSuccessMessage('<%:Some users are already in whitelist%>');
|
|
return;
|
|
}
|
|
|
|
var data = {
|
|
mac_list: selectedMacs
|
|
}
|
|
var data_str = JSON.stringify(data);
|
|
|
|
console.log(selectedMacs);
|
|
new XHR().post('<%=url('admin/network/add_whitelist_user')%>',
|
|
{ data: data_str },
|
|
function(x, data) {
|
|
|
|
setTimeout(() => {
|
|
getWhitelistData();
|
|
getAllUsersData();
|
|
renderAutoUserData();
|
|
}, 500);
|
|
closeWhitelistModal();
|
|
showSuccessMessage('<%:Add to whitelist successful%>');
|
|
}
|
|
);
|
|
}
|
|
|
|
function removeWhitelistUser(index) {
|
|
const userToRemove = whitelistData.users[index];
|
|
new XHR().post('<%=url('admin/network/del_whitelist_user')%>',
|
|
{ mac: userToRemove.mac },
|
|
function(x, data) {
|
|
|
|
setTimeout(() => {
|
|
getWhitelistData();
|
|
getAllUsersData();
|
|
renderAutoUserData();
|
|
}, 500);
|
|
showSuccessMessage('<%:Remove from whitelist successful%>');
|
|
}
|
|
);
|
|
}
|
|
|
|
window.onload = initData;
|
|
//]]></script>
|
|
|
|
<div class="cbi-section cbi-tblsection">
|
|
<div style="max-width: 1000px; margin-left: 5px;padding: 5px;">
|
|
<div class="mode-selection">
|
|
<label><input type="radio" name="mode" value="0" onclick="switchMode(0)"> <%:Automatic Mode%></label>
|
|
<label><input type="radio" name="mode" value="1" checked onclick="switchMode(1)"> <%:Manual Mode%></label>
|
|
</div>
|
|
|
|
|
|
<div id="modeDescription" style="margin-bottom: 15px;">
|
|
<%:In manual mode, only the following added terminals are controlled%>
|
|
</div>
|
|
|
|
<table class="cbi-section-table" id="user_table" style="display: none;">
|
|
<thead>
|
|
<tr class="tr table-titles">
|
|
<th class="th"><%:MAC%></th>
|
|
<th class="th"><%:Hostname%></th>
|
|
<th class="th"><%:Remark%></th>
|
|
<th class="th"><%:Actions%></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody></tbody>
|
|
</table>
|
|
|
|
<div id="auto_user_section" style="display: none; margin-top: 20px;">
|
|
<h3 style="margin-bottom: 15px;"><%:Effective Users%></h3>
|
|
<table class="cbi-section-table" id="auto_user_table">
|
|
<thead>
|
|
<tr class="tr table-titles">
|
|
<th class="th"><%:MAC%></th>
|
|
<th class="th"><%:Hostname%></th>
|
|
<th class="th"><%:Remark%></th>
|
|
<th class="th"><%:Actions%></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody></tbody>
|
|
</table>
|
|
|
|
<div id="no_users_message" style="display: none; text-align: center; padding: 40px 0; opacity: 0.6; font-size: 14px;">
|
|
<%:No Users Available%>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<div id="whitelist_section" style="display: none; margin-top: 20px;">
|
|
<h3 style="margin-bottom: 15px;"><%:Whitelist Users%></h3>
|
|
<div style="margin-bottom: 15px; opacity: 0.6; font-size: 13px;"><%:Users in whitelist are not controlled%></div>
|
|
<table class="cbi-section-table" id="whitelist_table">
|
|
<thead>
|
|
<tr class="tr table-titles">
|
|
<th class="th"><%:MAC%></th>
|
|
<th class="th"><%:Hostname%></th>
|
|
<th class="th"><%:Remark%></th>
|
|
<th class="th"><%:Actions%></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody></tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="cbi-section-create" style="margin-top: 15px;">
|
|
<input type="button" value="<%:Add User%>" onclick="showUserModal()" id="manualAddUserBtn" style="padding: 8px 16px; background-color: #1a73e8; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 13px; font-weight: 500; display: none;" />
|
|
<input type="button" value="<%:Add to Whitelist%>" onclick="showWhitelistModal()" id="whitelistAddUserBtn" style="padding: 8px 16px; background-color: #1a73e8; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 13px; font-weight: 500; display: none;" />
|
|
</div>
|
|
|
|
<div style="margin-top: 20px; display: flex; justify-content: flex-end;">
|
|
<button type="button" onclick="location.reload()" style="padding: 8px 20px; width: 150px; background: transparent; color: #1a73e8; border: 1px solid var(--border-color-medium, #d1d5db); border-radius: 4px; cursor: pointer; font-size: 13px; font-weight: 500; display: inline-flex; align-items: center; justify-content: center; line-height: 1.4;"><%:Refresh%></button>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<div id="userModal" class="oaf-modal-overlay">
|
|
<div class="oaf-modal-box">
|
|
<h3><%:Select Device%></h3>
|
|
<div id="userList" class="user-selection">
|
|
</div>
|
|
<div style="margin-top: 20px; display: flex; justify-content: flex-end; gap: 10px;">
|
|
<button type="button" onclick="closeUserModal()" style="padding: 7px 16px; border: 1px solid var(--border-color-medium, #d1d5db); border-radius: 4px; cursor: pointer; font-size: 13px; background: transparent;"><%:Cancel%></button>
|
|
<button type="button" onclick="addSelectedUsers()" style="padding: 7px 16px; background-color: #1a73e8; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 13px; font-weight: 500;"><%:OK%></button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="whitelistModal" class="oaf-modal-overlay">
|
|
<div class="oaf-modal-box">
|
|
<h3><%:Select Device for Whitelist%></h3>
|
|
<div id="whitelistUserList" class="user-selection">
|
|
</div>
|
|
<div style="margin-top: 20px; display: flex; justify-content: flex-end; gap: 10px;">
|
|
<button type="button" onclick="closeWhitelistModal()" style="padding: 7px 16px; border: 1px solid var(--border-color-medium, #d1d5db); border-radius: 4px; cursor: pointer; font-size: 13px; background: transparent;"><%:Cancel%></button>
|
|
<button type="button" onclick="addSelectedWhitelistUsers()" style="padding: 7px 16px; background-color: #1a73e8; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 13px; font-weight: 500;"><%:OK%></button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="modal" style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: transparent; z-index: 1000; justify-content: center; align-items: center;">
|
|
<div style="background-color: rgba(0, 0, 0, 0.65); padding: 14px 24px; border-radius: 6px; text-align: center; color: white; display: flex; justify-content: center; align-items: center;">
|
|
<p style="margin: 0; color: white; font-size: 14px;"><%:Settings saved successfully%></p>
|
|
</div>
|
|
</div>
|