@@ -564,5 +564,294 @@ const appsModule = {
564564 }
565565} ;
566566
567+ // Apps Dashboard functionality
568+ const appsDashboard = {
569+ statusCheckInterval : null ,
570+ activityRefreshInterval : null ,
571+
572+ // Initialize the dashboard
573+ init : function ( ) {
574+ console . log ( '[Apps Dashboard] Initializing...' ) ;
575+ this . checkAllAppStatus ( ) ;
576+ this . loadRecentActivity ( ) ;
577+ this . loadConfigurationSummary ( ) ;
578+ this . startAutoRefresh ( ) ;
579+ } ,
580+
581+ // Check status of all apps
582+ checkAllAppStatus : function ( ) {
583+ const apps = [ 'sonarr' , 'radarr' , 'lidarr' , 'readarr' , 'whisparr' , 'eros' , 'prowlarr' ] ;
584+
585+ apps . forEach ( app => {
586+ this . checkAppStatus ( app ) ;
587+ } ) ;
588+ } ,
589+
590+ // Check status of a specific app
591+ checkAppStatus : function ( app ) {
592+ const statusElement = document . getElementById ( `${ app } Status` ) ;
593+ const instancesElement = document . getElementById ( `${ app } Instances` ) ;
594+
595+ if ( ! statusElement ) return ;
596+
597+ // Set checking status
598+ statusElement . textContent = 'Checking...' ;
599+ statusElement . className = 'status-badge checking' ;
600+
601+ // Fetch app settings to check configuration and instances
602+ HuntarrUtils . fetchWithTimeout ( `./api/settings/${ app } ` )
603+ . then ( response => {
604+ if ( ! response . ok ) {
605+ throw new Error ( `HTTP ${ response . status } ` ) ;
606+ }
607+ return response . json ( ) ;
608+ } )
609+ . then ( settings => {
610+ let instanceCount = 0 ;
611+ let onlineCount = 0 ;
612+
613+ if ( settings . instances && Array . isArray ( settings . instances ) ) {
614+ instanceCount = settings . instances . length ;
615+
616+ // Check each instance status
617+ const statusPromises = settings . instances . map ( instance => {
618+ return this . checkInstanceConnection ( app , instance )
619+ . then ( isOnline => {
620+ if ( isOnline ) onlineCount ++ ;
621+ return isOnline ;
622+ } )
623+ . catch ( ( ) => false ) ;
624+ } ) ;
625+
626+ Promise . all ( statusPromises ) . then ( ( ) => {
627+ this . updateAppStatus ( app , instanceCount , onlineCount ) ;
628+ } ) ;
629+ } else {
630+ this . updateAppStatus ( app , 0 , 0 ) ;
631+ }
632+ } )
633+ . catch ( error => {
634+ console . error ( `Error checking ${ app } status:` , error ) ;
635+ statusElement . textContent = 'Error' ;
636+ statusElement . className = 'status-badge offline' ;
637+ if ( instancesElement ) {
638+ instancesElement . textContent = 'Not configured' ;
639+ }
640+ } ) ;
641+ } ,
642+
643+ // Check if a specific instance is online
644+ checkInstanceConnection : function ( app , instance ) {
645+ if ( ! instance . url || ! instance . api_key ) {
646+ return Promise . resolve ( false ) ;
647+ }
648+
649+ const testUrl = `${ instance . url } /api/v3/system/status` ;
650+
651+ return fetch ( testUrl , {
652+ method : 'GET' ,
653+ headers : {
654+ 'X-Api-Key' : instance . api_key ,
655+ 'Content-Type' : 'application/json'
656+ } ,
657+ timeout : 5000
658+ } )
659+ . then ( response => response . ok )
660+ . catch ( ( ) => false ) ;
661+ } ,
662+
663+ // Update app status display
664+ updateAppStatus : function ( app , totalInstances , onlineInstances ) {
665+ const statusElement = document . getElementById ( `${ app } Status` ) ;
666+ const instancesElement = document . getElementById ( `${ app } Instances` ) ;
667+
668+ if ( ! statusElement ) return ;
669+
670+ if ( totalInstances === 0 ) {
671+ statusElement . textContent = 'Not configured' ;
672+ statusElement . className = 'status-badge offline' ;
673+ if ( instancesElement ) {
674+ instancesElement . textContent = 'No instances' ;
675+ }
676+ } else if ( onlineInstances === totalInstances ) {
677+ statusElement . textContent = 'Online' ;
678+ statusElement . className = 'status-badge online' ;
679+ if ( instancesElement ) {
680+ instancesElement . textContent = `${ totalInstances } instance${ totalInstances > 1 ? 's' : '' } ` ;
681+ }
682+ } else if ( onlineInstances > 0 ) {
683+ statusElement . textContent = 'Partial' ;
684+ statusElement . className = 'status-badge checking' ;
685+ if ( instancesElement ) {
686+ instancesElement . textContent = `${ onlineInstances } /${ totalInstances } online` ;
687+ }
688+ } else {
689+ statusElement . textContent = 'Offline' ;
690+ statusElement . className = 'status-badge offline' ;
691+ if ( instancesElement ) {
692+ instancesElement . textContent = `${ totalInstances } instance${ totalInstances > 1 ? 's' : '' } offline` ;
693+ }
694+ }
695+ } ,
696+
697+ // Load recent activity
698+ loadRecentActivity : function ( ) {
699+ const activityList = document . getElementById ( 'recentActivity' ) ;
700+ if ( ! activityList ) return ;
701+
702+ // Simulate loading recent activity (replace with actual API call)
703+ setTimeout ( ( ) => {
704+ const activities = [
705+ { icon : 'fas fa-download' , text : 'Sonarr: Downloaded "The Office S09E23"' , time : '2 minutes ago' , type : 'success' } ,
706+ { icon : 'fas fa-search' , text : 'Radarr: Searching for "Inception (2010)"' , time : '5 minutes ago' , type : 'info' } ,
707+ { icon : 'fas fa-music' , text : 'Lidarr: Added "Pink Floyd - Dark Side of the Moon"' , time : '12 minutes ago' , type : 'success' } ,
708+ { icon : 'fas fa-exclamation-triangle' , text : 'Prowlarr: Indexer "TorrentLeech" failed health check' , time : '18 minutes ago' , type : 'warning' } ,
709+ { icon : 'fas fa-book' , text : 'Readarr: Imported "Dune by Frank Herbert"' , time : '25 minutes ago' , type : 'success' }
710+ ] ;
711+
712+ activityList . innerHTML = activities . map ( activity => `
713+ <div class="activity-item ${ activity . type } ">
714+ <i class="${ activity . icon } activity-icon"></i>
715+ <div class="activity-content">
716+ <span class="activity-text">${ activity . text } </span>
717+ <span class="activity-time">${ activity . time } </span>
718+ </div>
719+ </div>
720+ ` ) . join ( '' ) ;
721+ } , 1000 ) ;
722+ } ,
723+
724+ // Load configuration summary
725+ loadConfigurationSummary : function ( ) {
726+ const configuredAppsElement = document . getElementById ( 'configuredAppsCount' ) ;
727+ const totalInstancesElement = document . getElementById ( 'totalInstancesCount' ) ;
728+ const activeConnectionsElement = document . getElementById ( 'activeConnectionsCount' ) ;
729+
730+ if ( ! configuredAppsElement ) return ;
731+
732+ const apps = [ 'sonarr' , 'radarr' , 'lidarr' , 'readarr' , 'whisparr' , 'eros' , 'prowlarr' ] ;
733+ let configuredApps = 0 ;
734+ let totalInstances = 0 ;
735+ let activeConnections = 0 ;
736+
737+ const promises = apps . map ( app => {
738+ return HuntarrUtils . fetchWithTimeout ( `./api/settings/${ app } ` )
739+ . then ( response => response . json ( ) )
740+ . then ( settings => {
741+ if ( settings . instances && settings . instances . length > 0 ) {
742+ configuredApps ++ ;
743+ totalInstances += settings . instances . length ;
744+
745+ // Count active connections (simplified)
746+ settings . instances . forEach ( instance => {
747+ if ( instance . url && instance . api_key ) {
748+ activeConnections ++ ;
749+ }
750+ } ) ;
751+ }
752+ } )
753+ . catch ( ( ) => { } ) ;
754+ } ) ;
755+
756+ Promise . all ( promises ) . then ( ( ) => {
757+ if ( configuredAppsElement ) configuredAppsElement . textContent = configuredApps ;
758+ if ( totalInstancesElement ) totalInstancesElement . textContent = totalInstances ;
759+ if ( activeConnectionsElement ) activeConnectionsElement . textContent = activeConnections ;
760+ } ) ;
761+ } ,
762+
763+ // Start auto-refresh intervals
764+ startAutoRefresh : function ( ) {
765+ // Refresh status every 30 seconds
766+ this . statusCheckInterval = setInterval ( ( ) => {
767+ this . checkAllAppStatus ( ) ;
768+ } , 30000 ) ;
769+
770+ // Refresh activity every 60 seconds
771+ this . activityRefreshInterval = setInterval ( ( ) => {
772+ this . loadRecentActivity ( ) ;
773+ } , 60000 ) ;
774+ } ,
775+
776+ // Stop auto-refresh intervals
777+ stopAutoRefresh : function ( ) {
778+ if ( this . statusCheckInterval ) {
779+ clearInterval ( this . statusCheckInterval ) ;
780+ this . statusCheckInterval = null ;
781+ }
782+ if ( this . activityRefreshInterval ) {
783+ clearInterval ( this . activityRefreshInterval ) ;
784+ this . activityRefreshInterval = null ;
785+ }
786+ }
787+ } ;
788+
789+ // Global functions for dashboard actions
790+ function navigateToApp ( app ) {
791+ console . log ( `Navigating to ${ app } app` ) ;
792+ window . location . hash = `#${ app } ` ;
793+ }
794+
795+ function refreshAppStatus ( ) {
796+ console . log ( 'Refreshing all app status...' ) ;
797+ if ( typeof appsDashboard !== 'undefined' ) {
798+ appsDashboard . checkAllAppStatus ( ) ;
799+ appsDashboard . loadConfigurationSummary ( ) ;
800+ }
801+ }
802+
803+ function refreshAllApps ( ) {
804+ console . log ( 'Refreshing all apps...' ) ;
805+ refreshAppStatus ( ) ;
806+ if ( typeof appsDashboard !== 'undefined' ) {
807+ appsDashboard . loadRecentActivity ( ) ;
808+ }
809+ }
810+
811+ function globalSearch ( ) {
812+ console . log ( 'Opening global search...' ) ;
813+ // Implement global search functionality
814+ alert ( 'Global search functionality coming soon!' ) ;
815+ }
816+
817+ function systemMaintenance ( ) {
818+ console . log ( 'Opening system maintenance...' ) ;
819+ // Implement system maintenance functionality
820+ alert ( 'System maintenance functionality coming soon!' ) ;
821+ }
822+
823+ // Initialize dashboard when Apps section is shown
824+ document . addEventListener ( 'DOMContentLoaded' , function ( ) {
825+ // Check if we're on the apps section and initialize dashboard
826+ const observer = new MutationObserver ( function ( mutations ) {
827+ mutations . forEach ( function ( mutation ) {
828+ if ( mutation . type === 'attributes' && mutation . attributeName === 'class' ) {
829+ const appsSection = document . getElementById ( 'appsSection' ) ;
830+ if ( appsSection && appsSection . classList . contains ( 'active' ) ) {
831+ if ( typeof appsDashboard !== 'undefined' ) {
832+ appsDashboard . init ( ) ;
833+ }
834+ } else {
835+ if ( typeof appsDashboard !== 'undefined' ) {
836+ appsDashboard . stopAutoRefresh ( ) ;
837+ }
838+ }
839+ }
840+ } ) ;
841+ } ) ;
842+
843+ const appsSection = document . getElementById ( 'appsSection' ) ;
844+ if ( appsSection ) {
845+ observer . observe ( appsSection , { attributes : true } ) ;
846+
847+ // Initialize immediately if already active
848+ if ( appsSection . classList . contains ( 'active' ) ) {
849+ if ( typeof appsDashboard !== 'undefined' ) {
850+ appsDashboard . init ( ) ;
851+ }
852+ }
853+ }
854+ } ) ;
855+
567856// Apps module is now initialized per-app when navigating to individual app sections
568857// No automatic initialization needed
0 commit comments