'use strict'; 'require view'; 'require form'; 'require uci'; 'require rpc'; 'require ui'; var callStatus = rpc.declare({ object: 'luci.jellyfin', method: 'status', expect: {} }); var callStart = rpc.declare({ object: 'luci.jellyfin', method: 'start', expect: {} }); var callStop = rpc.declare({ object: 'luci.jellyfin', method: 'stop', expect: {} }); var callRestart = rpc.declare({ object: 'luci.jellyfin', method: 'restart', expect: {} }); var callInstall = rpc.declare({ object: 'luci.jellyfin', method: 'install', expect: {} }); var callLogs = rpc.declare({ object: 'luci.jellyfin', method: 'logs', params: ['lines'], expect: {} }); return view.extend({ load: function() { return Promise.all([ uci.load('jellyfin'), callStatus() ]); }, render: function(data) { var status = data[1] || {}; var m, s, o; m = new form.Map('jellyfin', _('Jellyfin Media Server'), _('Free media server for streaming movies, TV shows, music, and photos.')); /* ---- Status Section ---- */ s = m.section(form.NamedSection, 'main', 'jellyfin', _('Service Status')); s.anonymous = true; o = s.option(form.DummyValue, '_status', _('Status')); o.rawhtml = true; o.cfgvalue = function() { var cs = status.container_status || 'unknown'; var color = cs === 'running' ? '#27ae60' : (cs === 'stopped' ? '#e74c3c' : '#8892b0'); var label = cs === 'running' ? 'Running' : (cs === 'stopped' ? 'Stopped' : 'Not Installed'); var html = '' + label + ''; if (cs === 'running' && status.container_uptime) html += ' (' + status.container_uptime + ')'; return html; }; o = s.option(form.DummyValue, '_docker', _('Docker')); o.rawhtml = true; o.cfgvalue = function() { return status.docker_available ? 'Available' : 'Not available'; }; o = s.option(form.DummyValue, '_info', _('Details')); o.rawhtml = true; o.cfgvalue = function() { var port = status.port || 8096; var html = ''; html += ''; html += ''; html += ''; if (status.media_paths && status.media_paths.length > 0) html += ''; html += '
Image:' + (status.image || '-') + '
Port:' + port + '
Data:' + (status.data_path || '-') + '
Media:' + status.media_paths.join('
') + '
'; return html; }; /* ---- Action Buttons ---- */ o = s.option(form.DummyValue, '_actions', _('Actions')); o.rawhtml = true; o.cfgvalue = function() { return ''; }; var cs = status.container_status || 'not_installed'; if (cs === 'not_installed') { o = s.option(form.Button, '_install', _('Install')); o.inputtitle = _('Install Jellyfin'); o.inputstyle = 'apply'; o.onclick = function() { ui.showModal(_('Installing...'), [ E('p', { 'class': 'spinning' }, _('Pulling Docker image and configuring...')) ]); return callInstall().then(function(res) { ui.hideModal(); if (res && res.success) { ui.addNotification(null, E('p', {}, _('Jellyfin installed successfully.')), 'info'); } else { ui.addNotification(null, E('p', {}, _('Installation failed: ') + (res.output || 'Unknown error')), 'danger'); } window.location.href = window.location.pathname + '?' + Date.now(); }); }; } else { if (cs === 'stopped') { o = s.option(form.Button, '_start', _('Start')); o.inputtitle = _('Start'); o.inputstyle = 'apply'; o.onclick = function() { return callStart().then(function() { window.location.href = window.location.pathname + '?' + Date.now(); }); }; } if (cs === 'running') { o = s.option(form.Button, '_stop', _('Stop')); o.inputtitle = _('Stop'); o.inputstyle = 'remove'; o.onclick = function() { return callStop().then(function() { window.location.href = window.location.pathname + '?' + Date.now(); }); }; o = s.option(form.Button, '_restart', _('Restart')); o.inputtitle = _('Restart'); o.inputstyle = 'reload'; o.onclick = function() { return callRestart().then(function() { window.location.href = window.location.pathname + '?' + Date.now(); }); }; o = s.option(form.Button, '_webui', _('Web UI')); o.inputtitle = _('Open Web UI'); o.inputstyle = 'action'; o.onclick = function() { var port = status.port || 8096; window.open('http://' + window.location.hostname + ':' + port, '_blank'); }; } } /* ---- Configuration Section ---- */ s = m.section(form.NamedSection, 'main', 'jellyfin', _('Configuration')); s.anonymous = true; o = s.option(form.Flag, 'enabled', _('Enabled'), _('Enable the Jellyfin service.')); o.rmempty = false; o = s.option(form.Value, 'port', _('Port'), _('HTTP port for the Jellyfin web interface.')); o.datatype = 'port'; o.placeholder = '8096'; o = s.option(form.Value, 'image', _('Docker Image'), _('Docker image to use.')); o.placeholder = 'jellyfin/jellyfin:latest'; o = s.option(form.Value, 'data_path', _('Data Path'), _('Path for Jellyfin config and cache data.')); o.placeholder = '/srv/jellyfin'; o = s.option(form.Value, 'timezone', _('Timezone')); o.placeholder = 'Europe/Paris'; /* ---- Media Libraries ---- */ s = m.section(form.NamedSection, 'media', 'jellyfin', _('Media Libraries')); s.anonymous = true; o = s.option(form.DynamicList, 'media_path', _('Media Paths'), _('Directories containing your media files. Mounted read-only into the container.')); o.placeholder = '/mnt/media/movies'; /* ---- Transcoding ---- */ s = m.section(form.NamedSection, 'transcoding', 'jellyfin', _('Hardware Transcoding')); s.anonymous = true; o = s.option(form.Flag, 'hw_accel', _('Hardware Acceleration'), _('Enable GPU hardware transcoding. Requires a compatible GPU device.')); o.rmempty = false; o = s.option(form.Value, 'gpu_device', _('GPU Device'), _('Path to the GPU device for hardware transcoding.')); o.placeholder = '/dev/dri'; o.depends('hw_accel', '1'); /* ---- Logs Section ---- */ s = m.section(form.NamedSection, 'main', 'jellyfin', _('Logs')); s.anonymous = true; o = s.option(form.DummyValue, '_logs', ' '); o.rawhtml = true; o.cfgvalue = function() { return '
Click "Fetch Logs" to view container output.
'; }; o = s.option(form.Button, '_fetch_logs', _('Fetch Logs')); o.inputtitle = _('Fetch Logs'); o.inputstyle = 'action'; o.onclick = function() { var logsDiv = document.getElementById('jellyfin-logs'); if (logsDiv) logsDiv.textContent = 'Loading...'; return callLogs(50).then(function(res) { if (logsDiv) logsDiv.textContent = (res && res.logs) ? res.logs : 'No logs available.'; }); }; return m.render(); } });