- Incremented version to 9.4.1 (versionCode: 94100) - Added keep screen on functionality during video playback - Implemented device deletion in dashboard with confirmation - Enhanced device management with delete capability - Improved user experience during media playback - Better device lifecycle management in dashboard - Added confirmation dialog for device deletion 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
181 lines
5.9 KiB
JavaScript
181 lines
5.9 KiB
JavaScript
const tableBody = document.getElementById('devicesTable');
|
|
const refreshBtn = document.getElementById('refreshBtn');
|
|
const statusMessage = document.getElementById('statusMessage');
|
|
|
|
async function fetchDevices() {
|
|
setStatus('Cargando dispositivos...');
|
|
try {
|
|
const response = await fetch('/api/devices');
|
|
if (!response.ok) {
|
|
throw new Error('Error ' + response.status);
|
|
}
|
|
const { devices } = await response.json();
|
|
renderTable(devices || []);
|
|
setStatus('Actualizado ' + new Date().toLocaleTimeString());
|
|
} catch (error) {
|
|
console.error(error);
|
|
setStatus('No se pudo cargar el listado');
|
|
}
|
|
}
|
|
|
|
function renderTable(devices) {
|
|
tableBody.innerHTML = '';
|
|
if (!devices.length) {
|
|
tableBody.innerHTML = '<tr><td colspan="10" class="empty">Sin registros</td></tr>';
|
|
return;
|
|
}
|
|
devices.sort((a, b) => (a.lastSeen || '').localeCompare(b.lastSeen || '') * -1);
|
|
for (const device of devices) {
|
|
const tr = document.createElement('tr');
|
|
if (device.blocked) {
|
|
tr.classList.add('blocked');
|
|
}
|
|
const alias = device.alias && device.alias.trim().length ? device.alias : 'Sin alias';
|
|
const verificationStatus = device.verification && device.verification.status ? device.verification.status : 'pending';
|
|
const needsVerification = verificationStatus !== 'verified';
|
|
const verificationText = needsVerification
|
|
? `Pendiente - Token cliente: ${device.verification && device.verification.clientPart ? device.verification.clientPart : 'N/A'}`
|
|
: `Verificado ${device.verification.verifiedAt ? `(${formatDate(device.verification.verifiedAt)})` : ''}`;
|
|
const statusLabel = device.blocked
|
|
? 'Bloqueado'
|
|
: needsVerification ? 'Pendiente token' : 'Activo';
|
|
|
|
const actions = [`<button data-action="alias">Alias</button>`];
|
|
if (needsVerification) {
|
|
actions.push('<button data-action="verify" class="primary">Verificar token</button>');
|
|
}
|
|
actions.push(device.blocked ? '<button data-action="unblock" class="primary">Desbloquear</button>' : '<button data-action="block" class="danger">Bloquear</button>');
|
|
actions.push('<button data-action="delete" class="danger ghost">Borrar</button>');
|
|
|
|
tr.innerHTML = `
|
|
<td>
|
|
<div class="alias">${alias}</div>
|
|
<div class="device-name">${device.deviceName || '(sin nombre)'} </div>
|
|
</td>
|
|
<td>${device.deviceId}</td>
|
|
<td>${[device.manufacturer, device.model].filter(Boolean).join(' ')}</td>
|
|
<td>${device.appVersionName || ''} (${device.appVersionCode || ''})</td>
|
|
<td>${device.ip || '-'}</td>
|
|
<td>${formatCountry(device.country)}</td>
|
|
<td>${verificationText}</td>
|
|
<td>${formatDate(device.lastSeen)}</td>
|
|
<td>${statusLabel}</td>
|
|
<td class="actions-cell">
|
|
${actions.join(' ')}
|
|
</td>
|
|
`;
|
|
tr.dataset.deviceId = device.deviceId;
|
|
tableBody.appendChild(tr);
|
|
}
|
|
}
|
|
|
|
function formatDate(value) {
|
|
if (!value) return '-';
|
|
const date = new Date(value);
|
|
if (Number.isNaN(date.getTime())) return value;
|
|
return date.toLocaleString();
|
|
}
|
|
|
|
function formatCountry(value) {
|
|
if (!value || value === 'N/A') {
|
|
return '-';
|
|
}
|
|
return value;
|
|
}
|
|
|
|
async function blockDevice(deviceId) {
|
|
const reason = prompt('Motivo del bloqueo (opcional):');
|
|
await fetch(`/api/devices/${encodeURIComponent(deviceId)}/block`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ reason })
|
|
});
|
|
await fetchDevices();
|
|
}
|
|
|
|
async function unblockDevice(deviceId) {
|
|
await fetch(`/api/devices/${encodeURIComponent(deviceId)}/unblock`, { method: 'POST' });
|
|
await fetchDevices();
|
|
}
|
|
|
|
async function deleteDevice(deviceId) {
|
|
const confirmation = confirm('¿Seguro que quieres borrar este dispositivo? Generará un nuevo token cuando se registre de nuevo.');
|
|
if (!confirmation) {
|
|
return;
|
|
}
|
|
const response = await fetch(`/api/devices/${encodeURIComponent(deviceId)}`, { method: 'DELETE' });
|
|
if (!response.ok) {
|
|
alert('No se pudo borrar el dispositivo');
|
|
return;
|
|
}
|
|
await fetchDevices();
|
|
}
|
|
|
|
async function verifyDevice(deviceId) {
|
|
const clientTokenPart = prompt('Introduce el token que aparece en el dispositivo:');
|
|
if (clientTokenPart === null) {
|
|
return;
|
|
}
|
|
const adminTokenPart = prompt('Introduce el token recibido en Telegram:');
|
|
if (adminTokenPart === null) {
|
|
return;
|
|
}
|
|
const response = await fetch(`/api/devices/${encodeURIComponent(deviceId)}/verify`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ clientTokenPart, adminTokenPart })
|
|
});
|
|
if (!response.ok) {
|
|
const payload = await response.json().catch(() => ({}));
|
|
alert(payload.error || 'No se pudo verificar el token');
|
|
return;
|
|
}
|
|
await fetchDevices();
|
|
}
|
|
|
|
async function updateAlias(deviceId) {
|
|
const alias = prompt('Nuevo alias para el dispositivo:');
|
|
if (alias === null) {
|
|
return;
|
|
}
|
|
await fetch(`/api/devices/${encodeURIComponent(deviceId)}/alias`, {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ alias })
|
|
});
|
|
await fetchDevices();
|
|
}
|
|
|
|
function setStatus(message) {
|
|
statusMessage.textContent = message;
|
|
}
|
|
|
|
refreshBtn.addEventListener('click', fetchDevices);
|
|
|
|
tableBody.addEventListener('click', async (event) => {
|
|
const button = event.target.closest('button');
|
|
if (!button) return;
|
|
const tr = button.closest('tr');
|
|
const deviceId = tr && tr.dataset.deviceId;
|
|
if (!deviceId) return;
|
|
const action = button.dataset.action;
|
|
try {
|
|
if (action === 'block') {
|
|
await blockDevice(deviceId);
|
|
} else if (action === 'unblock') {
|
|
await unblockDevice(deviceId);
|
|
} else if (action === 'alias') {
|
|
await updateAlias(deviceId);
|
|
} else if (action === 'verify') {
|
|
await verifyDevice(deviceId);
|
|
} else if (action === 'delete') {
|
|
await deleteDevice(deviceId);
|
|
}
|
|
} catch (error) {
|
|
console.error(error);
|
|
alert('Operación fallida');
|
|
}
|
|
});
|
|
|
|
fetchDevices();
|