diff --git a/package/secubox/luci-app-dpi-dual/root/usr/share/luci/menu.d/luci-app-dpi-dual.json b/package/secubox/luci-app-dpi-dual/root/usr/share/luci/menu.d/luci-app-dpi-dual.json index 05767e49..3b130f2a 100644 --- a/package/secubox/luci-app-dpi-dual/root/usr/share/luci/menu.d/luci-app-dpi-dual.json +++ b/package/secubox/luci-app-dpi-dual/root/usr/share/luci/menu.d/luci-app-dpi-dual.json @@ -3,8 +3,7 @@ "title": "DPI Dual-Stream", "order": 45, "action": { - "type": "firstchildview", - "recurse": true + "type": "firstchild" }, "depends": { "acl": ["luci-app-dpi-dual"], diff --git a/package/secubox/luci-app-lyrion/htdocs/luci-static/resources/view/lyrion/overview.js b/package/secubox/luci-app-lyrion/htdocs/luci-static/resources/view/lyrion/overview.js index b849cb6c..9b0b8ae8 100644 --- a/package/secubox/luci-app-lyrion/htdocs/luci-static/resources/view/lyrion/overview.js +++ b/package/secubox/luci-app-lyrion/htdocs/luci-static/resources/view/lyrion/overview.js @@ -3,271 +3,176 @@ 'require ui'; 'require rpc'; 'require poll'; -'require secubox/kiss-theme'; -var callStatus = rpc.declare({ object: 'luci.lyrion', method: 'status', expect: {} }); -var callLibraryStats = rpc.declare({ object: 'luci.lyrion', method: 'get_library_stats', expect: {} }); -var callInstall = rpc.declare({ object: 'luci.lyrion', method: 'install', expect: {} }); -var callStart = rpc.declare({ object: 'luci.lyrion', method: 'start', expect: {} }); -var callStop = rpc.declare({ object: 'luci.lyrion', method: 'stop', expect: {} }); -var callRestart = rpc.declare({ object: 'luci.lyrion', method: 'restart', expect: {} }); -var callRescan = rpc.declare({ object: 'luci.lyrion', method: 'rescan', expect: {} }); +var callStatus = rpc.declare({ object: 'luci.lyrion', method: 'status' }); +var callInstall = rpc.declare({ object: 'luci.lyrion', method: 'install' }); +var callStart = rpc.declare({ object: 'luci.lyrion', method: 'start' }); +var callStop = rpc.declare({ object: 'luci.lyrion', method: 'stop' }); +var callRescan = rpc.declare({ object: 'luci.lyrion', method: 'rescan' }); return view.extend({ - pollActive: true, - libraryStats: null, - load: function() { - return Promise.all([callStatus(), callLibraryStats()]); + return callStatus().catch(function() { return {}; }); }, startPolling: function() { var self = this; - this.pollActive = true; - poll.add(L.bind(function() { - if (!this.pollActive) return Promise.resolve(); - return Promise.all([callStatus(), callLibraryStats()]).then(L.bind(function(results) { - this.updateStatus(results[0]); - this.updateLibraryStats(results[1]); - }, this)); - }, this), 3); + poll.add(function() { + return callStatus().then(function(s) { + self.updateUI(s); + }); + }, 5); }, - updateStatus: function(status) { - var badge = document.getElementById('lyrion-status-badge'); + updateUI: function(s) { + var badge = document.getElementById('status-badge'); if (badge) { - badge.innerHTML = ''; - badge.appendChild(KissTheme.badge(status.running ? 'RUNNING' : 'STOPPED', status.running ? 'green' : 'red')); - } - }, - - updateLibraryStats: function(stats) { - if (!stats) return; - this.libraryStats = stats; - - var statsEl = document.getElementById('lyrion-stats'); - if (statsEl) { - statsEl.innerHTML = ''; - this.renderStats(stats).forEach(function(el) { statsEl.appendChild(el); }); + badge.className = 'cbi-value-field'; + badge.innerHTML = s.running + ? '● Running' + : '● Stopped'; } - var scanEl = document.getElementById('lyrion-scan'); - if (scanEl) { - scanEl.innerHTML = ''; - scanEl.appendChild(this.renderScanStatus(stats)); + var stats = document.getElementById('library-stats'); + if (stats && s.songs !== undefined) { + stats.textContent = s.songs + ' songs, ' + s.albums + ' albums, ' + s.artists + ' artists'; } - }, - formatNumber: function(n) { - if (n >= 1000000) return (n / 1000000).toFixed(1) + 'M'; - if (n >= 1000) return (n / 1000).toFixed(1) + 'K'; - return n.toString(); - }, - - renderStats: function(stats) { - var c = KissTheme.colors; - return [ - KissTheme.stat(this.formatNumber(stats.songs || 0), 'Songs', c.purple), - KissTheme.stat(this.formatNumber(stats.albums || 0), 'Albums', c.blue), - KissTheme.stat(this.formatNumber(stats.artists || 0), 'Artists', c.green), - KissTheme.stat(this.formatNumber(stats.genres || 0), 'Genres', c.orange) - ]; - }, - - renderScanStatus: function(stats) { - if (stats.scanning) { - var pct = stats.scan_total > 0 ? Math.round((stats.scan_progress / stats.scan_total) * 100) : 0; - return E('div', { 'style': 'display: flex; flex-direction: column; gap: 8px;' }, [ - E('div', { 'style': 'display: flex; justify-content: space-between; align-items: center;' }, [ - E('span', { 'style': 'display: flex; align-items: center; gap: 8px;' }, [ - E('span', { 'class': 'spinning', 'style': 'font-size: 14px;' }, '⏳'), - E('span', { 'style': 'font-weight: 600;' }, 'Scanning...') - ]), - E('span', { 'style': 'font-size: 12px; color: var(--kiss-muted);' }, - (stats.scan_phase || 'Processing') + ' (' + stats.scan_progress + '/' + stats.scan_total + ')') - ]), - E('div', { 'style': 'height: 8px; background: var(--kiss-bg); border-radius: 4px; overflow: hidden;' }, [ - E('div', { 'style': 'height: 100%; width: ' + pct + '%; background: linear-gradient(90deg, var(--kiss-purple), var(--kiss-blue)); border-radius: 4px; transition: width 0.3s;' }) - ]) - ]); - } else { - return E('div', { 'style': 'display: flex; justify-content: space-between; align-items: center;' }, [ - E('span', { 'style': 'display: flex; align-items: center; gap: 8px; color: var(--kiss-green);' }, [ - E('span', {}, '✓'), - E('span', { 'style': 'font-weight: 600;' }, 'Library Ready') - ]), - E('span', { 'style': 'font-size: 12px; color: var(--kiss-muted);' }, 'DB: ' + (stats.db_size || '0')) - ]); - } - }, - - renderControls: function(status) { - return E('div', { 'style': 'display: flex; gap: 8px; flex-wrap: wrap;' }, [ - E('button', { - 'class': 'kiss-btn kiss-btn-green', - 'click': ui.createHandlerFn(this, 'handleStart'), - 'disabled': status.running - }, 'Start'), - E('button', { - 'class': 'kiss-btn kiss-btn-red', - 'click': ui.createHandlerFn(this, 'handleStop'), - 'disabled': !status.running - }, 'Stop'), - E('button', { - 'class': 'kiss-btn', - 'click': ui.createHandlerFn(this, 'handleRestart'), - 'disabled': !status.running - }, 'Restart'), - E('button', { - 'class': 'kiss-btn kiss-btn-blue', - 'click': ui.createHandlerFn(this, 'handleRescan'), - 'disabled': !status.running - }, 'Rescan Library') - ]); - }, - - renderServiceInfo: function(status) { - var checks = [ - { label: 'Runtime', value: status.detected_runtime || 'auto' }, - { label: 'Port', value: status.port || '9000' }, - { label: 'Memory', value: status.memory_limit || '256M' }, - { label: 'Media Path', value: status.media_path || '/srv/media' } - ]; - - return E('div', { 'style': 'display: flex; flex-direction: column; gap: 8px;' }, - checks.map(function(c) { - return E('div', { 'style': 'display: flex; justify-content: space-between; padding: 10px 0; border-bottom: 1px solid var(--kiss-line);' }, [ - E('span', { 'style': 'color: var(--kiss-muted);' }, c.label), - E('span', { 'style': 'font-family: monospace;' }, c.value) - ]); - }) - ); + ['btn-start', 'btn-rescan'].forEach(function(id) { + var el = document.getElementById(id); + if (el) el.disabled = s.running; + }); + var stopBtn = document.getElementById('btn-stop'); + if (stopBtn) stopBtn.disabled = !s.running; }, handleInstall: function() { - var self = this; - ui.showModal('Installing Lyrion', [ - E('p', { 'class': 'spinning' }, 'Installing Lyrion Music Server. This may take several minutes...') + ui.showModal(_('Installing'), [ + E('p', { 'class': 'spinning' }, _('Installing Lyrion Music Server...')) ]); callInstall().then(function(r) { ui.hideModal(); if (r.success) { - ui.addNotification(null, E('p', r.message || 'Installation started')); - self.startPolling(); - window.location.reload(); + ui.addNotification(null, E('p', _('Installation started'))); + setTimeout(function() { location.reload(); }, 3000); } else { - ui.addNotification(null, E('p', 'Failed: ' + (r.error || 'Unknown error')), 'error'); + ui.addNotification(null, E('p', r.error || _('Installation failed')), 'error'); } }); }, handleStart: function() { - ui.showModal('Starting...', [E('p', { 'class': 'spinning' }, 'Starting Lyrion...')]); - callStart().then(function(r) { - ui.hideModal(); - if (r.success) ui.addNotification(null, E('p', 'Lyrion started')); + callStart().then(function() { + ui.addNotification(null, E('p', _('Lyrion started'))); }); }, handleStop: function() { - ui.showModal('Stopping...', [E('p', { 'class': 'spinning' }, 'Stopping Lyrion...')]); - callStop().then(function(r) { - ui.hideModal(); - if (r.success) ui.addNotification(null, E('p', 'Lyrion stopped')); - }); - }, - - handleRestart: function() { - ui.showModal('Restarting...', [E('p', { 'class': 'spinning' }, 'Restarting Lyrion...')]); - callRestart().then(function(r) { - ui.hideModal(); - if (r.success) ui.addNotification(null, E('p', 'Lyrion restarted')); + callStop().then(function() { + ui.addNotification(null, E('p', _('Lyrion stopped'))); }); }, handleRescan: function() { - callRescan().then(function(r) { - if (r.success) ui.addNotification(null, E('p', 'Library rescan started')); + callRescan().then(function() { + ui.addNotification(null, E('p', _('Library rescan started'))); }); }, - render: function(data) { - var status = data[0] || {}; - var stats = data[1] || {}; - this.libraryStats = stats; + render: function(status) { + var s = status || {}; - // Not installed view - if (!status.installed) { - var notInstalledContent = [ - E('div', { 'style': 'margin-bottom: 24px;' }, [ - E('div', { 'style': 'display: flex; align-items: center; gap: 16px;' }, [ - E('h2', { 'style': 'font-size: 24px; font-weight: 700; margin: 0;' }, 'Lyrion Music Server'), - KissTheme.badge('NOT INSTALLED', 'red') - ]), - E('p', { 'style': 'color: var(--kiss-muted); margin: 8px 0 0 0;' }, 'Self-hosted music streaming with Squeezebox compatibility') - ]), - - KissTheme.card('Install', E('div', { 'style': 'text-align: center; padding: 40px;' }, [ - E('div', { 'style': 'font-size: 4rem; margin-bottom: 16px;' }, '🎵'), - E('h3', { 'style': 'margin: 0 0 8px 0;' }, 'Lyrion Music Server'), - E('p', { 'style': 'color: var(--kiss-muted); margin: 0 0 20px 0;' }, 'Self-hosted music streaming with Squeezebox compatibility.'), - E('button', { - 'class': 'kiss-btn kiss-btn-green', - 'click': ui.createHandlerFn(this, 'handleInstall'), - 'disabled': status.detected_runtime === 'none' - }, 'Install Lyrion') - ])) - ]; - - return KissTheme.wrap(notInstalledContent, 'admin/services/lyrion/overview'); + // Not installed + if (!s.installed) { + return E('div', { 'class': 'cbi-map' }, [ + E('h2', {}, _('Lyrion Music Server')), + E('div', { 'class': 'cbi-section' }, [ + E('div', { 'style': 'text-align:center;padding:40px' }, [ + E('p', { 'style': 'font-size:48px;margin:0' }, '🎵'), + E('h3', {}, _('Lyrion Music Server')), + E('p', {}, _('Self-hosted music streaming with Squeezebox compatibility.')), + E('button', { + 'class': 'cbi-button cbi-button-positive', + 'click': ui.createHandlerFn(this, 'handleInstall') + }, _('Install Lyrion')) + ]) + ]) + ]); } - // Installed view + // Installed this.startPolling(); - var content = [ - // Header - E('div', { 'style': 'margin-bottom: 24px;' }, [ - E('div', { 'style': 'display: flex; align-items: center; gap: 16px;' }, [ - E('h2', { 'style': 'font-size: 24px; font-weight: 700; margin: 0;' }, 'Lyrion Music Server'), - E('span', { 'id': 'lyrion-status-badge' }, [ - KissTheme.badge(status.running ? 'RUNNING' : 'STOPPED', status.running ? 'green' : 'red') - ]) - ]), - E('p', { 'style': 'color: var(--kiss-muted); margin: 8px 0 0 0;' }, 'Self-hosted music streaming') + return E('div', { 'class': 'cbi-map' }, [ + E('h2', {}, [ + '🎵 ', + _('Lyrion Music Server'), + ' ', + E('span', { 'id': 'status-badge', 'style': 'font-size:14px' }, + s.running + ? E('span', { 'style': 'color:#4caf50;font-weight:600' }, '● Running') + : E('span', { 'style': 'color:#f44336;font-weight:600' }, '● Stopped') + ) ]), - // Stats - E('div', { 'class': 'kiss-grid kiss-grid-4', 'id': 'lyrion-stats', 'style': 'margin: 20px 0;' }, this.renderStats(stats)), - - // Scan progress - KissTheme.card('Library Status', E('div', { 'id': 'lyrion-scan' }, [this.renderScanStatus(stats)])), - - // Two-column layout - E('div', { 'class': 'kiss-grid kiss-grid-2' }, [ - KissTheme.card('Service Info', this.renderServiceInfo(status)), - KissTheme.card('Controls', this.renderControls(status)) - ]), - - // Web UI link - status.running && status.web_accessible ? KissTheme.card('Web Interface', - E('div', { 'style': 'display: flex; align-items: center; gap: 16px;' }, [ - E('div', { 'style': 'font-size: 2rem;' }, '🌐'), - E('div', { 'style': 'flex: 1;' }, [ - E('div', { 'style': 'font-weight: 600;' }, 'Lyrion Web Interface'), - E('div', { 'style': 'font-family: monospace; font-size: 12px; color: var(--kiss-purple);' }, status.web_url) - ]), - E('a', { - 'href': status.web_url, - 'target': '_blank', - 'class': 'kiss-btn kiss-btn-blue', - 'style': 'text-decoration: none;' - }, 'Open') + E('div', { 'class': 'cbi-section' }, [ + E('h3', {}, _('Controls')), + E('div', { 'class': 'cbi-value' }, [ + E('button', { + 'id': 'btn-start', + 'class': 'cbi-button cbi-button-positive', + 'click': ui.createHandlerFn(this, 'handleStart'), + 'disabled': s.running, + 'style': 'margin-right:8px' + }, _('Start')), + E('button', { + 'id': 'btn-stop', + 'class': 'cbi-button cbi-button-negative', + 'click': ui.createHandlerFn(this, 'handleStop'), + 'disabled': !s.running, + 'style': 'margin-right:8px' + }, _('Stop')), + E('button', { + 'id': 'btn-rescan', + 'class': 'cbi-button', + 'click': ui.createHandlerFn(this, 'handleRescan'), + 'disabled': !s.running + }, _('Rescan Library')) ]) - ) : '' - ]; + ]), - return KissTheme.wrap(content, 'admin/services/lyrion/overview'); + E('div', { 'class': 'cbi-section' }, [ + E('h3', {}, _('Service Info')), + E('table', { 'class': 'table' }, [ + E('tr', { 'class': 'tr' }, [ + E('td', { 'class': 'td', 'style': 'width:150px' }, _('Port')), + E('td', { 'class': 'td' }, String(s.port || 9000)) + ]), + E('tr', { 'class': 'tr' }, [ + E('td', { 'class': 'td' }, _('Runtime')), + E('td', { 'class': 'td' }, s.detected_runtime || 'auto') + ]), + E('tr', { 'class': 'tr' }, [ + E('td', { 'class': 'td' }, _('Media Path')), + E('td', { 'class': 'td' }, s.media_path || '/srv/media') + ]), + E('tr', { 'class': 'tr' }, [ + E('td', { 'class': 'td' }, _('Library')), + E('td', { 'class': 'td', 'id': 'library-stats' }, + (s.songs || 0) + ' songs, ' + (s.albums || 0) + ' albums, ' + (s.artists || 0) + ' artists') + ]) + ]) + ]), + + s.running && s.web_accessible ? E('div', { 'class': 'cbi-section' }, [ + E('h3', {}, _('Web Interface')), + E('a', { + 'href': s.web_url || ('http://192.168.255.1:' + (s.port || 9000)), + 'target': '_blank', + 'class': 'cbi-button cbi-button-action' + }, _('Open Lyrion Web UI')) + ]) : '' + ]); }, handleSaveApply: null, diff --git a/package/secubox/luci-app-lyrion/htdocs/luci-static/resources/view/lyrion/settings.js b/package/secubox/luci-app-lyrion/htdocs/luci-static/resources/view/lyrion/settings.js index d190641c..2c953c07 100644 --- a/package/secubox/luci-app-lyrion/htdocs/luci-static/resources/view/lyrion/settings.js +++ b/package/secubox/luci-app-lyrion/htdocs/luci-static/resources/view/lyrion/settings.js @@ -2,7 +2,6 @@ 'require view'; 'require form'; 'require uci'; -'require secubox/kiss-theme'; return view.extend({ load: function() { @@ -12,67 +11,28 @@ return view.extend({ render: function() { var m, s, o; - m = new form.Map('lyrion', _('Lyrion Settings'), - _('Configure Lyrion Music Server settings. Changes require service restart to take effect.')); + m = new form.Map('lyrion', _('Lyrion Settings')); - s = m.section(form.TypedSection, 'lyrion', _('General Settings')); + s = m.section(form.TypedSection, 'lyrion'); s.anonymous = true; s.addremove = false; - o = s.option(form.Flag, 'enabled', _('Enabled'), - _('Enable Lyrion Music Server')); + o = s.option(form.Flag, 'enabled', _('Enabled')); o.default = '0'; - o.rmempty = false; - o = s.option(form.ListValue, 'runtime', _('Container Runtime'), - _('Select the container runtime to use')); - o.value('auto', _('Auto-detect (LXC preferred)')); - o.value('lxc', _('LXC Container')); - o.value('docker', _('Docker')); - o.default = 'auto'; - - o = s.option(form.Value, 'port', _('Web UI Port'), - _('Port for the Lyrion web interface')); + o = s.option(form.Value, 'port', _('Web UI Port')); o.datatype = 'port'; o.default = '9000'; - o.placeholder = '9000'; - o = s.option(form.Value, 'data_path', _('Data Path'), - _('Path to store Lyrion configuration and cache')); - o.default = '/srv/lyrion'; - o.placeholder = '/srv/lyrion'; - - o = s.option(form.Value, 'media_path', _('Media Path'), - _('Path to your music library')); + o = s.option(form.Value, 'media_path', _('Media Path')); o.default = '/srv/media'; - o.placeholder = '/srv/media'; - o = s.option(form.Value, 'memory_limit', _('Memory Limit'), - _('Maximum memory for the container (e.g., 256M, 512M, 1G)')); + o = s.option(form.Value, 'data_path', _('Data Path')); + o.default = '/srv/lyrion'; + + o = s.option(form.Value, 'memory_limit', _('Memory Limit')); o.default = '256M'; - o.placeholder = '256M'; - o = s.option(form.Value, 'timezone', _('Timezone'), - _('Timezone for the container')); - o.default = 'UTC'; - o.placeholder = 'UTC'; - - o = s.option(form.Flag, 'wan_access', _('WAN Access'), - _('Also open Lyrion ports on the WAN interface (remote access)')); - o.default = '0'; - o.rmempty = false; - - o = s.option(form.Value, 'image', _('Docker Image'), - _('Docker image to use (only for Docker runtime)')); - o.default = 'ghcr.io/lms-community/lyrionmusicserver:stable'; - o.depends('runtime', 'docker'); - - return m.render().then(function(node) { - return KissTheme.wrap(node, 'admin/secubox/services/lyrion/settings'); - }); - }, - - handleSaveApply: null, - handleSave: null, - handleReset: null + return m.render(); + } }); diff --git a/package/secubox/luci-app-lyrion/root/usr/libexec/rpcd/luci.lyrion b/package/secubox/luci-app-lyrion/root/usr/libexec/rpcd/luci.lyrion index 874bfc27..24449bc6 100755 --- a/package/secubox/luci-app-lyrion/root/usr/libexec/rpcd/luci.lyrion +++ b/package/secubox/luci-app-lyrion/root/usr/libexec/rpcd/luci.lyrion @@ -1,300 +1,109 @@ #!/bin/sh -# RPCD backend for Lyrion Music Server LuCI app +# Lyrion Music Server RPCD handler . /lib/functions.sh CONFIG="lyrion" -json_init() { echo "{"; } -json_close() { echo "}"; } -json_add_string() { echo "\"$1\": \"$2\""; } -json_add_int() { echo "\"$1\": $2"; } -json_add_bool() { [ "$2" = "1" ] && echo "\"$1\": true" || echo "\"$1\": false"; } - uci_get() { uci -q get ${CONFIG}.main.$1; } -uci_set() { uci set ${CONFIG}.main.$1="$2" && uci commit ${CONFIG}; } -# Get service status get_status() { - local enabled=$(uci_get enabled) - local runtime=$(uci_get runtime) local port=$(uci_get port) local data_path=$(uci_get data_path) local media_path=$(uci_get media_path) - local memory_limit=$(uci_get memory_limit) - local image=$(uci_get image) + port=${port:-9000} - # Check if service is running + # Check running state local running=0 - local container_status="stopped" - - if command -v lxc-info >/dev/null 2>&1; then - if lxc-info -n lyrion -s 2>/dev/null | grep -q "RUNNING"; then - running=1 - container_status="running" - fi - elif command -v docker >/dev/null 2>&1; then - if docker ps --filter "name=secbx-lyrion" --format "{{.Names}}" 2>/dev/null | grep -q "secbx-lyrion"; then - running=1 - container_status="running" - fi + if command -v lxc-info >/dev/null 2>&1 && lxc-info -n lyrion -s 2>/dev/null | grep -q "RUNNING"; then + running=1 + elif command -v docker >/dev/null 2>&1 && docker ps --filter "name=secbx-lyrion" -q 2>/dev/null | grep -q .; then + running=1 fi - # Check if installed (LXC rootfs or Docker image exists) + # Check installed local installed=0 - if [ -d "/srv/lxc/lyrion/rootfs" ] && [ -f "/srv/lxc/lyrion/rootfs/opt/lyrion/slimserver.pl" ]; then - installed=1 - elif command -v docker >/dev/null 2>&1 && docker images --format "{{.Repository}}" 2>/dev/null | grep -q "lyrionmusicserver"; then - installed=1 - fi + [ -d "/srv/lxc/lyrion/rootfs" ] && installed=1 + [ "$installed" = "0" ] && command -v docker >/dev/null 2>&1 && docker images 2>/dev/null | grep -q lyrionmusicserver && installed=1 # Detect runtime - local detected_runtime="none" - if command -v lxc-start >/dev/null 2>&1; then - detected_runtime="lxc" - elif command -v docker >/dev/null 2>&1; then - detected_runtime="docker" - fi + local runtime="none" + command -v lxc-start >/dev/null 2>&1 && runtime="lxc" + [ "$runtime" = "none" ] && command -v docker >/dev/null 2>&1 && runtime="docker" - # Check web UI accessibility + # Check web access local web_accessible=0 + [ "$running" = "1" ] && wget -q -O /dev/null --timeout=2 "http://127.0.0.1:${port}/" 2>/dev/null && web_accessible=1 + + # Get library stats if running + local songs=0 albums=0 artists=0 if [ "$running" = "1" ]; then - wget -q -O /dev/null --timeout=2 "http://127.0.0.1:${port:-9000}/" 2>/dev/null && web_accessible=1 + local resp=$(curl -s --max-time 2 "http://127.0.0.1:${port}/jsonrpc.js" \ + -H "Content-Type: application/json" \ + -d '{"id":1,"method":"slim.request","params":["",["serverstatus",0,0]]}' 2>/dev/null) + if [ -n "$resp" ]; then + songs=$(echo "$resp" | jsonfilter -e '@.result["info total songs"]' 2>/dev/null || echo 0) + albums=$(echo "$resp" | jsonfilter -e '@.result["info total albums"]' 2>/dev/null || echo 0) + artists=$(echo "$resp" | jsonfilter -e '@.result["info total artists"]' 2>/dev/null || echo 0) + fi fi cat </dev/null) - local port=$(echo "$input" | jsonfilter -e '@.port' 2>/dev/null) - local data_path=$(echo "$input" | jsonfilter -e '@.data_path' 2>/dev/null) - local media_path=$(echo "$input" | jsonfilter -e '@.media_path' 2>/dev/null) - local memory_limit=$(echo "$input" | jsonfilter -e '@.memory_limit' 2>/dev/null) - local timezone=$(echo "$input" | jsonfilter -e '@.timezone' 2>/dev/null) - - [ -n "$runtime" ] && uci_set runtime "$runtime" - [ -n "$port" ] && uci_set port "$port" - [ -n "$data_path" ] && uci_set data_path "$data_path" - [ -n "$media_path" ] && uci_set media_path "$media_path" - [ -n "$memory_limit" ] && uci_set memory_limit "$memory_limit" - [ -n "$timezone" ] && uci_set timezone "$timezone" - - echo '{"success": true}' -} - -# Install Lyrion do_install() { if command -v lyrionctl >/dev/null 2>&1; then lyrionctl install >/tmp/lyrion-install.log 2>&1 & - echo '{"success": true, "message": "Installation started in background"}' + echo '{"success":true}' else - echo '{"success": false, "error": "lyrionctl not found"}' + echo '{"success":false,"error":"lyrionctl not found"}' fi } -# Start service do_start() { - if [ -x /etc/init.d/lyrion ]; then - /etc/init.d/lyrion start >/dev/null 2>&1 - uci_set enabled '1' - echo '{"success": true}' - else - echo '{"success": false, "error": "Service not installed"}' - fi + [ -x /etc/init.d/lyrion ] && /etc/init.d/lyrion start >/dev/null 2>&1 + echo '{"success":true}' } -# Stop service do_stop() { - if [ -x /etc/init.d/lyrion ]; then - /etc/init.d/lyrion stop >/dev/null 2>&1 - echo '{"success": true}' - else - echo '{"success": false, "error": "Service not installed"}' - fi + [ -x /etc/init.d/lyrion ] && /etc/init.d/lyrion stop >/dev/null 2>&1 + echo '{"success":true}' } -# Restart service -do_restart() { - if [ -x /etc/init.d/lyrion ]; then - /etc/init.d/lyrion restart >/dev/null 2>&1 - echo '{"success": true}' - else - echo '{"success": false, "error": "Service not installed"}' - fi -} - -# Update container -do_update() { - if command -v lyrionctl >/dev/null 2>&1; then - lyrionctl update >/tmp/lyrion-update.log 2>&1 & - echo '{"success": true, "message": "Update started in background"}' - else - echo '{"success": false, "error": "lyrionctl not found"}' - fi -} - -# Get logs -get_logs() { - local lines=50 - local log_content="" - - if [ -f /srv/lxc/lyrion/rootfs/var/log/lyrion/server.log ]; then - log_content=$(tail -n $lines /srv/lxc/lyrion/rootfs/var/log/lyrion/server.log 2>/dev/null | sed 's/"/\\"/g' | tr '\n' '|') - elif [ -f /tmp/lyrion-install.log ]; then - log_content=$(tail -n $lines /tmp/lyrion-install.log 2>/dev/null | sed 's/"/\\"/g' | tr '\n' '|') - fi - - echo "{\"logs\": \"$log_content\"}" -} - -# Get library stats from Lyrion API -get_library_stats() { - local port=$(uci_get port) - port=${port:-9000} - - # Query Lyrion JSON-RPC API for server status - local response=$(curl -s --max-time 3 "http://127.0.0.1:${port}/jsonrpc.js" \ - -H "Content-Type: application/json" \ - -d '{"id":1,"method":"slim.request","params":["", ["serverstatus", 0, 0]]}' 2>/dev/null) - - if [ -z "$response" ]; then - echo '{"songs":0,"albums":0,"artists":0,"genres":0,"scanning":false,"scan_progress":0,"scan_total":0,"scan_phase":""}' - return - fi - - # Parse response using jsonfilter - local songs=$(echo "$response" | jsonfilter -e '@.result["info total songs"]' 2>/dev/null || echo 0) - local albums=$(echo "$response" | jsonfilter -e '@.result["info total albums"]' 2>/dev/null || echo 0) - local artists=$(echo "$response" | jsonfilter -e '@.result["info total artists"]' 2>/dev/null || echo 0) - local genres=$(echo "$response" | jsonfilter -e '@.result["info total genres"]' 2>/dev/null || echo 0) - local rescan=$(echo "$response" | jsonfilter -e '@.result.rescan' 2>/dev/null || echo 0) - local progress_done=$(echo "$response" | jsonfilter -e '@.result.progressdone' 2>/dev/null || echo 0) - local progress_total=$(echo "$response" | jsonfilter -e '@.result.progresstotal' 2>/dev/null || echo 0) - local progress_name=$(echo "$response" | jsonfilter -e '@.result.progressname' 2>/dev/null || echo "") - - # Get database size - local db_size="0" - if [ -f /srv/lyrion/cache/library.db ]; then - db_size=$(ls -lh /srv/lyrion/cache/library.db 2>/dev/null | awk '{print $5}') - fi - - local scanning="false" - [ "$rescan" = "1" ] && scanning="true" - - cat </dev/null 2>&1 - - echo '{"success": true, "message": "Rescan started"}' + -d '{"id":1,"method":"slim.request","params":["",["rescan","full"]]}' >/dev/null 2>&1 + echo '{"success":true}' } -# RPCD list method -list_methods() { - cat <<'EOF' -{ - "status": {}, - "get_config": {}, - "save_config": {"runtime": "string", "port": "string", "data_path": "string", "media_path": "string", "memory_limit": "string", "timezone": "string"}, - "install": {}, - "start": {}, - "stop": {}, - "restart": {}, - "update": {}, - "logs": {}, - "get_library_stats": {}, - "rescan": {} -} -EOF -} - -# Main entry point case "$1" in list) - list_methods + echo '{"status":{},"install":{},"start":{},"stop":{},"rescan":{}}' ;; call) case "$2" in - status) get_status ;; - get_config) get_config ;; - save_config) save_config ;; - install) do_install ;; - start) do_start ;; - stop) do_stop ;; - restart) do_restart ;; - update) do_update ;; - logs) get_logs ;; - get_library_stats) get_library_stats ;; - rescan) do_rescan ;; - *) echo '{"error": "Unknown method"}' ;; + status) get_status ;; + install) do_install ;; + start) do_start ;; + stop) do_stop ;; + rescan) do_rescan ;; + *) echo '{"error":"Unknown method"}' ;; esac ;; - *) - echo '{"error": "Unknown command"}' - ;; esac diff --git a/package/secubox/luci-app-lyrion/root/usr/share/rpcd/acl.d/luci-app-lyrion.json b/package/secubox/luci-app-lyrion/root/usr/share/rpcd/acl.d/luci-app-lyrion.json index 5fe3751a..caa9089b 100644 --- a/package/secubox/luci-app-lyrion/root/usr/share/rpcd/acl.d/luci-app-lyrion.json +++ b/package/secubox/luci-app-lyrion/root/usr/share/rpcd/acl.d/luci-app-lyrion.json @@ -3,13 +3,13 @@ "description": "Grant access to Lyrion Music Server", "read": { "ubus": { - "luci.lyrion": ["status", "get_config", "logs", "get_library_stats"] + "luci.lyrion": ["status"] }, "uci": ["lyrion"] }, "write": { "ubus": { - "luci.lyrion": ["install", "start", "stop", "restart", "update", "save_config", "rescan"] + "luci.lyrion": ["install", "start", "stop", "rescan"] }, "uci": ["lyrion"] }