'use strict'; 'require view'; 'require dom'; 'require poll'; 'require ui'; 'require uci'; 'require form'; 'require peertube.api as api'; return view.extend({ handleAction: function(action, args) { var self = this; var btn = document.activeElement; ui.showModal(_('Please wait...'), [ E('p', { 'class': 'spinning' }, _('Processing request...')) ]); var promise; switch(action) { case 'start': promise = api.start(); break; case 'stop': promise = api.stop(); break; case 'install': promise = api.install(); break; case 'uninstall': if (!confirm(_('This will remove the PeerTube container. Video data will be preserved. Continue?'))) return ui.hideModal(); promise = api.uninstall(); break; case 'update': promise = api.update(); break; case 'live_enable': promise = api.liveEnable(); break; case 'live_disable': promise = api.liveDisable(); break; case 'configure_haproxy': promise = api.configureHaproxy(); break; case 'emancipate': var domain = args; if (!domain) { ui.hideModal(); ui.addNotification(null, E('p', _('Domain is required')), 'error'); return; } promise = api.emancipate(domain); break; default: ui.hideModal(); return; } promise.then(function(res) { ui.hideModal(); if (res && res.success) { ui.addNotification(null, E('p', res.message || _('Action completed')), 'success'); self.load().then(function(data) { dom.content(document.querySelector('#peertube-content'), self.renderContent(data)); }); } else { ui.addNotification(null, E('p', res.error || _('Action failed')), 'error'); } }).catch(function(e) { ui.hideModal(); ui.addNotification(null, E('p', _('Error: ') + e.message), 'error'); }); }, load: function() { return Promise.all([ api.status(), uci.load('peertube') ]); }, renderInstallWizard: function() { var self = this; return E('div', { 'class': 'cbi-section' }, [ E('h3', {}, _('PeerTube Video Platform')), E('p', {}, _('PeerTube is a free, decentralized and federated video streaming platform. Videos are stored locally and can be shared across the Fediverse using ActivityPub.')), E('div', { 'class': 'cbi-value' }, [ E('h4', {}, _('Features')), E('ul', {}, [ E('li', {}, _('Self-hosted video hosting with HLS streaming')), E('li', {}, _('Live streaming with RTMP ingest')), E('li', {}, _('Automatic transcoding to multiple resolutions')), E('li', {}, _('Federation via ActivityPub protocol')), E('li', {}, _('User management and access controls')), E('li', {}, _('WebTorrent for distributed video delivery')) ]) ]), E('div', { 'class': 'cbi-value' }, [ E('h4', {}, _('Requirements')), E('ul', {}, [ E('li', {}, _('Minimum 2GB RAM recommended')), E('li', {}, _('10GB storage for system + additional for videos')), E('li', {}, _('Network access for container downloads')) ]) ]), E('div', { 'class': 'cbi-page-actions' }, [ E('button', { 'class': 'btn cbi-button cbi-button-positive', 'click': function() { self.handleAction('install'); } }, _('Install PeerTube')) ]) ]); }, renderStatusBadge: function(running) { var color = running === 'true' ? '#4CAF50' : '#f44336'; var text = running === 'true' ? _('Running') : _('Stopped'); return E('span', { 'style': 'display:inline-block;padding:3px 10px;border-radius:3px;color:#fff;background:' + color }, text); }, renderContent: function(data) { var self = this; var status = data[0] || {}; if (status.container_state === 'not_installed') { return this.renderInstallWizard(); } var running = status.running === 'true'; var liveEnabled = status.live_enabled === '1'; var haproxyConfigured = status.haproxy === '1'; var domain = status.domain || ''; var accessUrl = ''; if (running) { if (domain && haproxyConfigured) { accessUrl = (status.https === '1' ? 'https://' : 'http://') + domain; } else { accessUrl = 'http://192.168.255.1:' + (status.port || '9000'); } } return E('div', { 'class': 'cbi-section' }, [ E('h3', {}, _('PeerTube Video Platform')), E('div', { 'class': 'cbi-value' }, [ E('label', { 'class': 'cbi-value-title' }, _('Status')), E('div', { 'class': 'cbi-value-field' }, this.renderStatusBadge(status.running)) ]), running && accessUrl ? E('div', { 'class': 'cbi-value' }, [ E('label', { 'class': 'cbi-value-title' }, _('Access URL')), E('div', { 'class': 'cbi-value-field' }, [ E('a', { 'href': accessUrl, 'target': '_blank' }, accessUrl) ]) ]) : '', E('div', { 'class': 'cbi-value' }, [ E('label', { 'class': 'cbi-value-title' }, _('Hostname')), E('div', { 'class': 'cbi-value-field' }, status.hostname || '-') ]), E('div', { 'class': 'cbi-value' }, [ E('label', { 'class': 'cbi-value-title' }, _('Port')), E('div', { 'class': 'cbi-value-field' }, status.port || '9000') ]), E('div', { 'class': 'cbi-value' }, [ E('label', { 'class': 'cbi-value-title' }, _('Live Streaming')), E('div', { 'class': 'cbi-value-field' }, [ E('span', { 'style': 'display:inline-block;padding:3px 10px;border-radius:3px;color:#fff;background:' + (liveEnabled ? '#4CAF50' : '#9e9e9e') }, liveEnabled ? _('Enabled') : _('Disabled')), ' ', liveEnabled ? E('button', { 'class': 'btn cbi-button', 'click': function() { self.handleAction('live_disable'); } }, _('Disable')) : E('button', { 'class': 'btn cbi-button', 'click': function() { self.handleAction('live_enable'); } }, _('Enable')) ]) ]), E('div', { 'class': 'cbi-value' }, [ E('label', { 'class': 'cbi-value-title' }, _('HAProxy')), E('div', { 'class': 'cbi-value-field' }, [ E('span', { 'style': 'display:inline-block;padding:3px 10px;border-radius:3px;color:#fff;background:' + (haproxyConfigured ? '#4CAF50' : '#9e9e9e') }, haproxyConfigured ? _('Configured') : _('Not configured')), ' ', !haproxyConfigured ? E('button', { 'class': 'btn cbi-button', 'click': function() { self.handleAction('configure_haproxy'); } }, _('Configure')) : '' ]) ]), E('div', { 'class': 'cbi-value' }, [ E('label', { 'class': 'cbi-value-title' }, _('Domain')), E('div', { 'class': 'cbi-value-field' }, domain || _('Not configured')) ]), E('div', { 'class': 'cbi-value' }, [ E('label', { 'class': 'cbi-value-title' }, _('Admin Email')), E('div', { 'class': 'cbi-value-field' }, status.admin_email || '-') ]), E('hr'), E('h4', {}, _('Service Controls')), E('div', { 'class': 'cbi-page-actions', 'style': 'margin-bottom: 20px;' }, [ running ? E('button', { 'class': 'btn cbi-button cbi-button-negative', 'click': function() { self.handleAction('stop'); } }, _('Stop')) : E('button', { 'class': 'btn cbi-button cbi-button-positive', 'click': function() { self.handleAction('start'); } }, _('Start')), ' ', E('button', { 'class': 'btn cbi-button', 'click': function() { self.handleAction('update'); } }, _('Update')), ' ', E('button', { 'class': 'btn cbi-button cbi-button-negative', 'click': function() { self.handleAction('uninstall'); } }, _('Uninstall')) ]), E('hr'), E('h4', {}, _('Emancipate (Public Exposure)')), E('p', {}, _('Make PeerTube publicly accessible with SSL certificate and DNS configuration.')), E('div', { 'class': 'cbi-value' }, [ E('label', { 'class': 'cbi-value-title' }, _('Domain')), E('div', { 'class': 'cbi-value-field' }, [ E('input', { 'type': 'text', 'id': 'emancipate-domain', 'class': 'cbi-input-text', 'placeholder': 'peertube.example.com', 'value': domain }) ]) ]), E('div', { 'class': 'cbi-page-actions' }, [ E('button', { 'class': 'btn cbi-button cbi-button-action', 'click': function() { var domainInput = document.getElementById('emancipate-domain'); self.handleAction('emancipate', domainInput.value); } }, _('Emancipate')) ]), E('hr'), E('h4', {}, _('Logs')), E('div', { 'id': 'peertube-logs' }, [ E('pre', { 'style': 'background:#1e1e1e;color:#d4d4d4;padding:10px;max-height:300px;overflow:auto;font-size:12px;border-radius:4px;' }, _('Loading logs...')) ]), E('div', { 'class': 'cbi-page-actions' }, [ E('button', { 'class': 'btn cbi-button', 'click': function() { api.logs(100).then(function(res) { var logsEl = document.querySelector('#peertube-logs pre'); if (logsEl) { logsEl.textContent = res.logs || _('No logs available'); } }); } }, _('Refresh Logs')) ]) ]); }, render: function(data) { var self = this; var content = E('div', { 'id': 'peertube-content' }, this.renderContent(data)); // Load logs initially api.logs(50).then(function(res) { var logsEl = document.querySelector('#peertube-logs pre'); if (logsEl) { logsEl.textContent = res.logs || _('No logs available'); } }); // Poll for status updates poll.add(function() { return api.status().then(function(status) { var statusBadge = document.querySelector('.cbi-value-field span'); // Status badge is the first one - update if running state changed }); }, 10); return content; }, handleSaveApply: null, handleSave: null, handleReset: null });