|
4 | 4 | import type { EVault } from '../../routes/api/evaults/+server';
|
5 | 5 |
|
6 | 6 | let evaults: EVault[] = [];
|
7 |
| - let loading = false; |
8 |
| - let cacheStatus: any = null; |
9 |
| - let lastRefresh = ''; |
| 7 | + let loading = true; |
| 8 | + let error: string | null = null; |
| 9 | + let cacheStatus: { lastUpdated: number; isStale: boolean; itemCount: number }; |
10 | 10 |
|
11 |
| - // Load eVaults on component mount |
12 | 11 | onMount(async () => {
|
13 |
| - await loadEVaults(); |
14 |
| - // Get cache status for debugging |
15 |
| - cacheStatus = EVaultService.getCacheStatus(); |
| 12 | + try { |
| 13 | + await loadEVaults(); |
| 14 | + cacheStatus = EVaultService.getCacheStatus(); |
| 15 | + } catch (err) { |
| 16 | + error = 'Failed to load eVaults'; |
| 17 | + console.error(err); |
| 18 | + } finally { |
| 19 | + loading = false; |
| 20 | + } |
16 | 21 | });
|
17 | 22 |
|
18 | 23 | async function loadEVaults() {
|
19 |
| - loading = true; |
20 | 24 | try {
|
21 |
| - // This will return cached data immediately and refresh in background |
22 | 25 | evaults = await EVaultService.getEVaults();
|
23 |
| - lastRefresh = new Date().toLocaleTimeString(); |
24 |
| - } catch (error) { |
25 |
| - console.error('Failed to load eVaults:', error); |
26 |
| - } finally { |
27 |
| - loading = false; |
| 26 | + cacheStatus = EVaultService.getCacheStatus(); |
| 27 | + } catch (err) { |
| 28 | + console.error('Error loading eVaults:', err); |
| 29 | + throw err; |
28 | 30 | }
|
29 | 31 | }
|
30 | 32 |
|
31 | 33 | async function forceRefresh() {
|
32 |
| - loading = true; |
33 | 34 | try {
|
34 |
| - // Force refresh and get fresh data |
| 35 | + loading = true; |
35 | 36 | evaults = await EVaultService.forceRefresh();
|
36 |
| - lastRefresh = new Date().toLocaleTimeString(); |
37 |
| - // Update cache status |
38 | 37 | cacheStatus = EVaultService.getCacheStatus();
|
39 |
| - } catch (error) { |
40 |
| - console.error('Failed to force refresh:', error); |
| 38 | + } catch (err) { |
| 39 | + error = 'Failed to refresh eVaults'; |
| 40 | + console.error(err); |
41 | 41 | } finally {
|
42 | 42 | loading = false;
|
43 | 43 | }
|
44 | 44 | }
|
45 | 45 |
|
46 | 46 | async function clearCache() {
|
47 |
| - await EVaultService.clearCache(); |
48 |
| - cacheStatus = EVaultService.getCacheStatus(); |
49 |
| - // Reload data |
50 |
| - await loadEVaults(); |
| 47 | + try { |
| 48 | + await EVaultService.clearCache(); |
| 49 | + evaults = []; |
| 50 | + cacheStatus = EVaultService.getCacheStatus(); |
| 51 | + } catch (err) { |
| 52 | + console.error('Error clearing cache:', err); |
| 53 | + } |
| 54 | + } |
| 55 | +
|
| 56 | + function formatTimestamp(timestamp: number): string { |
| 57 | + if (timestamp === 0) return 'Never'; |
| 58 | + return new Date(timestamp).toLocaleString(); |
51 | 59 | }
|
52 | 60 | </script>
|
53 | 61 |
|
54 | 62 | <div class="p-6">
|
55 |
| - <div class="mb-6 flex items-center justify-between"> |
56 |
| - <h1 class="text-2xl font-bold">eVault Control Panel</h1> |
57 |
| - <div class="flex gap-2"> |
| 63 | + <div class="mb-6"> |
| 64 | + <h1 class="mb-4 text-2xl font-bold text-gray-900">eVault Management</h1> |
| 65 | + |
| 66 | + <!-- Cache Status --> |
| 67 | + <div class="mb-4 rounded-lg bg-gray-50 p-4"> |
| 68 | + <h3 class="mb-2 text-sm font-medium text-gray-700">Cache Status</h3> |
| 69 | + <div class="grid grid-cols-3 gap-4 text-sm"> |
| 70 | + <div> |
| 71 | + <span class="text-gray-500">Last Updated:</span> |
| 72 | + <span class="ml-2 font-mono" |
| 73 | + >{formatTimestamp(cacheStatus?.lastUpdated || 0)}</span |
| 74 | + > |
| 75 | + </div> |
| 76 | + <div> |
| 77 | + <span class="text-gray-500">Status:</span> |
| 78 | + <span |
| 79 | + class="ml-2 {cacheStatus?.isStale |
| 80 | + ? 'text-orange-600' |
| 81 | + : 'text-green-600'} font-medium" |
| 82 | + > |
| 83 | + {cacheStatus?.isStale ? 'Stale' : 'Fresh'} |
| 84 | + </span> |
| 85 | + </div> |
| 86 | + <div> |
| 87 | + <span class="text-gray-500">Items:</span> |
| 88 | + <span class="ml-2 font-mono">{cacheStatus?.itemCount || 0}</span> |
| 89 | + </div> |
| 90 | + </div> |
| 91 | + </div> |
| 92 | + |
| 93 | + <!-- Action Buttons --> |
| 94 | + <div class="mb-6 flex gap-3"> |
58 | 95 | <button
|
59 | 96 | on:click={loadEVaults}
|
60 | 97 | disabled={loading}
|
61 |
| - class="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600 disabled:opacity-50" |
| 98 | + class="rounded-lg bg-blue-600 px-4 py-2 text-white hover:bg-blue-700 disabled:opacity-50" |
62 | 99 | >
|
63 | 100 | {loading ? 'Loading...' : 'Refresh'}
|
64 | 101 | </button>
|
| 102 | + |
65 | 103 | <button
|
66 | 104 | on:click={forceRefresh}
|
67 | 105 | disabled={loading}
|
68 |
| - class="rounded bg-green-500 px-4 py-2 text-white hover:bg-green-600 disabled:opacity-50" |
| 106 | + class="rounded-lg bg-green-600 px-4 py-2 text-white hover:bg-green-700 disabled:opacity-50" |
69 | 107 | >
|
70 | 108 | Force Refresh
|
71 | 109 | </button>
|
| 110 | + |
72 | 111 | <button
|
73 | 112 | on:click={clearCache}
|
74 |
| - class="rounded bg-red-500 px-4 py-2 text-white hover:bg-red-600" |
| 113 | + class="rounded-lg bg-red-600 px-4 py-2 text-white hover:bg-red-700" |
75 | 114 | >
|
76 | 115 | Clear Cache
|
77 | 116 | </button>
|
78 | 117 | </div>
|
79 | 118 | </div>
|
80 | 119 |
|
81 |
| - <!-- Cache Status --> |
82 |
| - {#if cacheStatus} |
83 |
| - <div class="mb-6 rounded-lg bg-gray-100 p-4"> |
84 |
| - <h3 class="mb-2 font-semibold">Cache Status</h3> |
85 |
| - <div class="grid grid-cols-3 gap-4 text-sm"> |
86 |
| - <div> |
87 |
| - <strong>Last Updated:</strong><br /> |
88 |
| - {new Date(cacheStatus.lastUpdated).toLocaleString()} |
89 |
| - </div> |
90 |
| - <div> |
91 |
| - <strong>Status:</strong><br /> |
92 |
| - <span |
93 |
| - class="rounded px-2 py-1 text-xs {cacheStatus.isStale |
94 |
| - ? 'bg-yellow-200 text-yellow-800' |
95 |
| - : 'bg-green-200 text-green-800'}" |
96 |
| - > |
97 |
| - {cacheStatus.isStale ? 'Stale' : 'Fresh'} |
98 |
| - </span> |
99 |
| - </div> |
100 |
| - <div> |
101 |
| - <strong>Cached Items:</strong><br /> |
102 |
| - {cacheStatus.count} eVaults |
103 |
| - </div> |
104 |
| - </div> |
| 120 | + <!-- Error Display --> |
| 121 | + {#if error} |
| 122 | + <div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-red-700"> |
| 123 | + {error} |
105 | 124 | </div>
|
106 | 125 | {/if}
|
107 | 126 |
|
108 |
| - <!-- Last Refresh Info --> |
109 |
| - {#if lastRefresh} |
110 |
| - <div class="mb-4 text-sm text-gray-600"> |
111 |
| - Last refresh: {lastRefresh} |
| 127 | + <!-- Loading State --> |
| 128 | + {#if loading} |
| 129 | + <div class="py-8 text-center"> |
| 130 | + <div |
| 131 | + class="inline-block h-8 w-8 animate-spin rounded-full border-b-2 border-blue-600" |
| 132 | + ></div> |
| 133 | + <p class="mt-2 text-gray-600">Loading eVaults...</p> |
112 | 134 | </div>
|
113 |
| - {/if} |
114 |
| - |
115 |
| - <!-- eVault List --> |
116 |
| - <div class="grid gap-4"> |
117 |
| - {#if evaults.length === 0} |
118 |
| - <div class="py-8 text-center text-gray-500"> |
119 |
| - {loading ? 'Loading eVaults...' : 'No eVaults found'} |
120 |
| - </div> |
121 |
| - {:else} |
122 |
| - {#each evaults as evault} |
123 |
| - <div class="rounded-lg border p-4 transition-shadow hover:shadow-md"> |
124 |
| - <h3 class="text-lg font-semibold">{evault.name || 'Unnamed eVault'}</h3> |
125 |
| - <div class="mt-2 text-sm text-gray-600"> |
126 |
| - <strong>Namespace:</strong> |
127 |
| - {evault.namespace}<br /> |
128 |
| - <strong>Pod:</strong> |
129 |
| - {evault.podName}<br /> |
130 |
| - <strong>Status:</strong> |
131 |
| - <span |
132 |
| - class="rounded px-2 py-1 text-xs {evault.status === 'Running' |
133 |
| - ? 'bg-green-200 text-green-800' |
134 |
| - : 'bg-red-200 text-red-800'}" |
| 135 | + {:else if evaults.length === 0} |
| 136 | + <!-- Empty State --> |
| 137 | + <div class="py-8 text-center"> |
| 138 | + <p class="text-gray-500">No eVaults found</p> |
| 139 | + <p class="mt-1 text-sm text-gray-400"> |
| 140 | + Try refreshing or check your Kubernetes connection |
| 141 | + </p> |
| 142 | + </div> |
| 143 | + {:else} |
| 144 | + <!-- eVault List --> |
| 145 | + <div class="overflow-hidden rounded-lg bg-white shadow"> |
| 146 | + <table class="min-w-full divide-y divide-gray-200"> |
| 147 | + <thead class="bg-gray-50"> |
| 148 | + <tr> |
| 149 | + <th |
| 150 | + class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500" |
135 | 151 | >
|
136 |
| - {evault.status} |
137 |
| - </span> |
138 |
| - </div> |
139 |
| - </div> |
140 |
| - {/each} |
141 |
| - {/if} |
142 |
| - </div> |
| 152 | + Name |
| 153 | + </th> |
| 154 | + <th |
| 155 | + class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500" |
| 156 | + > |
| 157 | + Namespace |
| 158 | + </th> |
| 159 | + <th |
| 160 | + class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500" |
| 161 | + > |
| 162 | + Status |
| 163 | + </th> |
| 164 | + <th |
| 165 | + class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500" |
| 166 | + > |
| 167 | + Age |
| 168 | + </th> |
| 169 | + <th |
| 170 | + class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500" |
| 171 | + > |
| 172 | + Service URL |
| 173 | + </th> |
| 174 | + </tr> |
| 175 | + </thead> |
| 176 | + <tbody class="divide-y divide-gray-200 bg-white"> |
| 177 | + {#each evaults as evault} |
| 178 | + <tr class="hover:bg-gray-50"> |
| 179 | + <td |
| 180 | + class="whitespace-nowrap px-6 py-4 text-sm font-medium text-gray-900" |
| 181 | + > |
| 182 | + {evault.name} |
| 183 | + </td> |
| 184 | + <td class="whitespace-nowrap px-6 py-4 text-sm text-gray-500"> |
| 185 | + {evault.namespace} |
| 186 | + </td> |
| 187 | + <td class="whitespace-nowrap px-6 py-4"> |
| 188 | + <span |
| 189 | + class="inline-flex rounded-full px-2 py-1 text-xs font-semibold {evault.status === |
| 190 | + 'Running' |
| 191 | + ? 'bg-green-100 text-green-800' |
| 192 | + : evault.status === 'Pending' |
| 193 | + ? 'bg-yellow-100 text-yellow-800' |
| 194 | + : 'bg-red-100 text-red-800'}" |
| 195 | + > |
| 196 | + {evault.status} |
| 197 | + </span> |
| 198 | + </td> |
| 199 | + <td class="whitespace-nowrap px-6 py-4 text-sm text-gray-500"> |
| 200 | + {evault.age || 'Unknown'} |
| 201 | + </td> |
| 202 | + <td class="whitespace-nowrap px-6 py-4 text-sm text-gray-500"> |
| 203 | + {#if evault.serviceUrl} |
| 204 | + <a |
| 205 | + href={evault.serviceUrl} |
| 206 | + target="_blank" |
| 207 | + rel="noopener noreferrer" |
| 208 | + class="text-blue-600 underline hover:text-blue-800" |
| 209 | + > |
| 210 | + {evault.serviceUrl} |
| 211 | + </a> |
| 212 | + {:else} |
| 213 | + <span class="text-gray-400">No external access</span> |
| 214 | + {/if} |
| 215 | + </td> |
| 216 | + </tr> |
| 217 | + {/each} |
| 218 | + </tbody> |
| 219 | + </table> |
| 220 | + </div> |
| 221 | + {/if} |
143 | 222 | </div>
|
0 commit comments