mirror of
https://github.com/caiwx86/small-packages.git
synced 2026-07-29 18:01:55 +08:00
1538 lines
48 KiB
HTML
1538 lines
48 KiB
HTML
<link rel="stylesheet" href="<%=resource%>/css/common.css">
|
|
<% local dsp=require "luci.dispatcher" -%>
|
|
|
|
<script type="text/javascript" src="<%=resource%>/echarts.min.js?v=5.0"></script>
|
|
|
|
<script type="text/javascript">//<![CDATA[
|
|
let visit_time_data = [];
|
|
let user_list_data = {
|
|
list: [
|
|
|
|
]
|
|
}
|
|
let cur_page = 1; // Start from page 1
|
|
let page_size = 10; // Fixed page size
|
|
let total_pages = 1;
|
|
let total_count = 0;
|
|
|
|
// Visit list pagination variables
|
|
let visit_cur_page = 1;
|
|
let visit_page_size = 10;
|
|
let visit_total_pages = 1;
|
|
let visit_total_count = 0;
|
|
let current_mac = '';
|
|
|
|
function getAllUsersData(page, size) {
|
|
if (page === undefined) page = cur_page;
|
|
if (size === undefined) size = page_size;
|
|
new XHR().get('<%=url('admin/network/get_all_users')%>', {flag: 3, page: page, page_size: size},
|
|
function (x, data) {
|
|
user_list_data = data.data;
|
|
if (data.data.total !== undefined) {
|
|
total_count = data.data.total;
|
|
total_pages = data.data.total_pages || 1;
|
|
cur_page = data.data.page || 1;
|
|
// page_size is fixed at 15, don't update from response
|
|
}
|
|
render_user_list(user_list_data);
|
|
updatePagination();
|
|
}
|
|
);
|
|
}
|
|
|
|
function updatePagination() {
|
|
var paginationDiv = document.getElementById('pagination');
|
|
if (!paginationDiv || total_pages <= 0) {
|
|
if (paginationDiv) paginationDiv.innerHTML = '';
|
|
return;
|
|
}
|
|
|
|
// Calculate page numbers to display (max 10 pages)
|
|
// Try to show 5 pages on left and 5 pages on right of current page
|
|
var startPage = Math.max(1, cur_page - 5);
|
|
var endPage = Math.min(total_pages, cur_page + 5);
|
|
|
|
// Adjust if we don't have 10 pages
|
|
var pageCount = endPage - startPage + 1;
|
|
if (pageCount < 10) {
|
|
if (startPage == 1) {
|
|
// If at the beginning, extend to the right
|
|
endPage = Math.min(total_pages, startPage + 9);
|
|
} else if (endPage == total_pages) {
|
|
// If at the end, extend to the left
|
|
startPage = Math.max(1, endPage - 9);
|
|
} else {
|
|
// In the middle, try to balance
|
|
var needed = 10 - pageCount;
|
|
var canExtendLeft = startPage - 1;
|
|
var canExtendRight = total_pages - endPage;
|
|
if (canExtendLeft >= canExtendRight) {
|
|
startPage = Math.max(1, startPage - Math.min(needed, canExtendLeft));
|
|
endPage = Math.min(total_pages, startPage + 9);
|
|
} else {
|
|
endPage = Math.min(total_pages, endPage + Math.min(needed, canExtendRight));
|
|
startPage = Math.max(1, endPage - 9);
|
|
}
|
|
}
|
|
}
|
|
|
|
var html = '<div style="display: flex; align-items: center; justify-content: center; margin-top: 20px; padding: 15px 0;">';
|
|
html += '<div style="display: flex; align-items: center; gap: 4px;">';
|
|
|
|
// Previous page link
|
|
if (cur_page > 1) {
|
|
html += '<a href="javascript:void(0)" onclick="goToPage(' + (cur_page - 1) + ')" style="padding: 8px 12px; color: #1a73e8; text-decoration: none; cursor: pointer; border-radius: 4px;"><%:Previous%></a>';
|
|
} else {
|
|
html += '<span style="padding: 8px 12px; opacity: 0.4; cursor: default;"><%:Previous%></span>';
|
|
}
|
|
|
|
// Page numbers
|
|
for (var i = startPage; i <= endPage; i++) {
|
|
if (i == cur_page) {
|
|
html += '<span style="padding: 8px 12px; color: #1a73e8; font-weight: 500; cursor: default; border-radius: 4px;">' + i + '</span>';
|
|
} else {
|
|
html += '<a href="javascript:void(0)" onclick="goToPage(' + i + ')" style="padding: 8px 12px; text-decoration: none; cursor: pointer; border-radius: 4px;">' + i + '</a>';
|
|
}
|
|
}
|
|
|
|
// Next page link
|
|
if (cur_page < total_pages) {
|
|
html += '<a href="javascript:void(0)" onclick="goToPage(' + (cur_page + 1) + ')" style="padding: 8px 12px; color: #1a73e8; text-decoration: none; cursor: pointer; border-radius: 4px;"><%:Next%></a>';
|
|
} else {
|
|
html += '<span style="padding: 8px 12px; opacity: 0.4; cursor: default;"><%:Next%></span>';
|
|
}
|
|
|
|
html += '</div>';
|
|
html += '<div style="margin-left: 20px; opacity: 0.6; font-size: 14px;">';
|
|
html += '<%:Total%>: ' + total_count + ' | <%:Page%>: ' + cur_page + ' / ' + total_pages;
|
|
html += '</div>';
|
|
html += '</div>';
|
|
|
|
paginationDiv.innerHTML = html;
|
|
}
|
|
|
|
function goToPage(page) {
|
|
if (page < 1) page = 1;
|
|
if (page > total_pages && total_pages > 0) page = total_pages;
|
|
cur_page = page;
|
|
getAllUsersData(cur_page, page_size);
|
|
}
|
|
|
|
let currentView = 'table'; // 'table' or 'card'
|
|
|
|
function switchView(view) {
|
|
currentView = view;
|
|
var tableWrapper = document.getElementById('tableViewWrapper');
|
|
var cardWrapper = document.getElementById('cardViewWrapper');
|
|
var btnTable = document.getElementById('btnTableView');
|
|
var btnCard = document.getElementById('btnCardView');
|
|
|
|
if (view === 'card') {
|
|
tableWrapper.style.display = 'none';
|
|
cardWrapper.style.display = 'grid';
|
|
btnTable.classList.remove('active');
|
|
btnCard.classList.add('active');
|
|
} else {
|
|
tableWrapper.style.display = 'block';
|
|
cardWrapper.style.display = 'none';
|
|
btnTable.classList.add('active');
|
|
btnCard.classList.remove('active');
|
|
}
|
|
if (user_list_data && user_list_data.list) {
|
|
if (view === 'card') render_card_view(user_list_data);
|
|
}
|
|
}
|
|
|
|
function render_card_view(data) {
|
|
var container = document.getElementById('cardViewWrapper');
|
|
if (!container || !data || !data.list) return;
|
|
var user_list = data.list;
|
|
var html = '';
|
|
|
|
for (var i = 0; i < user_list.length; i++) {
|
|
var u = user_list[i];
|
|
var nickname = u.nickname || "";
|
|
var hostname = u.hostname || "";
|
|
var displayName = nickname || hostname || "--";
|
|
var mac = u.mac || "--";
|
|
|
|
// 在线状态
|
|
var statusClass = 'offline';
|
|
var statusText = '<%:Offline%>';
|
|
if (u.online == 2) { statusClass = 'active'; statusText = '<%:Active%>'; }
|
|
else if (u.online == 1) { statusClass = 'online'; statusText = '<%:Online%>'; }
|
|
|
|
var upRate = formatRate(u.up_rate, u.online);
|
|
var downRate = formatRate(u.down_rate, u.online);
|
|
|
|
var am_time = u.today_am_active_time || 0;
|
|
var pm_time = u.today_pm_active_time || 0;
|
|
var total_time = am_time + pm_time;
|
|
var time_str = formatTime(total_time);
|
|
var tooltip_text = "<%:Morning%>: " + formatTime(am_time) + ", <%:Afternoon%>: " + formatTime(pm_time);
|
|
|
|
// 今日流量
|
|
var today_up_flow = u.today_up_flow || 0;
|
|
var today_down_flow = u.today_down_flow || 0;
|
|
var total_flow = today_up_flow + today_down_flow;
|
|
var flow_str = formatFlow(total_flow);
|
|
|
|
if (!Array.isArray(u.applist)) u.applist = [];
|
|
var app_icons = u.applist.slice(0, 5).map(function(app) {
|
|
var iconSrc = app.icon === 0 ? '<%=resource%>/app_icons/default.png' : '<%=resource%>/app_icons/' + app.id + '.png';
|
|
return '<img src="' + iconSrc + '" alt="' + app.name + '" title="' + app.name + '" class="card-app-icon">';
|
|
}).join('');
|
|
if (!app_icons) app_icons = '<span style="opacity:0.4;font-size:12px;">--</span>';
|
|
|
|
html += '<div class="user-card ' + (u.online == 0 ? 'user-card--offline' : '') + '" onclick="showDetails(\'' + mac + '\')">';
|
|
html += '<span class="card-status card-status--' + statusClass + '">' + statusText + '</span>';
|
|
html += '<div class="card-device">';
|
|
html += '<div class="card-name">' + displayName + '</div>';
|
|
html += '<div class="card-mac">' + mac + '</div>';
|
|
html += '</div>';
|
|
html += '<div class="card-rate">';
|
|
html += '<span class="card-rate-item" title="<%:Up Rate%>"><svg width="12" height="12" viewBox="0 0 12 12" fill="#34a853"><path d="M6 1L10 6H7v5H5V6H2z"/></svg> ' + upRate + '</span>';
|
|
html += '<span class="card-rate-item" title="<%:Down Rate%>"><svg width="12" height="12" viewBox="0 0 12 12" fill="#4285f4"><path d="M6 11L2 6h3V1h2v5h3z"/></svg> ' + downRate + '</span>';
|
|
html += '</div>';
|
|
var current_app = u.app || "";
|
|
var current_url = u.url || "";
|
|
var display_url = current_url;
|
|
if (current_url.length > 18) {
|
|
display_url = current_url.substring(0, 8) + '...' + current_url.substring(current_url.length - 8);
|
|
}
|
|
html += '<div class="card-visiting">';
|
|
html += '<span class="card-visiting-label"><%:Visiting%>: </span>';
|
|
if (current_app || current_url) {
|
|
if (current_app) html += '<span class="card-visiting-app">' + current_app + '</span>';
|
|
if (current_url) html += '<span class="card-visiting-url" title="' + current_url + '">' + display_url + '</span>';
|
|
} else {
|
|
html += '<span class="card-visiting-none">--</span>';
|
|
}
|
|
html += '</div>';
|
|
html += '<div class="card-info">';
|
|
html += '<div class="card-apps">' + app_icons + '</div>';
|
|
html += '<div style="display: flex; gap: 12px; align-items: center;">';
|
|
html += '<div class="card-time" title="' + tooltip_text + '">' + time_str + '</div>';
|
|
html += '<div class="card-flow" title="<%:Today Flow%>">' + flow_str + '</div>';
|
|
html += '</div>';
|
|
html += '</div>';
|
|
html += '</div>';
|
|
}
|
|
|
|
container.innerHTML = html;
|
|
}
|
|
|
|
window.onload = function() {
|
|
cur_page = 1;
|
|
getAllUsersData(cur_page, page_size);
|
|
if (window.innerWidth <= 768) {
|
|
switchView('card');
|
|
}
|
|
|
|
setInterval(function() {
|
|
getAllUsersData(cur_page, page_size);
|
|
}, 3000);
|
|
}
|
|
|
|
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 formatTime(minutes) {
|
|
|
|
if (minutes === undefined || minutes === null || minutes === '') {
|
|
return '0m';
|
|
}
|
|
var mins = parseInt(minutes);
|
|
if (isNaN(mins) || mins < 0) {
|
|
return '0m';
|
|
}
|
|
|
|
if (mins < 60) {
|
|
return mins + 'm';
|
|
}
|
|
|
|
var hours = Math.floor(mins / 60);
|
|
var remainingMins = mins % 60;
|
|
if (remainingMins === 0) {
|
|
return hours + 'h';
|
|
} else {
|
|
return hours + 'h ' + remainingMins + 'm';
|
|
}
|
|
}
|
|
|
|
function formatRate(kbps, isOnline) {
|
|
if (isOnline !== undefined && isOnline == 0) {
|
|
return '--';
|
|
}
|
|
|
|
var rate = 0;
|
|
if (kbps !== undefined && kbps !== null && kbps !== '') {
|
|
rate = parseFloat(kbps);
|
|
if (isNaN(rate) || rate < 0) {
|
|
rate = 0;
|
|
}
|
|
}
|
|
rate = Math.round(rate);
|
|
if (rate >= 1024) {
|
|
return (rate / 1024).toFixed(1) + ' MB/s';
|
|
} else {
|
|
return rate + ' KB/s';
|
|
}
|
|
}
|
|
|
|
function formatFlow(kb) {
|
|
if (kb === undefined || kb === null || kb === '') {
|
|
return '0 KB';
|
|
}
|
|
// Use parseFloat to handle large numbers correctly
|
|
var totalKB = parseFloat(kb);
|
|
if (isNaN(totalKB) || totalKB < 0) {
|
|
return '0 KB';
|
|
}
|
|
// Round to integer for KB
|
|
totalKB = Math.round(totalKB);
|
|
if (totalKB === 0) {
|
|
return '0 KB';
|
|
}
|
|
// Convert to appropriate unit (input is in KB)
|
|
if (totalKB < 1024) {
|
|
// KB - no decimal
|
|
return totalKB + ' KB';
|
|
} else if (totalKB < 1024 * 1024) {
|
|
// MB - one decimal
|
|
return (totalKB / 1024).toFixed(1) + ' MB';
|
|
} else if (totalKB < 1024 * 1024 * 1024) {
|
|
// GB - one decimal
|
|
return (totalKB / (1024 * 1024)).toFixed(1) + ' GB';
|
|
} else {
|
|
// TB - one decimal
|
|
return (totalKB / (1024 * 1024 * 1024)).toFixed(1) + ' TB';
|
|
}
|
|
}
|
|
|
|
function render_user_list(data) {
|
|
var tb = document.getElementById('user_status_table');
|
|
var tbody = tb ? tb.querySelector('tbody') : null;
|
|
var user_list = data.list;
|
|
if (user_list && tbody) {
|
|
tbody.innerHTML = '';
|
|
for (var i = 0; i < user_list.length; i++) {
|
|
var nickname = user_list[i].nickname || "";
|
|
var hostname = user_list[i].hostname || "";
|
|
var displayName = nickname || hostname || "--";
|
|
|
|
var tr = tbody.insertRow(-1);
|
|
tr.className = 'tr';
|
|
if (user_list[i].online == 0) {
|
|
tr.style.color = '#A9A9A9';
|
|
}
|
|
tr.insertCell(-1).innerHTML = `
|
|
<div style="display: flex; align-items: center;">
|
|
<div>
|
|
<div>${displayName}</div>
|
|
<div>${user_list[i].mac}</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
tr.insertCell(-1).innerHTML = user_list[i].ip;
|
|
tr.insertCell(-1).innerHTML = formatRate(user_list[i].up_rate, user_list[i].online);
|
|
tr.insertCell(-1).innerHTML = formatRate(user_list[i].down_rate, user_list[i].online);
|
|
|
|
var am_time = user_list[i].today_am_active_time || 0;
|
|
var pm_time = user_list[i].today_pm_active_time || 0;
|
|
var total_time = am_time + pm_time;
|
|
var time_str = formatTime(total_time);
|
|
var tooltip_text = "<%:Morning%>: " + formatTime(am_time) + ", <%:Afternoon%>: " + formatTime(pm_time);
|
|
tr.insertCell(-1).innerHTML = `<span title="${tooltip_text}" style="cursor: help;">${time_str}</span>`;
|
|
|
|
// 今日流量
|
|
var today_up_flow = user_list[i].today_up_flow || 0;
|
|
var today_down_flow = user_list[i].today_down_flow || 0;
|
|
var total_flow = today_up_flow + today_down_flow;
|
|
var flow_str = formatFlow(total_flow);
|
|
tr.insertCell(-1).innerHTML = flow_str;
|
|
|
|
if (!Array.isArray(user_list[i].applist))
|
|
user_list[i].applist = []
|
|
var app_list_str = user_list[i].applist.map(app => {
|
|
var iconSrc = app.icon === 0 ? '<%=resource%>/app_icons/default.png' : `<%=resource%>/app_icons/${app.id}.png`;
|
|
return `<img src="${iconSrc}" alt="${app.name}" title="${app.name}" style="width: 20px; height: 20px; border-radius: 5px; margin-right: 8px;">`;
|
|
}).join("") || "--";
|
|
tr.insertCell(-1).innerHTML = app_list_str;
|
|
var current_app = user_list[i].app || "--";
|
|
tr.insertCell(-1).innerHTML = current_app;
|
|
var current_url = user_list[i].url || "--";
|
|
var display_url = current_url;
|
|
if (current_url !== "--" && current_url.length > 18) {
|
|
display_url = current_url.substring(0, 8) + "..." + current_url.substring(current_url.length - 8);
|
|
}
|
|
tr.insertCell(-1).innerHTML = `<span title="${current_url}">${display_url}</span>`;
|
|
var online_status_html = "";
|
|
if (user_list[i].online == 2) {
|
|
online_status_html = `<span style="color: #1a73e8;"><%:Active%></span>`;
|
|
} else if (user_list[i].online == 1) {
|
|
online_status_html = `<span style="color: green;"><%:Online%></span>`;
|
|
} else {
|
|
online_status_html = "<%:Offline%>";
|
|
}
|
|
tr.insertCell(-1).innerHTML = online_status_html;
|
|
tr.insertCell(-1).innerHTML = `
|
|
<button type="button" class="cbi-button cbi-button-add" onclick="showDetails('${user_list[i].mac}')" style="margin-right: 5px;"><%:Details%></button>
|
|
<button type="button" class="cbi-button cbi-button-add" onclick="showModifyNickname('${user_list[i].mac}')"><%:Remark%></button>
|
|
`;
|
|
|
|
|
|
var childs = tr.childNodes;
|
|
Array.prototype.forEach.call(childs, function (child) {
|
|
child.className = 'td';
|
|
});
|
|
}
|
|
}
|
|
if (currentView === 'card') {
|
|
render_card_view(data);
|
|
}
|
|
}
|
|
|
|
function render_visit_list_table(data) {
|
|
var tb = document.getElementById('visit_list_table');
|
|
var tbody = tb ? tb.querySelector('tbody') : null;
|
|
var visit_list = data.list;
|
|
if (visit_list && tbody) {
|
|
tbody.innerHTML = '';
|
|
for (var i = 0; i < visit_list.length; i++) {
|
|
var action_status = visit_list[i].act == 1 ?
|
|
'<span style="color: red;"><%:Filtered%></span>' :
|
|
'<span style="color: green;"><%:Unfiltered%></span>';
|
|
var hostname = visit_list[i].hostname == "" || visit_list[i].hostname == "*" ? "--" : visit_list[i].hostname;
|
|
var tr = tbody.insertRow(-1);
|
|
tr.className = 'tr';
|
|
|
|
var iconSrc = visit_list[i].icon === 0 ? '<%=resource%>/app_icons/default.png' : `<%=resource%>/app_icons/${visit_list[i].id}.png`;
|
|
tr.insertCell(-1).innerHTML = `
|
|
<div style="height: 24px; display: flex; align-items: center;">
|
|
<img src="${iconSrc}" alt="${visit_list[i].name}" title="${visit_list[i].name}" style="width: 20px; height: 20px; border-radius: 5px; margin-right: 4px;">
|
|
<span>${visit_list[i].name}</span>
|
|
</div>
|
|
`;
|
|
|
|
|
|
var first_time_date = new Date(visit_list[i].ft * 1000);
|
|
var first_time_str = first_time_date.toLocaleString();
|
|
|
|
var latest_time_date = new Date(visit_list[i].lt * 1000);
|
|
var latest_time_str = latest_time_date.toLocaleString();
|
|
|
|
tr.insertCell(-1).innerHTML = first_time_str;
|
|
tr.insertCell(-1).innerHTML = latest_time_str;
|
|
var hour = parseInt(visit_list[i].tt / 3600);
|
|
var seconds = visit_list[i].tt % 3600;
|
|
var min = parseInt(seconds / 60)
|
|
var total_time_str;
|
|
if (visit_list[i].act == 1)
|
|
total_time_str = "-"
|
|
else {
|
|
if (hour > 0)
|
|
total_time_str = hour + "<%:h%>" + min + "<%:m%>"
|
|
else {
|
|
if (min == 0)
|
|
min = 1;
|
|
total_time_str = min + "<%:m%>"
|
|
}
|
|
}
|
|
tr.insertCell(-1).innerHTML = total_time_str;
|
|
tr.insertCell(-1).innerHTML = action_status;
|
|
|
|
var childs = tr.childNodes;
|
|
Array.prototype.forEach.call(childs, function (child) {
|
|
child.className = 'td';
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
function showDetails(mac) {
|
|
var modal = document.getElementById('detailsModal');
|
|
modal.style.display = 'flex';
|
|
updateDeviceInfoTitle(mac);
|
|
current_mac = mac;
|
|
visit_cur_page = 1; // Reset to first page when opening details
|
|
|
|
new XHR().get('<%=url('admin/network/dev_visit_time')%>/' + mac, null,
|
|
function (x, st) {
|
|
visit_time_data = st;
|
|
display_app_visit_view(visit_time_data);
|
|
}
|
|
);
|
|
|
|
getVisitListData(mac, visit_cur_page, visit_page_size);
|
|
}
|
|
|
|
function getVisitListData(mac, page, size) {
|
|
if (page === undefined) page = visit_cur_page;
|
|
if (size === undefined) size = visit_page_size;
|
|
new XHR().get('<%=url('admin/network/dev_visit_list')%>/' + mac, {page: page, page_size: size},
|
|
function (x, st) {
|
|
visit_list_data = st;
|
|
if (st.total !== undefined) {
|
|
visit_total_count = st.total;
|
|
visit_total_pages = st.total_pages || 1;
|
|
visit_cur_page = st.page || 1;
|
|
visit_page_size = st.page_size || 10;
|
|
}
|
|
render_visit_list_table(visit_list_data);
|
|
updateVisitListPagination();
|
|
}
|
|
);
|
|
}
|
|
|
|
function updateVisitListPagination() {
|
|
var paginationDiv = document.getElementById('visitListPagination');
|
|
if (!paginationDiv || visit_total_pages <= 0) {
|
|
if (paginationDiv) paginationDiv.innerHTML = '';
|
|
return;
|
|
}
|
|
|
|
// Calculate page numbers to display (max 10 pages)
|
|
// Try to show 5 pages on left and 5 pages on right of current page
|
|
var startPage = Math.max(1, visit_cur_page - 5);
|
|
var endPage = Math.min(visit_total_pages, visit_cur_page + 5);
|
|
|
|
// Adjust if we don't have 10 pages
|
|
var pageCount = endPage - startPage + 1;
|
|
if (pageCount < 10) {
|
|
if (startPage == 1) {
|
|
// If at the beginning, extend to the right
|
|
endPage = Math.min(visit_total_pages, startPage + 9);
|
|
} else if (endPage == visit_total_pages) {
|
|
// If at the end, extend to the left
|
|
startPage = Math.max(1, endPage - 9);
|
|
} else {
|
|
// In the middle, try to balance
|
|
var needed = 10 - pageCount;
|
|
var canExtendLeft = startPage - 1;
|
|
var canExtendRight = visit_total_pages - endPage;
|
|
if (canExtendLeft >= canExtendRight) {
|
|
startPage = Math.max(1, startPage - Math.min(needed, canExtendLeft));
|
|
endPage = Math.min(visit_total_pages, startPage + 9);
|
|
} else {
|
|
endPage = Math.min(visit_total_pages, endPage + Math.min(needed, canExtendRight));
|
|
startPage = Math.max(1, endPage - 9);
|
|
}
|
|
}
|
|
}
|
|
|
|
var html = '<div style="display: flex; align-items: center; justify-content: center; margin-top: 20px; padding: 15px 0;">';
|
|
html += '<div style="display: flex; align-items: center; gap: 4px;">';
|
|
|
|
// Previous page link
|
|
if (visit_cur_page > 1) {
|
|
html += '<a href="javascript:void(0)" onclick="goToVisitPage(' + (visit_cur_page - 1) + ')" style="padding: 8px 12px; color: #1a73e8; text-decoration: none; cursor: pointer; border-radius: 4px;"><%:Previous%></a>';
|
|
} else {
|
|
html += '<span style="padding: 8px 12px; opacity: 0.4; cursor: default;"><%:Previous%></span>';
|
|
}
|
|
|
|
// Page numbers
|
|
for (var i = startPage; i <= endPage; i++) {
|
|
if (i == visit_cur_page) {
|
|
html += '<span style="padding: 8px 12px; color: #1a73e8; font-weight: 500; cursor: default; border-radius: 4px;">' + i + '</span>';
|
|
} else {
|
|
html += '<a href="javascript:void(0)" onclick="goToVisitPage(' + i + ')" style="padding: 8px 12px; text-decoration: none; cursor: pointer; border-radius: 4px;">' + i + '</a>';
|
|
}
|
|
}
|
|
|
|
// Next page link
|
|
if (visit_cur_page < visit_total_pages) {
|
|
html += '<a href="javascript:void(0)" onclick="goToVisitPage(' + (visit_cur_page + 1) + ')" style="padding: 8px 12px; color: #1a73e8; text-decoration: none; cursor: pointer; border-radius: 4px;"><%:Next%></a>';
|
|
} else {
|
|
html += '<span style="padding: 8px 12px; opacity: 0.4; cursor: default;"><%:Next%></span>';
|
|
}
|
|
|
|
html += '</div>';
|
|
html += '<div style="margin-left: 20px; opacity: 0.6; font-size: 14px;">';
|
|
html += '<%:Total%>: ' + visit_total_count + ' | <%:Page%>: ' + visit_cur_page + ' / ' + visit_total_pages;
|
|
html += '</div>';
|
|
html += '</div>';
|
|
|
|
paginationDiv.innerHTML = html;
|
|
}
|
|
|
|
function goToVisitPage(page) {
|
|
if (page < 1) page = 1;
|
|
if (page > visit_total_pages && visit_total_pages > 0) page = visit_total_pages;
|
|
visit_cur_page = page;
|
|
getVisitListData(current_mac, visit_cur_page, visit_page_size);
|
|
}
|
|
|
|
function updateDeviceInfoTitle(mac) {
|
|
var deviceInfo = '(' + mac + ')';
|
|
|
|
if (user_list_data && user_list_data.list) {
|
|
var device = user_list_data.list.find(function(user) {
|
|
return user.mac === mac;
|
|
});
|
|
|
|
if (device) {
|
|
if (device.nickname && device.nickname.trim() !== '') {
|
|
deviceInfo = '(' + device.nickname + ')';
|
|
} else if (device.hostname && device.hostname.trim() !== '') {
|
|
deviceInfo = '(' + device.hostname + ')';
|
|
}
|
|
}
|
|
}
|
|
|
|
document.getElementById('deviceInfo').textContent = deviceInfo;
|
|
}
|
|
|
|
function showModifyNickname(mac) {
|
|
var modal = document.getElementById('nicknameModal');
|
|
modal.style.display = 'flex';
|
|
document.getElementById('nicknameMac').value = mac;
|
|
document.getElementById('nicknameMacDisplay').textContent = mac;
|
|
document.getElementById('nicknameInput').value = '';
|
|
}
|
|
|
|
function validateNickname(nickname) {
|
|
const invalidChars = /[\s'"]/;
|
|
return !invalidChars.test(nickname) && nickname.length <= 32;
|
|
}
|
|
|
|
function submitNicknameChange() {
|
|
var mac = document.getElementById('nicknameMac').value;
|
|
var nickname = document.getElementById('nicknameInput').value;
|
|
|
|
if (!validateNickname(nickname)) {
|
|
alert('<%:Please enter a valid remark%>');
|
|
return;
|
|
}
|
|
|
|
new XHR().post('<%=url('admin/network/set_nickname')%>',
|
|
{ mac: mac, nickname: nickname },
|
|
function(x, data) {
|
|
console.log('Nickname updated successfully');
|
|
closeModal('nicknameModal');
|
|
showSuccessMessage('<%:Settings saved successfully%>');
|
|
getAllUsersData(cur_page, page_size);
|
|
});
|
|
}
|
|
|
|
function closeModal(modalId) {
|
|
var modal = document.getElementById(modalId);
|
|
if (modal) {
|
|
modal.style.display = 'none';
|
|
}
|
|
}
|
|
|
|
function showTabContent(tabId) {
|
|
var tabs = document.querySelectorAll('.tab-body');
|
|
var tabItems = document.querySelectorAll('.tab-item');
|
|
|
|
tabs.forEach(function(tab) {
|
|
tab.classList.remove('active');
|
|
});
|
|
|
|
tabItems.forEach(function(item) {
|
|
item.classList.remove('active');
|
|
});
|
|
|
|
document.getElementById(tabId).classList.add('active');
|
|
document.querySelector('.tab-item[onclick="showTabContent(\'' + tabId + '\')"]').classList.add('active');
|
|
}
|
|
|
|
function get_display_time(total_time) {
|
|
var hour = parseInt(total_time / 3600);
|
|
var seconds = total_time % 3600;
|
|
var min = parseInt(seconds / 60)
|
|
var seconds2 = seconds % 60;
|
|
var total_time_str;
|
|
|
|
if (hour > 0)
|
|
total_time_str = hour + "<%:h%>" + min + "<%:m%>"
|
|
else {
|
|
if (min == 0 && seconds2 != 0)
|
|
min = 1;
|
|
total_time_str = min + "<%:m%>"
|
|
}
|
|
return total_time_str;
|
|
}
|
|
|
|
function display_app_visit_view(data) {
|
|
var chartElement = document.getElementById('app_time_chart');
|
|
if (!chartElement) {
|
|
console.error("Chart element not found");
|
|
return;
|
|
}
|
|
var myChart = echarts.init(chartElement);
|
|
if (!data) {
|
|
return;
|
|
}
|
|
var total_time = 0
|
|
var app_stat_array = new Array();
|
|
if (data.length == 0){
|
|
var app_obj ={}
|
|
app_obj.name = "<%:Unknown App%>"
|
|
app_obj.value = 0
|
|
app_obj.legendname = app_obj.name
|
|
app_stat_array.push(app_obj)
|
|
}
|
|
else{
|
|
for (var i = 0; i < data.length; i++) {
|
|
var app_obj = {};
|
|
app_obj.value = data[i].t;
|
|
app_obj.legendname = data[i].name;
|
|
var tmp_time = get_display_time(data[i].t);
|
|
app_obj.name = data[i].name + " " + tmp_time;
|
|
total_time += data[i].t
|
|
app_stat_array.push(app_obj);
|
|
}
|
|
}
|
|
var total_time_str = get_display_time(total_time);
|
|
var option = {
|
|
grid:{
|
|
},
|
|
|
|
color: [
|
|
'#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de',
|
|
'#3ba272', '#fc8452', '#9a60b4', '#ea7ccc', '#ff9f7f',
|
|
'#ffdb5c', '#c23531', '#2f4554', '#61a0a8', '#d48265',
|
|
'#91c7ae', '#749f83', '#ca8622', '#bda29a', '#6e7074'
|
|
],
|
|
title: [
|
|
{
|
|
text: "<%:App Time Statistics%>",
|
|
textStyle: {
|
|
fontSize: 16,
|
|
color: "black"
|
|
},
|
|
left: "2%"
|
|
},
|
|
{
|
|
text: '',
|
|
subtext: total_time_str,
|
|
textStyle: {
|
|
fontSize: 15,
|
|
color: "black"
|
|
},
|
|
subtextStyle: {
|
|
fontSize: 15,
|
|
color: 'black'
|
|
},
|
|
textAlign: "center",
|
|
x: '34.5%',
|
|
y: '44%',
|
|
}],
|
|
tooltip: {
|
|
trigger: 'item',
|
|
formatter: function (parms) {
|
|
var total_time = get_display_time(parms.data.value);
|
|
var str = parms.seriesName + "</br>" +
|
|
parms.marker + "" + parms.data.legendname + "</br>" +
|
|
"<%:Visit Time%>: " + total_time + "</br>" +
|
|
"<%:Percentage%>: " + parms.percent + "%";
|
|
return str;
|
|
}
|
|
},
|
|
legend: {
|
|
type: "scroll",
|
|
orient: 'vertical',
|
|
left: '75%',
|
|
align: 'left',
|
|
top: 'middle',
|
|
textStyle: {
|
|
color: '#8C8C8C'
|
|
},
|
|
height: 250
|
|
},
|
|
series: [
|
|
{
|
|
name: "<%:Visit Time%>",
|
|
type: 'pie',
|
|
radius: ['58%', '70%'],
|
|
center: ['35%', '50%'],
|
|
clockwise: false,
|
|
avoidLabelOverlap: true,
|
|
itemStyle: {
|
|
borderRadius: 1,
|
|
borderColor: "#fff",
|
|
borderWidth: 1,
|
|
},
|
|
label: {
|
|
show: true,
|
|
position: 'outside',
|
|
formatter: '{b}: {c} ({d}%)',
|
|
normal: {
|
|
show: true,
|
|
position: 'outter',
|
|
formatter: function (parms) {
|
|
return parms.data.legendname
|
|
}
|
|
}
|
|
},
|
|
labelLine: {
|
|
show: true,
|
|
length: 8,
|
|
length2: 7,
|
|
smooth: true,
|
|
},
|
|
data: app_stat_array
|
|
}
|
|
]
|
|
};
|
|
|
|
myChart.setOption(option);
|
|
}
|
|
|
|
//]]></script>
|
|
|
|
<div class="view-toggle">
|
|
<button type="button" class="view-toggle-btn active" id="btnTableView" onclick="switchView('table')" title="<%:Table View%>">
|
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"><path d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2zm15 2h-4v3h4V4zm0 4h-4v3h4V8zm0 4h-4v3h3a1 1 0 0 0 1-1v-2zm-5 3v-3H6v3h4zm-5 0v-3H1v2a1 1 0 0 0 1 1h3zm-4-4h4V8H1v3zm0-4h4V4H1v3zm5-3v3h4V4H6zm4 4H6v3h4V8z"/></svg>
|
|
</button>
|
|
<button type="button" class="view-toggle-btn" id="btnCardView" onclick="switchView('card')" title="<%:Card View%>">
|
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"><path d="M1 2a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V2zm5 0a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V2zm5 0a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1V2zM1 7a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V7zm5 0a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V7zm5 0a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1V7zM1 12a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1v-2zm5 0a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1v-2zm5 0a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1v-2z"/></svg>
|
|
</button>
|
|
</div>
|
|
|
|
<div style="max-height: 750px; overflow-y: auto;padding-right: 20px;">
|
|
|
|
<div class="cbi-section cbi-tblsection">
|
|
|
|
<div id="tableViewWrapper" style="overflow-x: auto; min-width: 1200px;">
|
|
|
|
<table class="table cbi-section-table" id="user_status_table" style="table-layout: fixed; min-width: 1200px; width: 100%;">
|
|
<thead>
|
|
<tr class="tr">
|
|
<th class="th" style="width: 115px;">
|
|
<%:Device Info%>
|
|
</th>
|
|
<th class="th" style="width: 100px;">
|
|
<%:IP Address%>
|
|
</th>
|
|
<th class="th" style="width: 80px;">
|
|
<%:Up Rate%>
|
|
</th>
|
|
<th class="th" style="width: 80px;">
|
|
<%:Down Rate%>
|
|
</th>
|
|
<th class="th" style="width: 100px;">
|
|
<%:Today Active Time%>
|
|
</th>
|
|
<th class="th" style="width: 100px;">
|
|
<%:Today Flow%>
|
|
</th>
|
|
<th class="th" style="width: 160px; white-space: nowrap;">
|
|
<%:Common App(TOP5)%>
|
|
</th>
|
|
<th class="th" style="width: 85px;">
|
|
<%:Active App%>
|
|
</th>
|
|
<th class="th" style="width: 155px; white-space: nowrap;">
|
|
<%:Current URL%>
|
|
</th>
|
|
<th class="th" style="width: 70px;">
|
|
<%:Online Status%>
|
|
</th>
|
|
<th class="th" style="width: 120px;">
|
|
<%:Actions%>
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr class="tr">
|
|
<td class="td" colspan="11"><em><br />
|
|
<%:Collecting data...%>
|
|
</em></td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
</div>
|
|
|
|
<div id="cardViewWrapper" class="card-grid" style="display: none;"></div>
|
|
|
|
<!-- Pagination -->
|
|
<div id="pagination"></div>
|
|
</div>
|
|
</div>
|
|
<!-- Details Modal -->
|
|
<div id="detailsModal" class="oaf-modal-overlay">
|
|
<div class="oaf-modal-box" style="width: 820px; height: 620px;">
|
|
<button type="button" onclick="closeModal('detailsModal')" class="oaf-modal-close">×</button>
|
|
<h4 class="oaf-modal-title"><%:Device Details%> <span id="deviceInfo" style="opacity: 0.5; font-size: 14px; font-weight: normal;"></span></h4>
|
|
|
|
<!-- Tab List -->
|
|
<ul class="tab-list">
|
|
<!-- <li class="tab-item onclick="showTabContent('tab1')">基本信息</li> -->
|
|
<li class="tab-item active" onclick="showTabContent('tab2')"><%:App Statistics%></li>
|
|
<li class="tab-item" onclick="showTabContent('tab3')"><%:Access Records%></li>
|
|
</ul>
|
|
|
|
<div id="tab2" class="tab-body active">
|
|
<div class="pie-chart">
|
|
<div id="app_time_chart" style="width:100%;height: 350px;">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="tab3" class="tab-body">
|
|
<div style="max-height: 320px; overflow-y: auto;padding-right: 20px;"> <!-- Added container with fixed height and overflow, reduced height to make room for pagination -->
|
|
<table class="table cbi-section-table" id="visit_list_table">
|
|
<thead>
|
|
<tr class="tr table-titles">
|
|
<th class="th">
|
|
<%:App Name%>
|
|
</th>
|
|
<th class="th">
|
|
<%:Start Visit Time%>
|
|
</th>
|
|
<th class="th">
|
|
<%:Last Visit Time%>
|
|
</th>
|
|
<th class="th">
|
|
<%:Duration%>
|
|
</th>
|
|
<th class="th">
|
|
<%:Filter Status%>
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr class="tr">
|
|
<td class="td" colspan="5"><em><br />
|
|
<%:Collecting data...%>
|
|
</em></td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Visit List Pagination -->
|
|
<div id="visitListPagination" style="flex-shrink: 0; margin-top: 10px;"></div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Nickname Modal -->
|
|
<div id="nicknameModal" class="oaf-modal-overlay">
|
|
<div class="oaf-modal-box" style="width: 460px;">
|
|
<button type="button" onclick="closeModal('nicknameModal')" class="oaf-modal-close">×</button>
|
|
|
|
<h4 class="oaf-modal-title"><%:Modify Remark%></h4>
|
|
|
|
<div style="margin-bottom: 20px;">
|
|
<p style="margin: 0 0 8px 0; opacity: 0.7; font-size: 14px;"><span class="field-label"><%:MAC Address%>:</span> <span id="nicknameMacDisplay" style="font-weight: 500; opacity: 1;">--</span></p>
|
|
</div>
|
|
|
|
<input type="hidden" id="nicknameMac" value="">
|
|
|
|
<div style="margin-bottom: 30px;">
|
|
<p style="margin: 0 0 8px 0; opacity: 0.7; font-size: 14px;"><span class="field-label"><%:Remark%>:</span></p>
|
|
<input type="text" id="nicknameInput" class="oaf-input" placeholder="请输入备注信息">
|
|
</div>
|
|
|
|
<div style="display: flex; justify-content: flex-end; gap: 10px; margin-top: 10px;">
|
|
<button type="button" class="oaf-btn oaf-btn-cancel" onclick="closeModal('nicknameModal')"><%:Cancel%></button>
|
|
<button type="button" class="oaf-btn oaf-btn-primary" onclick="submitNicknameChange()"><%: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>
|
|
<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-table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
border-radius: 0;
|
|
border: none;
|
|
}
|
|
|
|
.cbi-section-table thead .th {
|
|
background: var(--background-color-low, #f5f5f5);
|
|
color: var(--text-color-medium, #6b7280);
|
|
font-weight: 600;
|
|
font-size: 12.5px;
|
|
letter-spacing: 0.2px;
|
|
text-transform: none;
|
|
padding: 10px 12px;
|
|
border-top: none;
|
|
border-bottom: 1px solid var(--border-color-medium, #d1d5db);
|
|
white-space: nowrap;
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 1;
|
|
text-align: left;
|
|
}
|
|
|
|
.cbi-section-table tbody .td {
|
|
padding: 10px 12px;
|
|
border-bottom: 1px solid var(--border-color-low, #e5e7eb);
|
|
font-size: 13px;
|
|
vertical-align: middle;
|
|
line-height: 1.5;
|
|
text-align: left;
|
|
}
|
|
|
|
.cbi-section-table tbody tr:last-child .td {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.cbi-section-table tbody tr:hover .td {
|
|
background-color: var(--background-color-low, #f5f5f5);
|
|
}
|
|
|
|
|
|
.cbi-section-table tbody tr:nth-child(even) .td {
|
|
background-color: var(--background-color-low, #fafbfc);
|
|
}
|
|
.cbi-section-table tbody tr:nth-child(even):hover .td {
|
|
background-color: var(--background-color-low, #f5f5f5);
|
|
}
|
|
|
|
|
|
#user_status_table {
|
|
min-width: 1000px;
|
|
}
|
|
|
|
#user_status_table .td {
|
|
padding-left: 12px !important;
|
|
}
|
|
|
|
|
|
#user_status_table .td:last-child {
|
|
white-space: nowrap;
|
|
}
|
|
|
|
|
|
#user_status_table td:nth-child(8),
|
|
#user_status_table .td:nth-child(8) {
|
|
white-space: nowrap;
|
|
min-width: 175px;
|
|
}
|
|
|
|
|
|
#user_status_table .cbi-button {
|
|
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;
|
|
}
|
|
|
|
#user_status_table .cbi-button:hover {
|
|
background-color: var(--background-color-low, #f5f5f5);
|
|
border-color: #1a73e8;
|
|
}
|
|
|
|
|
|
#visit_list_table .td {
|
|
padding: 6px 10px !important;
|
|
height: 32px !important;
|
|
vertical-align: middle !important;
|
|
font-size: 12.5px;
|
|
}
|
|
|
|
#visit_list_table .th {
|
|
padding: 8px 10px !important;
|
|
height: 36px !important;
|
|
vertical-align: middle !important;
|
|
}
|
|
|
|
|
|
.tab-container {
|
|
margin-top: 10px;
|
|
}
|
|
.tab-list {
|
|
display: flex;
|
|
list-style-type: none;
|
|
padding: 0;
|
|
margin: 0 0 0 0;
|
|
border-bottom: 2px solid var(--border-color-low, #e5e7eb);
|
|
gap: 2px;
|
|
}
|
|
.tab-item {
|
|
padding: 8px 18px;
|
|
cursor: pointer;
|
|
border: none;
|
|
background-color: transparent;
|
|
border-radius: 6px 6px 0 0;
|
|
transition: all 0.2s ease;
|
|
opacity: 0.6;
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
position: relative;
|
|
}
|
|
.tab-item.active {
|
|
color: #1a73e8;
|
|
font-weight: 600;
|
|
opacity: 1;
|
|
}
|
|
.tab-item.active::after {
|
|
content: '';
|
|
position: absolute;
|
|
bottom: -2px;
|
|
left: 0;
|
|
right: 0;
|
|
height: 2px;
|
|
background-color: #1a73e8;
|
|
border-radius: 2px 2px 0 0;
|
|
}
|
|
.tab-item:hover {
|
|
background-color: var(--background-color-low, #f5f5f5);
|
|
opacity: 0.8;
|
|
}
|
|
|
|
.tab-body {
|
|
margin-top: 5px;
|
|
display: none;
|
|
padding: 20px;
|
|
}
|
|
.tab-body.active {
|
|
display: block;
|
|
}
|
|
|
|
.pie-chart {
|
|
width: 600px;
|
|
max-width: 100%;
|
|
}
|
|
|
|
.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;
|
|
animation: oafFadeIn 0.25s ease;
|
|
}
|
|
|
|
.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);
|
|
animation: oafSlideUp 0.3s ease;
|
|
max-width: 95vw;
|
|
max-height: 90vh;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.oaf-modal-close {
|
|
position: absolute;
|
|
top: 14px;
|
|
right: 14px;
|
|
background-color: transparent;
|
|
border: none;
|
|
font-size: 22px;
|
|
opacity: 0.5;
|
|
cursor: pointer;
|
|
width: 32px;
|
|
height: 32px;
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
transition: all 0.2s;
|
|
line-height: 1;
|
|
}
|
|
|
|
.oaf-modal-close:hover {
|
|
background-color: var(--background-color-low, #f5f5f5);
|
|
opacity: 1;
|
|
}
|
|
|
|
.oaf-modal-title {
|
|
margin: 0 0 20px 0;
|
|
font-size: 17px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.oaf-btn {
|
|
padding: 8px 20px;
|
|
border-radius: 6px;
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
border: none;
|
|
}
|
|
|
|
.oaf-btn-primary {
|
|
background-color: #1a73e8;
|
|
color: #fff;
|
|
}
|
|
.oaf-btn-primary:hover {
|
|
background-color: #1557b0;
|
|
box-shadow: 0 2px 6px rgba(26, 115, 232, 0.3);
|
|
}
|
|
|
|
.oaf-btn-cancel {
|
|
background: transparent;
|
|
border: 1px solid var(--border-color-medium, #d1d5db);
|
|
}
|
|
.oaf-btn-cancel:hover {
|
|
background-color: var(--background-color-low, #f5f5f5);
|
|
}
|
|
|
|
.oaf-input {
|
|
padding: 10px 12px;
|
|
background: transparent;
|
|
border: 1px solid var(--border-color-medium, #d1d5db);
|
|
border-radius: 6px;
|
|
width: 100%;
|
|
font-size: 14px;
|
|
transition: border-color 0.2s, box-shadow 0.2s;
|
|
box-sizing: border-box;
|
|
color: inherit;
|
|
}
|
|
.oaf-input:focus {
|
|
outline: none;
|
|
border-color: #1a73e8;
|
|
box-shadow: 0 0 0 3px rgba(26, 115, 232, 0.12);
|
|
}
|
|
|
|
|
|
#pagination, #visitListPagination {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 12px 0;
|
|
margin-top: 8px;
|
|
}
|
|
|
|
|
|
.field-label {
|
|
font-weight: 600;
|
|
width: 75px;
|
|
display: inline-block;
|
|
}
|
|
.field-value {
|
|
color: #4CAF50;
|
|
}
|
|
.info-container {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
margin-left: 20px;
|
|
}
|
|
.info-row {
|
|
display: flex;
|
|
}
|
|
.info-column {
|
|
width: 50%;
|
|
text-align: left;
|
|
}
|
|
.info-column p {
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
|
|
.view-toggle {
|
|
position: relative;
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
gap: 4px;
|
|
margin-bottom: 10px;
|
|
margin-left: auto;
|
|
width: fit-content;
|
|
}
|
|
.view-toggle-btn {
|
|
padding: 6px 10px;
|
|
border: 1px solid var(--border-color-medium, #d1d5db);
|
|
background: transparent;
|
|
opacity: 0.5;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
.view-toggle-btn:first-child {
|
|
border-radius: 4px 0 0 4px;
|
|
}
|
|
.view-toggle-btn:last-child {
|
|
border-radius: 0 4px 4px 0;
|
|
margin-left: -1px;
|
|
}
|
|
.view-toggle-btn.active {
|
|
color: #1a73e8;
|
|
border-color: #1a73e8;
|
|
z-index: 1;
|
|
position: relative;
|
|
opacity: 1;
|
|
}
|
|
.view-toggle-btn:hover:not(.active) {
|
|
opacity: 0.7;
|
|
}
|
|
|
|
|
|
.card-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
|
|
gap: 12px;
|
|
padding: 4px 0;
|
|
}
|
|
|
|
|
|
.user-card {
|
|
border: 1px solid var(--border-color-low, #e5e7eb);
|
|
border-radius: 10px;
|
|
padding: 14px 16px;
|
|
position: relative;
|
|
cursor: pointer;
|
|
transition: box-shadow 0.2s, border-color 0.2s;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
}
|
|
.user-card:hover {
|
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
|
border-color: var(--border-color-medium, #d1d5db);
|
|
}
|
|
.user-card--offline {
|
|
opacity: 0.55;
|
|
}
|
|
.user-card--offline:hover {
|
|
opacity: 0.75;
|
|
}
|
|
|
|
.card-status {
|
|
position: absolute;
|
|
top: 10px;
|
|
right: 12px;
|
|
font-size: 11px;
|
|
font-weight: 500;
|
|
padding: 2px 8px;
|
|
border-radius: 10px;
|
|
line-height: 1.4;
|
|
}
|
|
.card-status--active {
|
|
background: #e8f0fe;
|
|
color: #1a73e8;
|
|
}
|
|
.card-status--online {
|
|
background: #e6f4ea;
|
|
color: #1e8e3e;
|
|
}
|
|
.card-status--offline {
|
|
background: var(--background-color-low, #f5f5f5);
|
|
opacity: 0.6;
|
|
}
|
|
|
|
.card-device {
|
|
padding-right: 60px;
|
|
}
|
|
.card-name {
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
line-height: 1.3;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
.card-mac {
|
|
font-size: 11.5px;
|
|
opacity: 0.5;
|
|
margin-top: 2px;
|
|
font-family: monospace;
|
|
}
|
|
|
|
.card-rate {
|
|
display: flex;
|
|
gap: 16px;
|
|
align-items: center;
|
|
}
|
|
.card-rate-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
font-size: 12.5px;
|
|
opacity: 0.7;
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
.card-rate-item svg {
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.card-visiting {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
font-size: 12px;
|
|
opacity: 0.6;
|
|
overflow: hidden;
|
|
white-space: nowrap;
|
|
}
|
|
.card-visiting-label {
|
|
opacity: 0.7;
|
|
flex-shrink: 0;
|
|
}
|
|
.card-visiting-app {
|
|
font-weight: 500;
|
|
flex-shrink: 0;
|
|
opacity: 1;
|
|
}
|
|
.card-visiting-none {
|
|
opacity: 0.4;
|
|
}
|
|
.card-visiting-url {
|
|
opacity: 0.7;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
cursor: help;
|
|
}
|
|
|
|
.card-info {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
border-top: 1px solid var(--border-color-low, #e5e7eb);
|
|
padding-top: 8px;
|
|
}
|
|
.card-apps {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
flex: 1;
|
|
min-width: 0;
|
|
overflow: hidden;
|
|
}
|
|
.card-app-icon {
|
|
width: 20px;
|
|
height: 20px;
|
|
border-radius: 4px;
|
|
flex-shrink: 0;
|
|
}
|
|
.card-time {
|
|
font-size: 12px;
|
|
opacity: 0.6;
|
|
cursor: help;
|
|
flex-shrink: 0;
|
|
padding: 2px 8px;
|
|
border-radius: 4px;
|
|
}
|
|
.card-flow {
|
|
font-size: 12px;
|
|
opacity: 0.6;
|
|
cursor: help;
|
|
flex-shrink: 0;
|
|
padding: 2px 8px;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.oaf-modal-box {
|
|
width: 100vw !important;
|
|
height: 100vh !important;
|
|
max-width: 100vw !important;
|
|
max-height: 100vh !important;
|
|
border-radius: 0 !important;
|
|
padding: 16px !important;
|
|
margin: 0 !important;
|
|
}
|
|
.oaf-modal-overlay {
|
|
align-items: stretch !important;
|
|
}
|
|
.oaf-modal-close {
|
|
top: 10px !important;
|
|
right: 10px !important;
|
|
z-index: 10;
|
|
background-color: var(--background-color-low, #f5f5f5) !important;
|
|
}
|
|
.oaf-modal-title {
|
|
font-size: 15px !important;
|
|
padding-right: 40px;
|
|
}
|
|
.pie-chart {
|
|
width: 100% !important;
|
|
}
|
|
#tab2 .pie-chart #app_time_chart {
|
|
height: 260px !important;
|
|
}
|
|
#tab3 > div {
|
|
max-height: none !important;
|
|
overflow: auto !important;
|
|
}
|
|
.tab-body {
|
|
padding: 10px 0 !important;
|
|
}
|
|
#visit_list_table {
|
|
min-width: 500px;
|
|
}
|
|
.tab-body {
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
min-height: 0;
|
|
}
|
|
#tableViewWrapper {
|
|
display: none !important;
|
|
}
|
|
#cardViewWrapper {
|
|
display: grid !important;
|
|
}
|
|
.view-toggle {
|
|
display: none;
|
|
}
|
|
.card-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
|
|
@media (min-width: 769px) and (max-width: 1200px) {
|
|
.card-grid {
|
|
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
|
}
|
|
}
|
|
|
|
@keyframes oafFadeIn {
|
|
from { opacity: 0; }
|
|
to { opacity: 1; }
|
|
}
|
|
@keyframes oafSlideUp {
|
|
from { transform: translateY(16px); opacity: 0; }
|
|
to { transform: translateY(0); opacity: 1; }
|
|
}
|
|
</style>
|