1
+ // Empty state UI components
2
+ PulseApp . ui = PulseApp . ui || { } ;
3
+
4
+ PulseApp . ui . emptyStates = ( ( ) => {
5
+
6
+ function createEmptyState ( type , context = { } ) {
7
+ const emptyStates = {
8
+ 'no-guests' : {
9
+ icon : `<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="mx-auto mb-4 text-gray-300 dark:text-gray-600">
10
+ <rect width="20" height="14" x="2" y="5" rx="2" ry="2"/>
11
+ <line x1="2" y1="10" x2="22" y2="10"/>
12
+ </svg>` ,
13
+ title : 'No Virtual Machines or Containers' ,
14
+ message : 'No VMs or containers are currently configured on this node.' ,
15
+ actions : [ ]
16
+ } ,
17
+ 'no-results' : {
18
+ icon : `<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="mx-auto mb-4 text-gray-300 dark:text-gray-600">
19
+ <circle cx="11" cy="11" r="8"/>
20
+ <path d="m21 21-4.35-4.35"/>
21
+ <line x1="11" y1="8" x2="11" y2="14"/>
22
+ <line x1="8" y1="11" x2="14" y2="11"/>
23
+ </svg>` ,
24
+ title : 'No Matching Results' ,
25
+ message : _buildFilterMessage ( context ) ,
26
+ actions : [ {
27
+ text : 'Clear Filters' ,
28
+ onclick : 'PulseApp.ui.common.resetDashboardView()'
29
+ } ]
30
+ } ,
31
+ 'no-storage' : {
32
+ icon : `<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="mx-auto mb-4 text-gray-300 dark:text-gray-600">
33
+ <ellipse cx="12" cy="5" rx="9" ry="3"/>
34
+ <path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"/>
35
+ <path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"/>
36
+ </svg>` ,
37
+ title : 'No Storage Configured' ,
38
+ message : 'No storage repositories are configured on this system.' ,
39
+ actions : [ ]
40
+ } ,
41
+ 'no-backups' : {
42
+ icon : `<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="mx-auto mb-4 text-gray-300 dark:text-gray-600">
43
+ <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
44
+ <path d="m9 12 2 2 4-4"/>
45
+ </svg>` ,
46
+ title : 'No Backups Found' ,
47
+ message : context . filtered ? 'No backups match your current filters.' : 'No backup data is available yet.' ,
48
+ actions : context . filtered ? [ {
49
+ text : 'Clear Filters' ,
50
+ onclick : 'PulseApp.ui.backups.resetBackupsView()'
51
+ } ] : [ ]
52
+ } ,
53
+ 'no-pbs' : {
54
+ icon : `<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="mx-auto mb-4 text-gray-300 dark:text-gray-600">
55
+ <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
56
+ <line x1="12" y1="8" x2="12" y2="12"/>
57
+ <line x1="12" y1="16" x2="12.01" y2="16"/>
58
+ </svg>` ,
59
+ title : 'No Proxmox Backup Servers' ,
60
+ message : 'No PBS instances are configured or available.' ,
61
+ actions : [ ]
62
+ } ,
63
+ 'loading' : {
64
+ icon : `<div class="mx-auto mb-4">
65
+ <div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500"></div>
66
+ </div>` ,
67
+ title : 'Loading...' ,
68
+ message : 'Fetching data from the server.' ,
69
+ actions : [ ]
70
+ } ,
71
+ 'error' : {
72
+ icon : `<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="mx-auto mb-4 text-red-400 dark:text-red-600">
73
+ <circle cx="12" cy="12" r="10"/>
74
+ <line x1="12" y1="8" x2="12" y2="12"/>
75
+ <line x1="12" y1="16" x2="12.01" y2="16"/>
76
+ </svg>` ,
77
+ title : 'Error Loading Data' ,
78
+ message : context . error || 'Failed to load data. Please try again.' ,
79
+ actions : [ {
80
+ text : 'Retry' ,
81
+ onclick : 'location.reload()'
82
+ } ]
83
+ }
84
+ } ;
85
+
86
+ const state = emptyStates [ type ] || emptyStates [ 'no-results' ] ;
87
+
88
+ return `
89
+ <div class="flex flex-col items-center justify-center py-12 px-4">
90
+ ${ state . icon }
91
+ <h3 class="text-lg font-medium text-gray-700 dark:text-gray-300 mb-2">${ state . title } </h3>
92
+ <p class="text-sm text-gray-700 dark:text-gray-300 text-center max-w-md mb-6">${ state . message } </p>
93
+ ${ state . actions . length > 0 ? `
94
+ <div class="flex gap-3">
95
+ ${ state . actions . map ( action => `
96
+ <button onclick="${ action . onclick } " class="px-4 py-2 bg-blue-500 text-white text-sm rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800 transition-colors">
97
+ ${ action . text }
98
+ </button>
99
+ ` ) . join ( '' ) }
100
+ </div>
101
+ ` : '' }
102
+ </div>
103
+ ` ;
104
+ }
105
+
106
+ function _buildFilterMessage ( context ) {
107
+ const filters = [ ] ;
108
+
109
+ if ( context . filterType && context . filterType !== 'all' ) {
110
+ filters . push ( `Type: ${ context . filterType . toUpperCase ( ) } ` ) ;
111
+ }
112
+
113
+ if ( context . filterStatus && context . filterStatus !== 'all' ) {
114
+ filters . push ( `Status: ${ context . filterStatus } ` ) ;
115
+ }
116
+
117
+ if ( context . searchTerms && context . searchTerms . length > 0 ) {
118
+ filters . push ( `Search: "${ context . searchTerms . join ( ', ' ) } "` ) ;
119
+ }
120
+
121
+ if ( context . thresholds && context . thresholds . length > 0 ) {
122
+ filters . push ( `Thresholds: ${ context . thresholds . join ( ', ' ) } ` ) ;
123
+ }
124
+
125
+ if ( filters . length === 0 ) {
126
+ return 'No items match your current view.' ;
127
+ }
128
+
129
+ return `No items found matching: ${ filters . join ( ' • ' ) } ` ;
130
+ }
131
+
132
+ function createTableEmptyState ( type , context , colspan ) {
133
+ const emptyStateHtml = createEmptyState ( type , context ) ;
134
+ return `<tr><td colspan="${ colspan } " class="p-0">${ emptyStateHtml } </td></tr>` ;
135
+ }
136
+
137
+ return {
138
+ createEmptyState,
139
+ createTableEmptyState
140
+ } ;
141
+ } ) ( ) ;
0 commit comments