Instancia transcoder t1: cambios locales
This commit is contained in:
@@ -8,6 +8,8 @@ class SourceManager {
|
||||
this.xtreamList = document.getElementById('xtream-list');
|
||||
this.m3uList = document.getElementById('m3u-list');
|
||||
this.epgList = document.getElementById('epg-list');
|
||||
/** @type {Record<number, string>} id → username (admin, for labels) */
|
||||
this.userNamesById = {};
|
||||
|
||||
// Content browser state
|
||||
this.contentType = 'channels'; // 'channels' or 'movies'
|
||||
@@ -20,6 +22,29 @@ class SourceManager {
|
||||
this.init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Comprueba si la sesión es admin. Usa /api/auth/me si window.app.currentUser
|
||||
* aún no está (carrera: el modal se abre antes de que termine checkAuth).
|
||||
*/
|
||||
async isAdminSession() {
|
||||
let role = window.app?.currentUser?.role;
|
||||
if (!role && typeof localStorage !== 'undefined') {
|
||||
const token = localStorage.getItem('authToken');
|
||||
if (token) {
|
||||
try {
|
||||
const r = await fetch('/api/auth/me', {
|
||||
headers: { Authorization: `Bearer ${token}` }
|
||||
});
|
||||
if (r.ok) {
|
||||
const u = await r.json();
|
||||
role = u.role;
|
||||
}
|
||||
} catch (_) { /* ignore */ }
|
||||
}
|
||||
}
|
||||
return String(role || '').toLowerCase() === 'admin';
|
||||
}
|
||||
|
||||
init() {
|
||||
// Add source buttons
|
||||
document.getElementById('add-xtream').addEventListener('click', () => this.showAddModal('xtream'));
|
||||
@@ -112,9 +137,20 @@ class SourceManager {
|
||||
try {
|
||||
const sources = await API.sources.getAll();
|
||||
|
||||
this.renderSourceList(this.xtreamList, sources.filter(s => s.type === 'xtream'), 'xtream');
|
||||
this.renderSourceList(this.m3uList, sources.filter(s => s.type === 'm3u'), 'm3u');
|
||||
this.renderSourceList(this.epgList, sources.filter(s => s.type === 'epg'), 'epg');
|
||||
const admin = await this.isAdminSession();
|
||||
this.userNamesById = {};
|
||||
if (admin) {
|
||||
try {
|
||||
const users = await API.users.getAll();
|
||||
users.forEach(u => { this.userNamesById[u.id] = u.username; });
|
||||
} catch (e) {
|
||||
console.warn('[SourceManager] Could not load users for labels:', e);
|
||||
}
|
||||
}
|
||||
|
||||
this.renderSourceList(this.xtreamList, sources.filter(s => s.type === 'xtream'), 'xtream', admin);
|
||||
this.renderSourceList(this.m3uList, sources.filter(s => s.type === 'm3u'), 'm3u', admin);
|
||||
this.renderSourceList(this.epgList, sources.filter(s => s.type === 'epg'), 'epg', admin);
|
||||
} catch (err) {
|
||||
console.error('Error loading sources:', err);
|
||||
}
|
||||
@@ -123,7 +159,7 @@ class SourceManager {
|
||||
/**
|
||||
* Render source list
|
||||
*/
|
||||
renderSourceList(container, sources, type) {
|
||||
renderSourceList(container, sources, type, isAdmin = false) {
|
||||
if (sources.length === 0) {
|
||||
container.innerHTML = `<p class="hint">No ${type.toUpperCase()} sources configured</p>`;
|
||||
return;
|
||||
@@ -131,12 +167,21 @@ class SourceManager {
|
||||
|
||||
const icons = { xtream: Icons.live, m3u: Icons.guide, epg: Icons.series };
|
||||
|
||||
const esc = (t) => String(t ?? '').replace(/&/g, '&').replace(/</g, '<').replace(/"/g, '"');
|
||||
const admin = isAdmin;
|
||||
const ownerLine = admin
|
||||
? `<div class="source-meta hint">${source.ownerId != null
|
||||
? `Propietario: ${esc(this.userNamesById[source.ownerId] || ('#' + source.ownerId))}`
|
||||
: 'Fuente compartida (todos los usuarios)'}</div>`
|
||||
: '';
|
||||
|
||||
container.innerHTML = sources.map(source => `
|
||||
<div class="source-item ${source.enabled ? '' : 'disabled'}" data-id="${source.id}">
|
||||
<span class="source-icon">${icons[type]}</span>
|
||||
<div class="source-info">
|
||||
<div class="source-name">${source.name}</div>
|
||||
<div class="source-url">${source.url}</div>
|
||||
<div class="source-name">${esc(source.name)}</div>
|
||||
<div class="source-url">${esc(source.url)}</div>
|
||||
${ownerLine}
|
||||
</div>
|
||||
<div class="source-actions">
|
||||
<button class="btn btn-sm btn-secondary" data-action="refresh" title="Refresh Data">${Icons.refresh}</button>
|
||||
@@ -165,7 +210,7 @@ class SourceManager {
|
||||
/**
|
||||
* Show add source modal
|
||||
*/
|
||||
showAddModal(type) {
|
||||
async showAddModal(type) {
|
||||
const modal = document.getElementById('modal');
|
||||
const title = document.getElementById('modal-title');
|
||||
const body = document.getElementById('modal-body');
|
||||
@@ -174,7 +219,7 @@ class SourceManager {
|
||||
const titles = { xtream: 'Add Xtream Connection', m3u: 'Add M3U Playlist', epg: 'Add EPG Source' };
|
||||
title.textContent = titles[type];
|
||||
|
||||
body.innerHTML = this.getSourceForm(type);
|
||||
body.innerHTML = await this.getSourceForm(type);
|
||||
|
||||
footer.innerHTML = `
|
||||
<button class="btn btn-secondary" id="modal-cancel">Cancel</button>
|
||||
@@ -202,7 +247,7 @@ class SourceManager {
|
||||
const footer = document.getElementById('modal-footer');
|
||||
|
||||
title.textContent = `Edit ${type.toUpperCase()} Source`;
|
||||
body.innerHTML = this.getSourceForm(type, source);
|
||||
body.innerHTML = await this.getSourceForm(type, source);
|
||||
|
||||
footer.innerHTML = `
|
||||
<button class="btn btn-secondary" id="modal-cancel">Cancel</button>
|
||||
@@ -222,11 +267,32 @@ class SourceManager {
|
||||
/**
|
||||
* Get source form HTML
|
||||
*/
|
||||
getSourceForm(type, source = {}) {
|
||||
async getSourceForm(type, source = {}) {
|
||||
const esc = (t) => String(t ?? '').replace(/&/g, '&').replace(/</g, '<').replace(/"/g, '"');
|
||||
|
||||
let ownerField = '';
|
||||
if (await this.isAdminSession()) {
|
||||
let users = [];
|
||||
try {
|
||||
users = await API.users.getAll();
|
||||
} catch (e) {
|
||||
console.warn('[SourceManager] users list:', e);
|
||||
}
|
||||
const opts = [`<option value="">Compartida (todos los usuarios autenticados)</option>`].concat(
|
||||
users.map(u => `<option value="${u.id}" ${Number(source.ownerId) === Number(u.id) ? 'selected' : ''}>${esc(u.username)}</option>`)
|
||||
);
|
||||
ownerField = `
|
||||
<div class="form-group">
|
||||
<label for="source-owner">Propietario</label>
|
||||
<select id="source-owner" class="form-input">${opts.join('')}</select>
|
||||
<p class="hint">Si asignas un usuario, solo él podrá usar esta fuente en el proxy (evita doble consumo de conexiones XC).</p>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
const nameField = `
|
||||
<div class="form-group">
|
||||
<label for="source-name">Name</label>
|
||||
<input type="text" id="source-name" class="form-input" placeholder="My Source" value="${source.name || ''}">
|
||||
<input type="text" id="source-name" class="form-input" placeholder="My Source" value="${esc(source.name)}">
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -235,27 +301,28 @@ class SourceManager {
|
||||
<label for="source-url">${type === 'xtream' ? 'Server URL' : 'URL'}</label>
|
||||
<input type="text" id="source-url" class="form-input"
|
||||
placeholder="${type === 'xtream' ? 'http://server.com:port' : 'https://example.com/playlist.m3u'}"
|
||||
value="${source.url || ''}">
|
||||
value="${esc(source.url)}">
|
||||
</div>
|
||||
`;
|
||||
|
||||
if (type === 'xtream') {
|
||||
const pwVal = source.password && !String(source.password).includes('•') ? esc(source.password) : '';
|
||||
return `
|
||||
${ownerField}
|
||||
${nameField}
|
||||
${urlField}
|
||||
<div class="form-group">
|
||||
<label for="source-username">Username</label>
|
||||
<input type="text" id="source-username" class="form-input" value="${source.username || ''}">
|
||||
<input type="text" id="source-username" class="form-input" value="${esc(source.username)}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="source-password">Password</label>
|
||||
<input type="password" id="source-password" class="form-input"
|
||||
value="${source.password && !source.password.includes('•') ? source.password : ''}">
|
||||
<input type="password" id="source-password" class="form-input" value="${pwVal}">
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
return nameField + urlField;
|
||||
return ownerField + nameField + urlField;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -295,7 +362,15 @@ class SourceManager {
|
||||
}
|
||||
}
|
||||
|
||||
await API.sources.create({ type, name, url, username, password });
|
||||
const payload = { type, name, url, username, password };
|
||||
if (await this.isAdminSession()) {
|
||||
const sel = document.getElementById('source-owner');
|
||||
if (sel) {
|
||||
const v = sel.value;
|
||||
payload.ownerId = v === '' ? null : parseInt(v, 10);
|
||||
}
|
||||
}
|
||||
await API.sources.create(payload);
|
||||
document.getElementById('modal').classList.remove('active');
|
||||
await this.loadSources();
|
||||
|
||||
@@ -329,6 +404,13 @@ class SourceManager {
|
||||
data.username = username;
|
||||
if (password) data.password = password;
|
||||
}
|
||||
if (await this.isAdminSession()) {
|
||||
const sel = document.getElementById('source-owner');
|
||||
if (sel) {
|
||||
const v = sel.value;
|
||||
data.ownerId = v === '' ? null : parseInt(v, 10);
|
||||
}
|
||||
}
|
||||
|
||||
await API.sources.update(id, data);
|
||||
document.getElementById('modal').classList.remove('active');
|
||||
|
||||
Reference in New Issue
Block a user