175 lines
5.1 KiB
JavaScript
175 lines
5.1 KiB
JavaScript
'use strict';
|
|
'require view';
|
|
'require dom';
|
|
'require poll';
|
|
'require rpc';
|
|
'require ui';
|
|
|
|
var callGetClients = rpc.declare({
|
|
object: 'luci.client-guardian',
|
|
method: 'clients',
|
|
expect: { clients: [] }
|
|
});
|
|
|
|
var callApproveClient = rpc.declare({
|
|
object: 'luci.client-guardian',
|
|
method: 'approve_client',
|
|
params: ['mac', 'name', 'zone', 'notes']
|
|
});
|
|
|
|
var callBanClient = rpc.declare({
|
|
object: 'luci.client-guardian',
|
|
method: 'ban_client',
|
|
params: ['mac', 'reason']
|
|
});
|
|
|
|
var callUnbanClient = rpc.declare({
|
|
object: 'luci.client-guardian',
|
|
method: 'unban_client',
|
|
params: ['mac']
|
|
});
|
|
|
|
return view.extend({
|
|
load: function() {
|
|
return callGetClients();
|
|
},
|
|
|
|
render: function(data) {
|
|
var clients = Array.isArray(data) ? data : (data.clients || []);
|
|
var online = clients.filter(function(c) { return c.online; }).length;
|
|
var approved = clients.filter(function(c) { return c.status === 'approved'; }).length;
|
|
var banned = clients.filter(function(c) { return c.status === 'banned'; }).length;
|
|
|
|
var view = E('div', { 'class': 'cbi-map' }, [
|
|
E('h2', {}, 'Client Guardian'),
|
|
E('div', { 'class': 'cbi-map-descr' }, 'Network client management'),
|
|
|
|
// Stats
|
|
E('div', { 'style': 'display:flex;gap:20px;margin:20px 0;' }, [
|
|
E('div', { 'style': 'padding:15px;background:#22c55e22;border-radius:8px;' }, [
|
|
E('strong', { 'style': 'font-size:24px;color:#22c55e;' }, String(online)),
|
|
E('div', {}, 'Online')
|
|
]),
|
|
E('div', { 'style': 'padding:15px;background:#3b82f622;border-radius:8px;' }, [
|
|
E('strong', { 'style': 'font-size:24px;color:#3b82f6;' }, String(approved)),
|
|
E('div', {}, 'Approved')
|
|
]),
|
|
E('div', { 'style': 'padding:15px;background:#ef444422;border-radius:8px;' }, [
|
|
E('strong', { 'style': 'font-size:24px;color:#ef4444;' }, String(banned)),
|
|
E('div', {}, 'Banned')
|
|
])
|
|
]),
|
|
|
|
// Client Table
|
|
E('div', { 'class': 'cbi-section' }, [
|
|
E('table', { 'class': 'table', 'id': 'client-table' }, [
|
|
E('tr', { 'class': 'tr table-titles' }, [
|
|
E('th', { 'class': 'th' }, 'Status'),
|
|
E('th', { 'class': 'th' }, 'Name'),
|
|
E('th', { 'class': 'th' }, 'MAC'),
|
|
E('th', { 'class': 'th' }, 'IP'),
|
|
E('th', { 'class': 'th' }, 'Actions')
|
|
])
|
|
].concat(clients.map(L.bind(this.renderClientRow, this))))
|
|
])
|
|
]);
|
|
|
|
poll.add(L.bind(this.refresh, this), 10);
|
|
return view;
|
|
},
|
|
|
|
renderClientRow: function(client) {
|
|
var statusIcon = client.online ? '🟢' : '⚪';
|
|
var statusStyle = '';
|
|
if (client.status === 'banned') {
|
|
statusIcon = '🔴';
|
|
statusStyle = 'background:#fee2e2;';
|
|
}
|
|
|
|
return E('tr', { 'class': 'tr', 'style': statusStyle, 'data-mac': client.mac }, [
|
|
E('td', { 'class': 'td' }, statusIcon),
|
|
E('td', { 'class': 'td' }, client.name || client.hostname || '-'),
|
|
E('td', { 'class': 'td', 'style': 'font-family:monospace;' }, client.mac),
|
|
E('td', { 'class': 'td' }, client.ip || '-'),
|
|
E('td', { 'class': 'td' }, this.renderActions(client))
|
|
]);
|
|
},
|
|
|
|
renderActions: function(client) {
|
|
var actions = E('div', { 'style': 'display:flex;gap:8px;' });
|
|
|
|
if (client.status !== 'approved') {
|
|
var approveBtn = E('button', {
|
|
'class': 'cbi-button cbi-button-positive',
|
|
'style': 'padding:4px 12px;',
|
|
'data-mac': client.mac
|
|
}, '✓ Approve');
|
|
approveBtn.addEventListener('click', L.bind(this.handleApprove, this));
|
|
actions.appendChild(approveBtn);
|
|
}
|
|
|
|
if (client.status === 'banned') {
|
|
var unbanBtn = E('button', {
|
|
'class': 'cbi-button cbi-button-action',
|
|
'style': 'padding:4px 12px;',
|
|
'data-mac': client.mac
|
|
}, 'Unban');
|
|
unbanBtn.addEventListener('click', L.bind(this.handleUnban, this));
|
|
actions.appendChild(unbanBtn);
|
|
} else {
|
|
var banBtn = E('button', {
|
|
'class': 'cbi-button cbi-button-negative',
|
|
'style': 'padding:4px 12px;',
|
|
'data-mac': client.mac
|
|
}, 'Ban');
|
|
banBtn.addEventListener('click', L.bind(this.handleBan, this));
|
|
actions.appendChild(banBtn);
|
|
}
|
|
|
|
return actions;
|
|
},
|
|
|
|
handleApprove: function(ev) {
|
|
var mac = ev.currentTarget.dataset.mac;
|
|
callApproveClient(mac, '', 'lan_private', '').then(L.bind(function() {
|
|
ui.addNotification(null, E('p', 'Client approved'), 'info');
|
|
this.refresh();
|
|
}, this));
|
|
},
|
|
|
|
handleBan: function(ev) {
|
|
var mac = ev.currentTarget.dataset.mac;
|
|
if (confirm('Ban this client?\n' + mac)) {
|
|
callBanClient(mac, 'Manual ban').then(L.bind(function() {
|
|
ui.addNotification(null, E('p', 'Client banned'), 'info');
|
|
this.refresh();
|
|
}, this));
|
|
}
|
|
},
|
|
|
|
handleUnban: function(ev) {
|
|
var mac = ev.currentTarget.dataset.mac;
|
|
callUnbanClient(mac).then(L.bind(function() {
|
|
ui.addNotification(null, E('p', 'Client unbanned'), 'info');
|
|
this.refresh();
|
|
}, this));
|
|
},
|
|
|
|
refresh: function() {
|
|
return callGetClients().then(L.bind(function(data) {
|
|
var clients = Array.isArray(data) ? data : (data.clients || []);
|
|
var table = document.getElementById('client-table');
|
|
if (table) {
|
|
while (table.rows.length > 1) table.deleteRow(1);
|
|
clients.forEach(L.bind(function(client) {
|
|
table.appendChild(this.renderClientRow(client));
|
|
}, this));
|
|
}
|
|
}, this));
|
|
},
|
|
|
|
handleSaveApply: null,
|
|
handleSave: null,
|
|
handleReset: null
|
|
});
|