Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion infrastructure/control-panel/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -50,7 +50,7 @@
"flowbite": "^3.1.2",
"flowbite-svelte": "^1.10.7",
"flowbite-svelte-icons": "^2.2.1",
"lowdb": "^9.1.0",
"lowdb": "^7.0.1",
"lucide-svelte": "^0.539.0",
"tailwind-merge": "^3.0.2"
}
Expand Down
253 changes: 166 additions & 87 deletions infrastructure/control-panel/src/lib/components/EVaultList.svelte
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,140 +4,219 @@
import type { EVault } from '../../routes/api/evaults/+server';

let evaults: EVault[] = [];
let loading = false;
let cacheStatus: any = null;
let lastRefresh = '';
let loading = true;
let error: string | null = null;
let cacheStatus: { lastUpdated: number; isStale: boolean; itemCount: number };

// Load eVaults on component mount
onMount(async () => {
await loadEVaults();
// Get cache status for debugging
cacheStatus = EVaultService.getCacheStatus();
try {
await loadEVaults();
cacheStatus = EVaultService.getCacheStatus();
} catch (err) {
error = 'Failed to load eVaults';
console.error(err);
} finally {
loading = false;
}
});

async function loadEVaults() {
loading = true;
try {
// This will return cached data immediately and refresh in background
evaults = await EVaultService.getEVaults();
lastRefresh = new Date().toLocaleTimeString();
} catch (error) {
console.error('Failed to load eVaults:', error);
} finally {
loading = false;
cacheStatus = EVaultService.getCacheStatus();
} catch (err) {
console.error('Error loading eVaults:', err);
throw err;
}
}

async function forceRefresh() {
loading = true;
try {
// Force refresh and get fresh data
loading = true;
evaults = await EVaultService.forceRefresh();
lastRefresh = new Date().toLocaleTimeString();
// Update cache status
cacheStatus = EVaultService.getCacheStatus();
} catch (error) {
console.error('Failed to force refresh:', error);
} catch (err) {
error = 'Failed to refresh eVaults';
console.error(err);
} finally {
loading = false;
}
}

async function clearCache() {
await EVaultService.clearCache();
cacheStatus = EVaultService.getCacheStatus();
// Reload data
await loadEVaults();
try {
await EVaultService.clearCache();
evaults = [];
cacheStatus = EVaultService.getCacheStatus();
} catch (err) {
console.error('Error clearing cache:', err);
}
}

function formatTimestamp(timestamp: number): string {
if (timestamp === 0) return 'Never';
return new Date(timestamp).toLocaleString();
}
</script>

<div class="p-6">
<div class="mb-6 flex items-center justify-between">
<h1 class="text-2xl font-bold">eVault Control Panel</h1>
<div class="flex gap-2">
<div class="mb-6">
<h1 class="mb-4 text-2xl font-bold text-gray-900">eVault Management</h1>

<!-- Cache Status -->
<div class="mb-4 rounded-lg bg-gray-50 p-4">
<h3 class="mb-2 text-sm font-medium text-gray-700">Cache Status</h3>
<div class="grid grid-cols-3 gap-4 text-sm">
<div>
<span class="text-gray-500">Last Updated:</span>
<span class="ml-2 font-mono"
>{formatTimestamp(cacheStatus?.lastUpdated || 0)}</span
>
</div>
<div>
<span class="text-gray-500">Status:</span>
<span
class="ml-2 {cacheStatus?.isStale
? 'text-orange-600'
: 'text-green-600'} font-medium"
>
{cacheStatus?.isStale ? 'Stale' : 'Fresh'}
</span>
</div>
<div>
<span class="text-gray-500">Items:</span>
<span class="ml-2 font-mono">{cacheStatus?.itemCount || 0}</span>
</div>
</div>
</div>

<!-- Action Buttons -->
<div class="mb-6 flex gap-3">
<button
on:click={loadEVaults}
disabled={loading}
class="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600 disabled:opacity-50"
class="rounded-lg bg-blue-600 px-4 py-2 text-white hover:bg-blue-700 disabled:opacity-50"
>
{loading ? 'Loading...' : 'Refresh'}
</button>

<button
on:click={forceRefresh}
disabled={loading}
class="rounded bg-green-500 px-4 py-2 text-white hover:bg-green-600 disabled:opacity-50"
class="rounded-lg bg-green-600 px-4 py-2 text-white hover:bg-green-700 disabled:opacity-50"
>
Force Refresh
</button>

<button
on:click={clearCache}
class="rounded bg-red-500 px-4 py-2 text-white hover:bg-red-600"
class="rounded-lg bg-red-600 px-4 py-2 text-white hover:bg-red-700"
>
Clear Cache
</button>
</div>
</div>

<!-- Cache Status -->
{#if cacheStatus}
<div class="mb-6 rounded-lg bg-gray-100 p-4">
<h3 class="mb-2 font-semibold">Cache Status</h3>
<div class="grid grid-cols-3 gap-4 text-sm">
<div>
<strong>Last Updated:</strong><br />
{new Date(cacheStatus.lastUpdated).toLocaleString()}
</div>
<div>
<strong>Status:</strong><br />
<span
class="rounded px-2 py-1 text-xs {cacheStatus.isStale
? 'bg-yellow-200 text-yellow-800'
: 'bg-green-200 text-green-800'}"
>
{cacheStatus.isStale ? 'Stale' : 'Fresh'}
</span>
</div>
<div>
<strong>Cached Items:</strong><br />
{cacheStatus.count} eVaults
</div>
</div>
<!-- Error Display -->
{#if error}
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-red-700">
{error}
</div>
{/if}

<!-- Last Refresh Info -->
{#if lastRefresh}
<div class="mb-4 text-sm text-gray-600">
Last refresh: {lastRefresh}
<!-- Loading State -->
{#if loading}
<div class="py-8 text-center">
<div
class="inline-block h-8 w-8 animate-spin rounded-full border-b-2 border-blue-600"
></div>
<p class="mt-2 text-gray-600">Loading eVaults...</p>
</div>
{/if}

<!-- eVault List -->
<div class="grid gap-4">
{#if evaults.length === 0}
<div class="py-8 text-center text-gray-500">
{loading ? 'Loading eVaults...' : 'No eVaults found'}
</div>
{:else}
{#each evaults as evault}
<div class="rounded-lg border p-4 transition-shadow hover:shadow-md">
<h3 class="text-lg font-semibold">{evault.name || 'Unnamed eVault'}</h3>
<div class="mt-2 text-sm text-gray-600">
<strong>Namespace:</strong>
{evault.namespace}<br />
<strong>Pod:</strong>
{evault.podName}<br />
<strong>Status:</strong>
<span
class="rounded px-2 py-1 text-xs {evault.status === 'Running'
? 'bg-green-200 text-green-800'
: 'bg-red-200 text-red-800'}"
{:else if evaults.length === 0}
<!-- Empty State -->
<div class="py-8 text-center">
<p class="text-gray-500">No eVaults found</p>
<p class="mt-1 text-sm text-gray-400">
Try refreshing or check your Kubernetes connection
</p>
</div>
{:else}
<!-- eVault List -->
<div class="overflow-hidden rounded-lg bg-white shadow">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th
class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500"
>
{evault.status}
</span>
</div>
</div>
{/each}
{/if}
</div>
Name
</th>
<th
class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500"
>
Namespace
</th>
<th
class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500"
>
Status
</th>
<th
class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500"
>
Age
</th>
<th
class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500"
>
Service URL
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 bg-white">
{#each evaults as evault}
<tr class="hover:bg-gray-50">
<td
class="whitespace-nowrap px-6 py-4 text-sm font-medium text-gray-900"
>
{evault.name}
</td>
<td class="whitespace-nowrap px-6 py-4 text-sm text-gray-500">
{evault.namespace}
</td>
<td class="whitespace-nowrap px-6 py-4">
<span
class="inline-flex rounded-full px-2 py-1 text-xs font-semibold {evault.status ===
'Running'
? 'bg-green-100 text-green-800'
: evault.status === 'Pending'
? 'bg-yellow-100 text-yellow-800'
: 'bg-red-100 text-red-800'}"
>
{evault.status}
</span>
</td>
<td class="whitespace-nowrap px-6 py-4 text-sm text-gray-500">
{evault.age || 'Unknown'}
</td>
<td class="whitespace-nowrap px-6 py-4 text-sm text-gray-500">
{#if evault.serviceUrl}
<a
href={evault.serviceUrl}
target="_blank"
rel="noopener noreferrer"
class="text-blue-600 underline hover:text-blue-800"
>
{evault.serviceUrl}
</a>
{:else}
<span class="text-gray-400">No external access</span>
{/if}
</td>
</tr>
{/each}
</tbody>
</table>
</div>
{/if}
</div>
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion infrastructure/control-panel/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -50,7 +50,7 @@
"flowbite": "^3.1.2",
"flowbite-svelte": "^1.10.7",
"flowbite-svelte-icons": "^2.2.1",
"lowdb": "^9.1.0",
"lowdb": "^7.0.1",
"lucide-svelte": "^0.539.0",
"tailwind-merge": "^3.0.2"
}
Expand Down
253 changes: 166 additions & 87 deletions infrastructure/control-panel/src/lib/components/EVaultList.svelte
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,140 +4,219 @@
import type { EVault } from '../../routes/api/evaults/+server';

let evaults: EVault[] = [];
let loading = false;
let cacheStatus: any = null;
let lastRefresh = '';
let loading = true;
let error: string | null = null;
let cacheStatus: { lastUpdated: number; isStale: boolean; itemCount: number };

// Load eVaults on component mount
onMount(async () => {
await loadEVaults();
// Get cache status for debugging
cacheStatus = EVaultService.getCacheStatus();
try {
await loadEVaults();
cacheStatus = EVaultService.getCacheStatus();
} catch (err) {
error = 'Failed to load eVaults';
console.error(err);
} finally {
loading = false;
}
});

async function loadEVaults() {
loading = true;
try {
// This will return cached data immediately and refresh in background
evaults = await EVaultService.getEVaults();
lastRefresh = new Date().toLocaleTimeString();
} catch (error) {
console.error('Failed to load eVaults:', error);
} finally {
loading = false;
cacheStatus = EVaultService.getCacheStatus();
} catch (err) {
console.error('Error loading eVaults:', err);
throw err;
}
}

async function forceRefresh() {
loading = true;
try {
// Force refresh and get fresh data
loading = true;
evaults = await EVaultService.forceRefresh();
lastRefresh = new Date().toLocaleTimeString();
// Update cache status
cacheStatus = EVaultService.getCacheStatus();
} catch (error) {
console.error('Failed to force refresh:', error);
} catch (err) {
error = 'Failed to refresh eVaults';
console.error(err);
} finally {
loading = false;
}
}

async function clearCache() {
await EVaultService.clearCache();
cacheStatus = EVaultService.getCacheStatus();
// Reload data
await loadEVaults();
try {
await EVaultService.clearCache();
evaults = [];
cacheStatus = EVaultService.getCacheStatus();
} catch (err) {
console.error('Error clearing cache:', err);
}
}

function formatTimestamp(timestamp: number): string {
if (timestamp === 0) return 'Never';
return new Date(timestamp).toLocaleString();
}
</script>

<div class="p-6">
<div class="mb-6 flex items-center justify-between">
<h1 class="text-2xl font-bold">eVault Control Panel</h1>
<div class="flex gap-2">
<div class="mb-6">
<h1 class="mb-4 text-2xl font-bold text-gray-900">eVault Management</h1>

<!-- Cache Status -->
<div class="mb-4 rounded-lg bg-gray-50 p-4">
<h3 class="mb-2 text-sm font-medium text-gray-700">Cache Status</h3>
<div class="grid grid-cols-3 gap-4 text-sm">
<div>
<span class="text-gray-500">Last Updated:</span>
<span class="ml-2 font-mono"
>{formatTimestamp(cacheStatus?.lastUpdated || 0)}</span
>
</div>
<div>
<span class="text-gray-500">Status:</span>
<span
class="ml-2 {cacheStatus?.isStale
? 'text-orange-600'
: 'text-green-600'} font-medium"
>
{cacheStatus?.isStale ? 'Stale' : 'Fresh'}
</span>
</div>
<div>
<span class="text-gray-500">Items:</span>
<span class="ml-2 font-mono">{cacheStatus?.itemCount || 0}</span>
</div>
</div>
</div>

<!-- Action Buttons -->
<div class="mb-6 flex gap-3">
<button
on:click={loadEVaults}
disabled={loading}
class="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600 disabled:opacity-50"
class="rounded-lg bg-blue-600 px-4 py-2 text-white hover:bg-blue-700 disabled:opacity-50"
>
{loading ? 'Loading...' : 'Refresh'}
</button>

<button
on:click={forceRefresh}
disabled={loading}
class="rounded bg-green-500 px-4 py-2 text-white hover:bg-green-600 disabled:opacity-50"
class="rounded-lg bg-green-600 px-4 py-2 text-white hover:bg-green-700 disabled:opacity-50"
>
Force Refresh
</button>

<button
on:click={clearCache}
class="rounded bg-red-500 px-4 py-2 text-white hover:bg-red-600"
class="rounded-lg bg-red-600 px-4 py-2 text-white hover:bg-red-700"
>
Clear Cache
</button>
</div>
</div>

<!-- Cache Status -->
{#if cacheStatus}
<div class="mb-6 rounded-lg bg-gray-100 p-4">
<h3 class="mb-2 font-semibold">Cache Status</h3>
<div class="grid grid-cols-3 gap-4 text-sm">
<div>
<strong>Last Updated:</strong><br />
{new Date(cacheStatus.lastUpdated).toLocaleString()}
</div>
<div>
<strong>Status:</strong><br />
<span
class="rounded px-2 py-1 text-xs {cacheStatus.isStale
? 'bg-yellow-200 text-yellow-800'
: 'bg-green-200 text-green-800'}"
>
{cacheStatus.isStale ? 'Stale' : 'Fresh'}
</span>
</div>
<div>
<strong>Cached Items:</strong><br />
{cacheStatus.count} eVaults
</div>
</div>
<!-- Error Display -->
{#if error}
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-red-700">
{error}
</div>
{/if}

<!-- Last Refresh Info -->
{#if lastRefresh}
<div class="mb-4 text-sm text-gray-600">
Last refresh: {lastRefresh}
<!-- Loading State -->
{#if loading}
<div class="py-8 text-center">
<div
class="inline-block h-8 w-8 animate-spin rounded-full border-b-2 border-blue-600"
></div>
<p class="mt-2 text-gray-600">Loading eVaults...</p>
</div>
{/if}

<!-- eVault List -->
<div class="grid gap-4">
{#if evaults.length === 0}
<div class="py-8 text-center text-gray-500">
{loading ? 'Loading eVaults...' : 'No eVaults found'}
</div>
{:else}
{#each evaults as evault}
<div class="rounded-lg border p-4 transition-shadow hover:shadow-md">
<h3 class="text-lg font-semibold">{evault.name || 'Unnamed eVault'}</h3>
<div class="mt-2 text-sm text-gray-600">
<strong>Namespace:</strong>
{evault.namespace}<br />
<strong>Pod:</strong>
{evault.podName}<br />
<strong>Status:</strong>
<span
class="rounded px-2 py-1 text-xs {evault.status === 'Running'
? 'bg-green-200 text-green-800'
: 'bg-red-200 text-red-800'}"
{:else if evaults.length === 0}
<!-- Empty State -->
<div class="py-8 text-center">
<p class="text-gray-500">No eVaults found</p>
<p class="mt-1 text-sm text-gray-400">
Try refreshing or check your Kubernetes connection
</p>
</div>
{:else}
<!-- eVault List -->
<div class="overflow-hidden rounded-lg bg-white shadow">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th
class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500"
>
{evault.status}
</span>
</div>
</div>
{/each}
{/if}
</div>
Name
</th>
<th
class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500"
>
Namespace
</th>
<th
class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500"
>
Status
</th>
<th
class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500"
>
Age
</th>
<th
class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500"
>
Service URL
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 bg-white">
{#each evaults as evault}
<tr class="hover:bg-gray-50">
<td
class="whitespace-nowrap px-6 py-4 text-sm font-medium text-gray-900"
>
{evault.name}
</td>
<td class="whitespace-nowrap px-6 py-4 text-sm text-gray-500">
{evault.namespace}
</td>
<td class="whitespace-nowrap px-6 py-4">
<span
class="inline-flex rounded-full px-2 py-1 text-xs font-semibold {evault.status ===
'Running'
? 'bg-green-100 text-green-800'
: evault.status === 'Pending'
? 'bg-yellow-100 text-yellow-800'
: 'bg-red-100 text-red-800'}"
>
{evault.status}
</span>
</td>
<td class="whitespace-nowrap px-6 py-4 text-sm text-gray-500">
{evault.age || 'Unknown'}
</td>
<td class="whitespace-nowrap px-6 py-4 text-sm text-gray-500">
{#if evault.serviceUrl}
<a
href={evault.serviceUrl}
target="_blank"
rel="noopener noreferrer"
class="text-blue-600 underline hover:text-blue-800"
>
{evault.serviceUrl}
</a>
{:else}
<span class="text-gray-400">No external access</span>
{/if}
</td>
</tr>
{/each}
</tbody>
</table>
</div>
{/if}
</div>
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion infrastructure/control-panel/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -50,7 +50,7 @@
"flowbite": "^3.1.2",
"flowbite-svelte": "^1.10.7",
"flowbite-svelte-icons": "^2.2.1",
"lowdb": "^9.1.0",
"lowdb": "^7.0.1",
"lucide-svelte": "^0.539.0",
"tailwind-merge": "^3.0.2"
}
Expand Down
253 changes: 166 additions & 87 deletions infrastructure/control-panel/src/lib/components/EVaultList.svelte
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,140 +4,219 @@
import type { EVault } from '../../routes/api/evaults/+server';

let evaults: EVault[] = [];
let loading = false;
let cacheStatus: any = null;
let lastRefresh = '';
let loading = true;
let error: string | null = null;
let cacheStatus: { lastUpdated: number; isStale: boolean; itemCount: number };

// Load eVaults on component mount
onMount(async () => {
await loadEVaults();
// Get cache status for debugging
cacheStatus = EVaultService.getCacheStatus();
try {
await loadEVaults();
cacheStatus = EVaultService.getCacheStatus();
} catch (err) {
error = 'Failed to load eVaults';
console.error(err);
} finally {
loading = false;
}
});

async function loadEVaults() {
loading = true;
try {
// This will return cached data immediately and refresh in background
evaults = await EVaultService.getEVaults();
lastRefresh = new Date().toLocaleTimeString();
} catch (error) {
console.error('Failed to load eVaults:', error);
} finally {
loading = false;
cacheStatus = EVaultService.getCacheStatus();
} catch (err) {
console.error('Error loading eVaults:', err);
throw err;
}
}

async function forceRefresh() {
loading = true;
try {
// Force refresh and get fresh data
loading = true;
evaults = await EVaultService.forceRefresh();
lastRefresh = new Date().toLocaleTimeString();
// Update cache status
cacheStatus = EVaultService.getCacheStatus();
} catch (error) {
console.error('Failed to force refresh:', error);
} catch (err) {
error = 'Failed to refresh eVaults';
console.error(err);
} finally {
loading = false;
}
}

async function clearCache() {
await EVaultService.clearCache();
cacheStatus = EVaultService.getCacheStatus();
// Reload data
await loadEVaults();
try {
await EVaultService.clearCache();
evaults = [];
cacheStatus = EVaultService.getCacheStatus();
} catch (err) {
console.error('Error clearing cache:', err);
}
}

function formatTimestamp(timestamp: number): string {
if (timestamp === 0) return 'Never';
return new Date(timestamp).toLocaleString();
}
</script>

<div class="p-6">
<div class="mb-6 flex items-center justify-between">
<h1 class="text-2xl font-bold">eVault Control Panel</h1>
<div class="flex gap-2">
<div class="mb-6">
<h1 class="mb-4 text-2xl font-bold text-gray-900">eVault Management</h1>

<!-- Cache Status -->
<div class="mb-4 rounded-lg bg-gray-50 p-4">
<h3 class="mb-2 text-sm font-medium text-gray-700">Cache Status</h3>
<div class="grid grid-cols-3 gap-4 text-sm">
<div>
<span class="text-gray-500">Last Updated:</span>
<span class="ml-2 font-mono"
>{formatTimestamp(cacheStatus?.lastUpdated || 0)}</span
>
</div>
<div>
<span class="text-gray-500">Status:</span>
<span
class="ml-2 {cacheStatus?.isStale
? 'text-orange-600'
: 'text-green-600'} font-medium"
>
{cacheStatus?.isStale ? 'Stale' : 'Fresh'}
</span>
</div>
<div>
<span class="text-gray-500">Items:</span>
<span class="ml-2 font-mono">{cacheStatus?.itemCount || 0}</span>
</div>
</div>
</div>

<!-- Action Buttons -->
<div class="mb-6 flex gap-3">
<button
on:click={loadEVaults}
disabled={loading}
class="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600 disabled:opacity-50"
class="rounded-lg bg-blue-600 px-4 py-2 text-white hover:bg-blue-700 disabled:opacity-50"
>
{loading ? 'Loading...' : 'Refresh'}
</button>

<button
on:click={forceRefresh}
disabled={loading}
class="rounded bg-green-500 px-4 py-2 text-white hover:bg-green-600 disabled:opacity-50"
class="rounded-lg bg-green-600 px-4 py-2 text-white hover:bg-green-700 disabled:opacity-50"
>
Force Refresh
</button>

<button
on:click={clearCache}
class="rounded bg-red-500 px-4 py-2 text-white hover:bg-red-600"
class="rounded-lg bg-red-600 px-4 py-2 text-white hover:bg-red-700"
>
Clear Cache
</button>
</div>
</div>

<!-- Cache Status -->
{#if cacheStatus}
<div class="mb-6 rounded-lg bg-gray-100 p-4">
<h3 class="mb-2 font-semibold">Cache Status</h3>
<div class="grid grid-cols-3 gap-4 text-sm">
<div>
<strong>Last Updated:</strong><br />
{new Date(cacheStatus.lastUpdated).toLocaleString()}
</div>
<div>
<strong>Status:</strong><br />
<span
class="rounded px-2 py-1 text-xs {cacheStatus.isStale
? 'bg-yellow-200 text-yellow-800'
: 'bg-green-200 text-green-800'}"
>
{cacheStatus.isStale ? 'Stale' : 'Fresh'}
</span>
</div>
<div>
<strong>Cached Items:</strong><br />
{cacheStatus.count} eVaults
</div>
</div>
<!-- Error Display -->
{#if error}
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-red-700">
{error}
</div>
{/if}

<!-- Last Refresh Info -->
{#if lastRefresh}
<div class="mb-4 text-sm text-gray-600">
Last refresh: {lastRefresh}
<!-- Loading State -->
{#if loading}
<div class="py-8 text-center">
<div
class="inline-block h-8 w-8 animate-spin rounded-full border-b-2 border-blue-600"
></div>
<p class="mt-2 text-gray-600">Loading eVaults...</p>
</div>
{/if}

<!-- eVault List -->
<div class="grid gap-4">
{#if evaults.length === 0}
<div class="py-8 text-center text-gray-500">
{loading ? 'Loading eVaults...' : 'No eVaults found'}
</div>
{:else}
{#each evaults as evault}
<div class="rounded-lg border p-4 transition-shadow hover:shadow-md">
<h3 class="text-lg font-semibold">{evault.name || 'Unnamed eVault'}</h3>
<div class="mt-2 text-sm text-gray-600">
<strong>Namespace:</strong>
{evault.namespace}<br />
<strong>Pod:</strong>
{evault.podName}<br />
<strong>Status:</strong>
<span
class="rounded px-2 py-1 text-xs {evault.status === 'Running'
? 'bg-green-200 text-green-800'
: 'bg-red-200 text-red-800'}"
{:else if evaults.length === 0}
<!-- Empty State -->
<div class="py-8 text-center">
<p class="text-gray-500">No eVaults found</p>
<p class="mt-1 text-sm text-gray-400">
Try refreshing or check your Kubernetes connection
</p>
</div>
{:else}
<!-- eVault List -->
<div class="overflow-hidden rounded-lg bg-white shadow">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th
class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500"
>
{evault.status}
</span>
</div>
</div>
{/each}
{/if}
</div>
Name
</th>
<th
class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500"
>
Namespace
</th>
<th
class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500"
>
Status
</th>
<th
class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500"
>
Age
</th>
<th
class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500"
>
Service URL
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 bg-white">
{#each evaults as evault}
<tr class="hover:bg-gray-50">
<td
class="whitespace-nowrap px-6 py-4 text-sm font-medium text-gray-900"
>
{evault.name}
</td>
<td class="whitespace-nowrap px-6 py-4 text-sm text-gray-500">
{evault.namespace}
</td>
<td class="whitespace-nowrap px-6 py-4">
<span
class="inline-flex rounded-full px-2 py-1 text-xs font-semibold {evault.status ===
'Running'
? 'bg-green-100 text-green-800'
: evault.status === 'Pending'
? 'bg-yellow-100 text-yellow-800'
: 'bg-red-100 text-red-800'}"
>
{evault.status}
</span>
</td>
<td class="whitespace-nowrap px-6 py-4 text-sm text-gray-500">
{evault.age || 'Unknown'}
</td>
<td class="whitespace-nowrap px-6 py-4 text-sm text-gray-500">
{#if evault.serviceUrl}
<a
href={evault.serviceUrl}
target="_blank"
rel="noopener noreferrer"
class="text-blue-600 underline hover:text-blue-800"
>
{evault.serviceUrl}
</a>
{:else}
<span class="text-gray-400">No external access</span>
{/if}
</td>
</tr>
{/each}
</tbody>
</table>
</div>
{/if}
</div>
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion infrastructure/control-panel/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -50,7 +50,7 @@
"flowbite": "^3.1.2",
"flowbite-svelte": "^1.10.7",
"flowbite-svelte-icons": "^2.2.1",
"lowdb": "^9.1.0",
"lowdb": "^7.0.1",
"lucide-svelte": "^0.539.0",
"tailwind-merge": "^3.0.2"
}
Expand Down
253 changes: 166 additions & 87 deletions infrastructure/control-panel/src/lib/components/EVaultList.svelte
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,140 +4,219 @@
import type { EVault } from '../../routes/api/evaults/+server';

let evaults: EVault[] = [];
let loading = false;
let cacheStatus: any = null;
let lastRefresh = '';
let loading = true;
let error: string | null = null;
let cacheStatus: { lastUpdated: number; isStale: boolean; itemCount: number };

// Load eVaults on component mount
onMount(async () => {
await loadEVaults();
// Get cache status for debugging
cacheStatus = EVaultService.getCacheStatus();
try {
await loadEVaults();
cacheStatus = EVaultService.getCacheStatus();
} catch (err) {
error = 'Failed to load eVaults';
console.error(err);
} finally {
loading = false;
}
});

async function loadEVaults() {
loading = true;
try {
// This will return cached data immediately and refresh in background
evaults = await EVaultService.getEVaults();
lastRefresh = new Date().toLocaleTimeString();
} catch (error) {
console.error('Failed to load eVaults:', error);
} finally {
loading = false;
cacheStatus = EVaultService.getCacheStatus();
} catch (err) {
console.error('Error loading eVaults:', err);
throw err;
}
}

async function forceRefresh() {
loading = true;
try {
// Force refresh and get fresh data
loading = true;
evaults = await EVaultService.forceRefresh();
lastRefresh = new Date().toLocaleTimeString();
// Update cache status
cacheStatus = EVaultService.getCacheStatus();
} catch (error) {
console.error('Failed to force refresh:', error);
} catch (err) {
error = 'Failed to refresh eVaults';
console.error(err);
} finally {
loading = false;
}
}

async function clearCache() {
await EVaultService.clearCache();
cacheStatus = EVaultService.getCacheStatus();
// Reload data
await loadEVaults();
try {
await EVaultService.clearCache();
evaults = [];
cacheStatus = EVaultService.getCacheStatus();
} catch (err) {
console.error('Error clearing cache:', err);
}
}

function formatTimestamp(timestamp: number): string {
if (timestamp === 0) return 'Never';
return new Date(timestamp).toLocaleString();
}
</script>

<div class="p-6">
<div class="mb-6 flex items-center justify-between">
<h1 class="text-2xl font-bold">eVault Control Panel</h1>
<div class="flex gap-2">
<div class="mb-6">
<h1 class="mb-4 text-2xl font-bold text-gray-900">eVault Management</h1>

<!-- Cache Status -->
<div class="mb-4 rounded-lg bg-gray-50 p-4">
<h3 class="mb-2 text-sm font-medium text-gray-700">Cache Status</h3>
<div class="grid grid-cols-3 gap-4 text-sm">
<div>
<span class="text-gray-500">Last Updated:</span>
<span class="ml-2 font-mono"
>{formatTimestamp(cacheStatus?.lastUpdated || 0)}</span
>
</div>
<div>
<span class="text-gray-500">Status:</span>
<span
class="ml-2 {cacheStatus?.isStale
? 'text-orange-600'
: 'text-green-600'} font-medium"
>
{cacheStatus?.isStale ? 'Stale' : 'Fresh'}
</span>
</div>
<div>
<span class="text-gray-500">Items:</span>
<span class="ml-2 font-mono">{cacheStatus?.itemCount || 0}</span>
</div>
</div>
</div>

<!-- Action Buttons -->
<div class="mb-6 flex gap-3">
<button
on:click={loadEVaults}
disabled={loading}
class="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600 disabled:opacity-50"
class="rounded-lg bg-blue-600 px-4 py-2 text-white hover:bg-blue-700 disabled:opacity-50"
>
{loading ? 'Loading...' : 'Refresh'}
</button>

<button
on:click={forceRefresh}
disabled={loading}
class="rounded bg-green-500 px-4 py-2 text-white hover:bg-green-600 disabled:opacity-50"
class="rounded-lg bg-green-600 px-4 py-2 text-white hover:bg-green-700 disabled:opacity-50"
>
Force Refresh
</button>

<button
on:click={clearCache}
class="rounded bg-red-500 px-4 py-2 text-white hover:bg-red-600"
class="rounded-lg bg-red-600 px-4 py-2 text-white hover:bg-red-700"
>
Clear Cache
</button>
</div>
</div>

<!-- Cache Status -->
{#if cacheStatus}
<div class="mb-6 rounded-lg bg-gray-100 p-4">
<h3 class="mb-2 font-semibold">Cache Status</h3>
<div class="grid grid-cols-3 gap-4 text-sm">
<div>
<strong>Last Updated:</strong><br />
{new Date(cacheStatus.lastUpdated).toLocaleString()}
</div>
<div>
<strong>Status:</strong><br />
<span
class="rounded px-2 py-1 text-xs {cacheStatus.isStale
? 'bg-yellow-200 text-yellow-800'
: 'bg-green-200 text-green-800'}"
>
{cacheStatus.isStale ? 'Stale' : 'Fresh'}
</span>
</div>
<div>
<strong>Cached Items:</strong><br />
{cacheStatus.count} eVaults
</div>
</div>
<!-- Error Display -->
{#if error}
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-red-700">
{error}
</div>
{/if}

<!-- Last Refresh Info -->
{#if lastRefresh}
<div class="mb-4 text-sm text-gray-600">
Last refresh: {lastRefresh}
<!-- Loading State -->
{#if loading}
<div class="py-8 text-center">
<div
class="inline-block h-8 w-8 animate-spin rounded-full border-b-2 border-blue-600"
></div>
<p class="mt-2 text-gray-600">Loading eVaults...</p>
</div>
{/if}

<!-- eVault List -->
<div class="grid gap-4">
{#if evaults.length === 0}
<div class="py-8 text-center text-gray-500">
{loading ? 'Loading eVaults...' : 'No eVaults found'}
</div>
{:else}
{#each evaults as evault}
<div class="rounded-lg border p-4 transition-shadow hover:shadow-md">
<h3 class="text-lg font-semibold">{evault.name || 'Unnamed eVault'}</h3>
<div class="mt-2 text-sm text-gray-600">
<strong>Namespace:</strong>
{evault.namespace}<br />
<strong>Pod:</strong>
{evault.podName}<br />
<strong>Status:</strong>
<span
class="rounded px-2 py-1 text-xs {evault.status === 'Running'
? 'bg-green-200 text-green-800'
: 'bg-red-200 text-red-800'}"
{:else if evaults.length === 0}
<!-- Empty State -->
<div class="py-8 text-center">
<p class="text-gray-500">No eVaults found</p>
<p class="mt-1 text-sm text-gray-400">
Try refreshing or check your Kubernetes connection
</p>
</div>
{:else}
<!-- eVault List -->
<div class="overflow-hidden rounded-lg bg-white shadow">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th
class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500"
>
{evault.status}
</span>
</div>
</div>
{/each}
{/if}
</div>
Name
</th>
<th
class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500"
>
Namespace
</th>
<th
class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500"
>
Status
</th>
<th
class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500"
>
Age
</th>
<th
class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500"
>
Service URL
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 bg-white">
{#each evaults as evault}
<tr class="hover:bg-gray-50">
<td
class="whitespace-nowrap px-6 py-4 text-sm font-medium text-gray-900"
>
{evault.name}
</td>
<td class="whitespace-nowrap px-6 py-4 text-sm text-gray-500">
{evault.namespace}
</td>
<td class="whitespace-nowrap px-6 py-4">
<span
class="inline-flex rounded-full px-2 py-1 text-xs font-semibold {evault.status ===
'Running'
? 'bg-green-100 text-green-800'
: evault.status === 'Pending'
? 'bg-yellow-100 text-yellow-800'
: 'bg-red-100 text-red-800'}"
>
{evault.status}
</span>
</td>
<td class="whitespace-nowrap px-6 py-4 text-sm text-gray-500">
{evault.age || 'Unknown'}
</td>
<td class="whitespace-nowrap px-6 py-4 text-sm text-gray-500">
{#if evault.serviceUrl}
<a
href={evault.serviceUrl}
target="_blank"
rel="noopener noreferrer"
class="text-blue-600 underline hover:text-blue-800"
>
{evault.serviceUrl}
</a>
{:else}
<span class="text-gray-400">No external access</span>
{/if}
</td>
</tr>
{/each}
</tbody>
</table>
</div>
{/if}
</div>
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion infrastructure/control-panel/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -50,7 +50,7 @@
"flowbite": "^3.1.2",
"flowbite-svelte": "^1.10.7",
"flowbite-svelte-icons": "^2.2.1",
"lowdb": "^9.1.0",
"lowdb": "^7.0.1",
"lucide-svelte": "^0.539.0",
"tailwind-merge": "^3.0.2"
}
Expand Down
253 changes: 166 additions & 87 deletions infrastructure/control-panel/src/lib/components/EVaultList.svelte
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,140 +4,219 @@
import type { EVault } from '../../routes/api/evaults/+server';

let evaults: EVault[] = [];
let loading = false;
let cacheStatus: any = null;
let lastRefresh = '';
let loading = true;
let error: string | null = null;
let cacheStatus: { lastUpdated: number; isStale: boolean; itemCount: number };

// Load eVaults on component mount
onMount(async () => {
await loadEVaults();
// Get cache status for debugging
cacheStatus = EVaultService.getCacheStatus();
try {
await loadEVaults();
cacheStatus = EVaultService.getCacheStatus();
} catch (err) {
error = 'Failed to load eVaults';
console.error(err);
} finally {
loading = false;
}
});

async function loadEVaults() {
loading = true;
try {
// This will return cached data immediately and refresh in background
evaults = await EVaultService.getEVaults();
lastRefresh = new Date().toLocaleTimeString();
} catch (error) {
console.error('Failed to load eVaults:', error);
} finally {
loading = false;
cacheStatus = EVaultService.getCacheStatus();
} catch (err) {
console.error('Error loading eVaults:', err);
throw err;
}
}

async function forceRefresh() {
loading = true;
try {
// Force refresh and get fresh data
loading = true;
evaults = await EVaultService.forceRefresh();
lastRefresh = new Date().toLocaleTimeString();
// Update cache status
cacheStatus = EVaultService.getCacheStatus();
} catch (error) {
console.error('Failed to force refresh:', error);
} catch (err) {
error = 'Failed to refresh eVaults';
console.error(err);
} finally {
loading = false;
}
}

async function clearCache() {
await EVaultService.clearCache();
cacheStatus = EVaultService.getCacheStatus();
// Reload data
await loadEVaults();
try {
await EVaultService.clearCache();
evaults = [];
cacheStatus = EVaultService.getCacheStatus();
} catch (err) {
console.error('Error clearing cache:', err);
}
}

function formatTimestamp(timestamp: number): string {
if (timestamp === 0) return 'Never';
return new Date(timestamp).toLocaleString();
}
</script>

<div class="p-6">
<div class="mb-6 flex items-center justify-between">
<h1 class="text-2xl font-bold">eVault Control Panel</h1>
<div class="flex gap-2">
<div class="mb-6">
<h1 class="mb-4 text-2xl font-bold text-gray-900">eVault Management</h1>

<!-- Cache Status -->
<div class="mb-4 rounded-lg bg-gray-50 p-4">
<h3 class="mb-2 text-sm font-medium text-gray-700">Cache Status</h3>
<div class="grid grid-cols-3 gap-4 text-sm">
<div>
<span class="text-gray-500">Last Updated:</span>
<span class="ml-2 font-mono"
>{formatTimestamp(cacheStatus?.lastUpdated || 0)}</span
>
</div>
<div>
<span class="text-gray-500">Status:</span>
<span
class="ml-2 {cacheStatus?.isStale
? 'text-orange-600'
: 'text-green-600'} font-medium"
>
{cacheStatus?.isStale ? 'Stale' : 'Fresh'}
</span>
</div>
<div>
<span class="text-gray-500">Items:</span>
<span class="ml-2 font-mono">{cacheStatus?.itemCount || 0}</span>
</div>
</div>
</div>

<!-- Action Buttons -->
<div class="mb-6 flex gap-3">
<button
on:click={loadEVaults}
disabled={loading}
class="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600 disabled:opacity-50"
class="rounded-lg bg-blue-600 px-4 py-2 text-white hover:bg-blue-700 disabled:opacity-50"
>
{loading ? 'Loading...' : 'Refresh'}
</button>

<button
on:click={forceRefresh}
disabled={loading}
class="rounded bg-green-500 px-4 py-2 text-white hover:bg-green-600 disabled:opacity-50"
class="rounded-lg bg-green-600 px-4 py-2 text-white hover:bg-green-700 disabled:opacity-50"
>
Force Refresh
</button>

<button
on:click={clearCache}
class="rounded bg-red-500 px-4 py-2 text-white hover:bg-red-600"
class="rounded-lg bg-red-600 px-4 py-2 text-white hover:bg-red-700"
>
Clear Cache
</button>
</div>
</div>

<!-- Cache Status -->
{#if cacheStatus}
<div class="mb-6 rounded-lg bg-gray-100 p-4">
<h3 class="mb-2 font-semibold">Cache Status</h3>
<div class="grid grid-cols-3 gap-4 text-sm">
<div>
<strong>Last Updated:</strong><br />
{new Date(cacheStatus.lastUpdated).toLocaleString()}
</div>
<div>
<strong>Status:</strong><br />
<span
class="rounded px-2 py-1 text-xs {cacheStatus.isStale
? 'bg-yellow-200 text-yellow-800'
: 'bg-green-200 text-green-800'}"
>
{cacheStatus.isStale ? 'Stale' : 'Fresh'}
</span>
</div>
<div>
<strong>Cached Items:</strong><br />
{cacheStatus.count} eVaults
</div>
</div>
<!-- Error Display -->
{#if error}
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-red-700">
{error}
</div>
{/if}

<!-- Last Refresh Info -->
{#if lastRefresh}
<div class="mb-4 text-sm text-gray-600">
Last refresh: {lastRefresh}
<!-- Loading State -->
{#if loading}
<div class="py-8 text-center">
<div
class="inline-block h-8 w-8 animate-spin rounded-full border-b-2 border-blue-600"
></div>
<p class="mt-2 text-gray-600">Loading eVaults...</p>
</div>
{/if}

<!-- eVault List -->
<div class="grid gap-4">
{#if evaults.length === 0}
<div class="py-8 text-center text-gray-500">
{loading ? 'Loading eVaults...' : 'No eVaults found'}
</div>
{:else}
{#each evaults as evault}
<div class="rounded-lg border p-4 transition-shadow hover:shadow-md">
<h3 class="text-lg font-semibold">{evault.name || 'Unnamed eVault'}</h3>
<div class="mt-2 text-sm text-gray-600">
<strong>Namespace:</strong>
{evault.namespace}<br />
<strong>Pod:</strong>
{evault.podName}<br />
<strong>Status:</strong>
<span
class="rounded px-2 py-1 text-xs {evault.status === 'Running'
? 'bg-green-200 text-green-800'
: 'bg-red-200 text-red-800'}"
{:else if evaults.length === 0}
<!-- Empty State -->
<div class="py-8 text-center">
<p class="text-gray-500">No eVaults found</p>
<p class="mt-1 text-sm text-gray-400">
Try refreshing or check your Kubernetes connection
</p>
</div>
{:else}
<!-- eVault List -->
<div class="overflow-hidden rounded-lg bg-white shadow">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th
class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500"
>
{evault.status}
</span>
</div>
</div>
{/each}
{/if}
</div>
Name
</th>
<th
class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500"
>
Namespace
</th>
<th
class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500"
>
Status
</th>
<th
class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500"
>
Age
</th>
<th
class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500"
>
Service URL
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 bg-white">
{#each evaults as evault}
<tr class="hover:bg-gray-50">
<td
class="whitespace-nowrap px-6 py-4 text-sm font-medium text-gray-900"
>
{evault.name}
</td>
<td class="whitespace-nowrap px-6 py-4 text-sm text-gray-500">
{evault.namespace}
</td>
<td class="whitespace-nowrap px-6 py-4">
<span
class="inline-flex rounded-full px-2 py-1 text-xs font-semibold {evault.status ===
'Running'
? 'bg-green-100 text-green-800'
: evault.status === 'Pending'
? 'bg-yellow-100 text-yellow-800'
: 'bg-red-100 text-red-800'}"
>
{evault.status}
</span>
</td>
<td class="whitespace-nowrap px-6 py-4 text-sm text-gray-500">
{evault.age || 'Unknown'}
</td>
<td class="whitespace-nowrap px-6 py-4 text-sm text-gray-500">
{#if evault.serviceUrl}
<a
href={evault.serviceUrl}
target="_blank"
rel="noopener noreferrer"
class="text-blue-600 underline hover:text-blue-800"
>
{evault.serviceUrl}
</a>
{:else}
<span class="text-gray-400">No external access</span>
{/if}
</td>
</tr>
{/each}
</tbody>
</table>
</div>
{/if}
</div>
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion infrastructure/control-panel/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -50,7 +50,7 @@
"flowbite": "^3.1.2",
"flowbite-svelte": "^1.10.7",
"flowbite-svelte-icons": "^2.2.1",
"lowdb": "^9.1.0",
"lowdb": "^7.0.1",
"lucide-svelte": "^0.539.0",
"tailwind-merge": "^3.0.2"
}
Expand Down
253 changes: 166 additions & 87 deletions infrastructure/control-panel/src/lib/components/EVaultList.svelte
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,140 +4,219 @@
import type { EVault } from '../../routes/api/evaults/+server';

let evaults: EVault[] = [];
let loading = false;
let cacheStatus: any = null;
let lastRefresh = '';
let loading = true;
let error: string | null = null;
let cacheStatus: { lastUpdated: number; isStale: boolean; itemCount: number };

// Load eVaults on component mount
onMount(async () => {
await loadEVaults();
// Get cache status for debugging
cacheStatus = EVaultService.getCacheStatus();
try {
await loadEVaults();
cacheStatus = EVaultService.getCacheStatus();
} catch (err) {
error = 'Failed to load eVaults';
console.error(err);
} finally {
loading = false;
}
});

async function loadEVaults() {
loading = true;
try {
// This will return cached data immediately and refresh in background
evaults = await EVaultService.getEVaults();
lastRefresh = new Date().toLocaleTimeString();
} catch (error) {
console.error('Failed to load eVaults:', error);
} finally {
loading = false;
cacheStatus = EVaultService.getCacheStatus();
} catch (err) {
console.error('Error loading eVaults:', err);
throw err;
}
}

async function forceRefresh() {
loading = true;
try {
// Force refresh and get fresh data
loading = true;
evaults = await EVaultService.forceRefresh();
lastRefresh = new Date().toLocaleTimeString();
// Update cache status
cacheStatus = EVaultService.getCacheStatus();
} catch (error) {
console.error('Failed to force refresh:', error);
} catch (err) {
error = 'Failed to refresh eVaults';
console.error(err);
} finally {
loading = false;
}
}

async function clearCache() {
await EVaultService.clearCache();
cacheStatus = EVaultService.getCacheStatus();
// Reload data
await loadEVaults();
try {
await EVaultService.clearCache();
evaults = [];
cacheStatus = EVaultService.getCacheStatus();
} catch (err) {
console.error('Error clearing cache:', err);
}
}

function formatTimestamp(timestamp: number): string {
if (timestamp === 0) return 'Never';
return new Date(timestamp).toLocaleString();
}
</script>

<div class="p-6">
<div class="mb-6 flex items-center justify-between">
<h1 class="text-2xl font-bold">eVault Control Panel</h1>
<div class="flex gap-2">
<div class="mb-6">
<h1 class="mb-4 text-2xl font-bold text-gray-900">eVault Management</h1>

<!-- Cache Status -->
<div class="mb-4 rounded-lg bg-gray-50 p-4">
<h3 class="mb-2 text-sm font-medium text-gray-700">Cache Status</h3>
<div class="grid grid-cols-3 gap-4 text-sm">
<div>
<span class="text-gray-500">Last Updated:</span>
<span class="ml-2 font-mono"
>{formatTimestamp(cacheStatus?.lastUpdated || 0)}</span
>
</div>
<div>
<span class="text-gray-500">Status:</span>
<span
class="ml-2 {cacheStatus?.isStale
? 'text-orange-600'
: 'text-green-600'} font-medium"
>
{cacheStatus?.isStale ? 'Stale' : 'Fresh'}
</span>
</div>
<div>
<span class="text-gray-500">Items:</span>
<span class="ml-2 font-mono">{cacheStatus?.itemCount || 0}</span>
</div>
</div>
</div>

<!-- Action Buttons -->
<div class="mb-6 flex gap-3">
<button
on:click={loadEVaults}
disabled={loading}
class="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600 disabled:opacity-50"
class="rounded-lg bg-blue-600 px-4 py-2 text-white hover:bg-blue-700 disabled:opacity-50"
>
{loading ? 'Loading...' : 'Refresh'}
</button>

<button
on:click={forceRefresh}
disabled={loading}
class="rounded bg-green-500 px-4 py-2 text-white hover:bg-green-600 disabled:opacity-50"
class="rounded-lg bg-green-600 px-4 py-2 text-white hover:bg-green-700 disabled:opacity-50"
>
Force Refresh
</button>

<button
on:click={clearCache}
class="rounded bg-red-500 px-4 py-2 text-white hover:bg-red-600"
class="rounded-lg bg-red-600 px-4 py-2 text-white hover:bg-red-700"
>
Clear Cache
</button>
</div>
</div>

<!-- Cache Status -->
{#if cacheStatus}
<div class="mb-6 rounded-lg bg-gray-100 p-4">
<h3 class="mb-2 font-semibold">Cache Status</h3>
<div class="grid grid-cols-3 gap-4 text-sm">
<div>
<strong>Last Updated:</strong><br />
{new Date(cacheStatus.lastUpdated).toLocaleString()}
</div>
<div>
<strong>Status:</strong><br />
<span
class="rounded px-2 py-1 text-xs {cacheStatus.isStale
? 'bg-yellow-200 text-yellow-800'
: 'bg-green-200 text-green-800'}"
>
{cacheStatus.isStale ? 'Stale' : 'Fresh'}
</span>
</div>
<div>
<strong>Cached Items:</strong><br />
{cacheStatus.count} eVaults
</div>
</div>
<!-- Error Display -->
{#if error}
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-red-700">
{error}
</div>
{/if}

<!-- Last Refresh Info -->
{#if lastRefresh}
<div class="mb-4 text-sm text-gray-600">
Last refresh: {lastRefresh}
<!-- Loading State -->
{#if loading}
<div class="py-8 text-center">
<div
class="inline-block h-8 w-8 animate-spin rounded-full border-b-2 border-blue-600"
></div>
<p class="mt-2 text-gray-600">Loading eVaults...</p>
</div>
{/if}

<!-- eVault List -->
<div class="grid gap-4">
{#if evaults.length === 0}
<div class="py-8 text-center text-gray-500">
{loading ? 'Loading eVaults...' : 'No eVaults found'}
</div>
{:else}
{#each evaults as evault}
<div class="rounded-lg border p-4 transition-shadow hover:shadow-md">
<h3 class="text-lg font-semibold">{evault.name || 'Unnamed eVault'}</h3>
<div class="mt-2 text-sm text-gray-600">
<strong>Namespace:</strong>
{evault.namespace}<br />
<strong>Pod:</strong>
{evault.podName}<br />
<strong>Status:</strong>
<span
class="rounded px-2 py-1 text-xs {evault.status === 'Running'
? 'bg-green-200 text-green-800'
: 'bg-red-200 text-red-800'}"
{:else if evaults.length === 0}
<!-- Empty State -->
<div class="py-8 text-center">
<p class="text-gray-500">No eVaults found</p>
<p class="mt-1 text-sm text-gray-400">
Try refreshing or check your Kubernetes connection
</p>
</div>
{:else}
<!-- eVault List -->
<div class="overflow-hidden rounded-lg bg-white shadow">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th
class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500"
>
{evault.status}
</span>
</div>
</div>
{/each}
{/if}
</div>
Name
</th>
<th
class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500"
>
Namespace
</th>
<th
class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500"
>
Status
</th>
<th
class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500"
>
Age
</th>
<th
class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500"
>
Service URL
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 bg-white">
{#each evaults as evault}
<tr class="hover:bg-gray-50">
<td
class="whitespace-nowrap px-6 py-4 text-sm font-medium text-gray-900"
>
{evault.name}
</td>
<td class="whitespace-nowrap px-6 py-4 text-sm text-gray-500">
{evault.namespace}
</td>
<td class="whitespace-nowrap px-6 py-4">
<span
class="inline-flex rounded-full px-2 py-1 text-xs font-semibold {evault.status ===
'Running'
? 'bg-green-100 text-green-800'
: evault.status === 'Pending'
? 'bg-yellow-100 text-yellow-800'
: 'bg-red-100 text-red-800'}"
>
{evault.status}
</span>
</td>
<td class="whitespace-nowrap px-6 py-4 text-sm text-gray-500">
{evault.age || 'Unknown'}
</td>
<td class="whitespace-nowrap px-6 py-4 text-sm text-gray-500">
{#if evault.serviceUrl}
<a
href={evault.serviceUrl}
target="_blank"
rel="noopener noreferrer"
class="text-blue-600 underline hover:text-blue-800"
>
{evault.serviceUrl}
</a>
{:else}
<span class="text-gray-400">No external access</span>
{/if}
</td>
</tr>
{/each}
</tbody>
</table>
</div>
{/if}
</div>
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion infrastructure/control-panel/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -50,7 +50,7 @@
"flowbite": "^3.1.2",
"flowbite-svelte": "^1.10.7",
"flowbite-svelte-icons": "^2.2.1",
"lowdb": "^9.1.0",
"lowdb": "^7.0.1",
"lucide-svelte": "^0.539.0",
"tailwind-merge": "^3.0.2"
}
Expand Down
253 changes: 166 additions & 87 deletions infrastructure/control-panel/src/lib/components/EVaultList.svelte
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,140 +4,219 @@
import type { EVault } from '../../routes/api/evaults/+server';

let evaults: EVault[] = [];
let loading = false;
let cacheStatus: any = null;
let lastRefresh = '';
let loading = true;
let error: string | null = null;
let cacheStatus: { lastUpdated: number; isStale: boolean; itemCount: number };

// Load eVaults on component mount
onMount(async () => {
await loadEVaults();
// Get cache status for debugging
cacheStatus = EVaultService.getCacheStatus();
try {
await loadEVaults();
cacheStatus = EVaultService.getCacheStatus();
} catch (err) {
error = 'Failed to load eVaults';
console.error(err);
} finally {
loading = false;
}
});

async function loadEVaults() {
loading = true;
try {
// This will return cached data immediately and refresh in background
evaults = await EVaultService.getEVaults();
lastRefresh = new Date().toLocaleTimeString();
} catch (error) {
console.error('Failed to load eVaults:', error);
} finally {
loading = false;
cacheStatus = EVaultService.getCacheStatus();
} catch (err) {
console.error('Error loading eVaults:', err);
throw err;
}
}

async function forceRefresh() {
loading = true;
try {
// Force refresh and get fresh data
loading = true;
evaults = await EVaultService.forceRefresh();
lastRefresh = new Date().toLocaleTimeString();
// Update cache status
cacheStatus = EVaultService.getCacheStatus();
} catch (error) {
console.error('Failed to force refresh:', error);
} catch (err) {
error = 'Failed to refresh eVaults';
console.error(err);
} finally {
loading = false;
}
}

async function clearCache() {
await EVaultService.clearCache();
cacheStatus = EVaultService.getCacheStatus();
// Reload data
await loadEVaults();
try {
await EVaultService.clearCache();
evaults = [];
cacheStatus = EVaultService.getCacheStatus();
} catch (err) {
console.error('Error clearing cache:', err);
}
}

function formatTimestamp(timestamp: number): string {
if (timestamp === 0) return 'Never';
return new Date(timestamp).toLocaleString();
}
</script>

<div class="p-6">
<div class="mb-6 flex items-center justify-between">
<h1 class="text-2xl font-bold">eVault Control Panel</h1>
<div class="flex gap-2">
<div class="mb-6">
<h1 class="mb-4 text-2xl font-bold text-gray-900">eVault Management</h1>

<!-- Cache Status -->
<div class="mb-4 rounded-lg bg-gray-50 p-4">
<h3 class="mb-2 text-sm font-medium text-gray-700">Cache Status</h3>
<div class="grid grid-cols-3 gap-4 text-sm">
<div>
<span class="text-gray-500">Last Updated:</span>
<span class="ml-2 font-mono"
>{formatTimestamp(cacheStatus?.lastUpdated || 0)}</span
>
</div>
<div>
<span class="text-gray-500">Status:</span>
<span
class="ml-2 {cacheStatus?.isStale
? 'text-orange-600'
: 'text-green-600'} font-medium"
>
{cacheStatus?.isStale ? 'Stale' : 'Fresh'}
</span>
</div>
<div>
<span class="text-gray-500">Items:</span>
<span class="ml-2 font-mono">{cacheStatus?.itemCount || 0}</span>
</div>
</div>
</div>

<!-- Action Buttons -->
<div class="mb-6 flex gap-3">
<button
on:click={loadEVaults}
disabled={loading}
class="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600 disabled:opacity-50"
class="rounded-lg bg-blue-600 px-4 py-2 text-white hover:bg-blue-700 disabled:opacity-50"
>
{loading ? 'Loading...' : 'Refresh'}
</button>

<button
on:click={forceRefresh}
disabled={loading}
class="rounded bg-green-500 px-4 py-2 text-white hover:bg-green-600 disabled:opacity-50"
class="rounded-lg bg-green-600 px-4 py-2 text-white hover:bg-green-700 disabled:opacity-50"
>
Force Refresh
</button>

<button
on:click={clearCache}
class="rounded bg-red-500 px-4 py-2 text-white hover:bg-red-600"
class="rounded-lg bg-red-600 px-4 py-2 text-white hover:bg-red-700"
>
Clear Cache
</button>
</div>
</div>

<!-- Cache Status -->
{#if cacheStatus}
<div class="mb-6 rounded-lg bg-gray-100 p-4">
<h3 class="mb-2 font-semibold">Cache Status</h3>
<div class="grid grid-cols-3 gap-4 text-sm">
<div>
<strong>Last Updated:</strong><br />
{new Date(cacheStatus.lastUpdated).toLocaleString()}
</div>
<div>
<strong>Status:</strong><br />
<span
class="rounded px-2 py-1 text-xs {cacheStatus.isStale
? 'bg-yellow-200 text-yellow-800'
: 'bg-green-200 text-green-800'}"
>
{cacheStatus.isStale ? 'Stale' : 'Fresh'}
</span>
</div>
<div>
<strong>Cached Items:</strong><br />
{cacheStatus.count} eVaults
</div>
</div>
<!-- Error Display -->
{#if error}
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-red-700">
{error}
</div>
{/if}

<!-- Last Refresh Info -->
{#if lastRefresh}
<div class="mb-4 text-sm text-gray-600">
Last refresh: {lastRefresh}
<!-- Loading State -->
{#if loading}
<div class="py-8 text-center">
<div
class="inline-block h-8 w-8 animate-spin rounded-full border-b-2 border-blue-600"
></div>
<p class="mt-2 text-gray-600">Loading eVaults...</p>
</div>
{/if}

<!-- eVault List -->
<div class="grid gap-4">
{#if evaults.length === 0}
<div class="py-8 text-center text-gray-500">
{loading ? 'Loading eVaults...' : 'No eVaults found'}
</div>
{:else}
{#each evaults as evault}
<div class="rounded-lg border p-4 transition-shadow hover:shadow-md">
<h3 class="text-lg font-semibold">{evault.name || 'Unnamed eVault'}</h3>
<div class="mt-2 text-sm text-gray-600">
<strong>Namespace:</strong>
{evault.namespace}<br />
<strong>Pod:</strong>
{evault.podName}<br />
<strong>Status:</strong>
<span
class="rounded px-2 py-1 text-xs {evault.status === 'Running'
? 'bg-green-200 text-green-800'
: 'bg-red-200 text-red-800'}"
{:else if evaults.length === 0}
<!-- Empty State -->
<div class="py-8 text-center">
<p class="text-gray-500">No eVaults found</p>
<p class="mt-1 text-sm text-gray-400">
Try refreshing or check your Kubernetes connection
</p>
</div>
{:else}
<!-- eVault List -->
<div class="overflow-hidden rounded-lg bg-white shadow">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th
class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500"
>
{evault.status}
</span>
</div>
</div>
{/each}
{/if}
</div>
Name
</th>
<th
class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500"
>
Namespace
</th>
<th
class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500"
>
Status
</th>
<th
class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500"
>
Age
</th>
<th
class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500"
>
Service URL
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 bg-white">
{#each evaults as evault}
<tr class="hover:bg-gray-50">
<td
class="whitespace-nowrap px-6 py-4 text-sm font-medium text-gray-900"
>
{evault.name}
</td>
<td class="whitespace-nowrap px-6 py-4 text-sm text-gray-500">
{evault.namespace}
</td>
<td class="whitespace-nowrap px-6 py-4">
<span
class="inline-flex rounded-full px-2 py-1 text-xs font-semibold {evault.status ===
'Running'
? 'bg-green-100 text-green-800'
: evault.status === 'Pending'
? 'bg-yellow-100 text-yellow-800'
: 'bg-red-100 text-red-800'}"
>
{evault.status}
</span>
</td>
<td class="whitespace-nowrap px-6 py-4 text-sm text-gray-500">
{evault.age || 'Unknown'}
</td>
<td class="whitespace-nowrap px-6 py-4 text-sm text-gray-500">
{#if evault.serviceUrl}
<a
href={evault.serviceUrl}
target="_blank"
rel="noopener noreferrer"
class="text-blue-600 underline hover:text-blue-800"
>
{evault.serviceUrl}
</a>
{:else}
<span class="text-gray-400">No external access</span>
{/if}
</td>
</tr>
{/each}
</tbody>
</table>
</div>
{/if}
</div>
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion infrastructure/control-panel/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -50,7 +50,7 @@
"flowbite": "^3.1.2",
"flowbite-svelte": "^1.10.7",
"flowbite-svelte-icons": "^2.2.1",
"lowdb": "^9.1.0",
"lowdb": "^7.0.1",
"lucide-svelte": "^0.539.0",
"tailwind-merge": "^3.0.2"
}
Expand Down
253 changes: 166 additions & 87 deletions infrastructure/control-panel/src/lib/components/EVaultList.svelte
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,140 +4,219 @@
import type { EVault } from '../../routes/api/evaults/+server';

let evaults: EVault[] = [];
let loading = false;
let cacheStatus: any = null;
let lastRefresh = '';
let loading = true;
let error: string | null = null;
let cacheStatus: { lastUpdated: number; isStale: boolean; itemCount: number };

// Load eVaults on component mount
onMount(async () => {
await loadEVaults();
// Get cache status for debugging
cacheStatus = EVaultService.getCacheStatus();
try {
await loadEVaults();
cacheStatus = EVaultService.getCacheStatus();
} catch (err) {
error = 'Failed to load eVaults';
console.error(err);
} finally {
loading = false;
}
});

async function loadEVaults() {
loading = true;
try {
// This will return cached data immediately and refresh in background
evaults = await EVaultService.getEVaults();
lastRefresh = new Date().toLocaleTimeString();
} catch (error) {
console.error('Failed to load eVaults:', error);
} finally {
loading = false;
cacheStatus = EVaultService.getCacheStatus();
} catch (err) {
console.error('Error loading eVaults:', err);
throw err;
}
}

async function forceRefresh() {
loading = true;
try {
// Force refresh and get fresh data
loading = true;
evaults = await EVaultService.forceRefresh();
lastRefresh = new Date().toLocaleTimeString();
// Update cache status
cacheStatus = EVaultService.getCacheStatus();
} catch (error) {
console.error('Failed to force refresh:', error);
} catch (err) {
error = 'Failed to refresh eVaults';
console.error(err);
} finally {
loading = false;
}
}

async function clearCache() {
await EVaultService.clearCache();
cacheStatus = EVaultService.getCacheStatus();
// Reload data
await loadEVaults();
try {
await EVaultService.clearCache();
evaults = [];
cacheStatus = EVaultService.getCacheStatus();
} catch (err) {
console.error('Error clearing cache:', err);
}
}

function formatTimestamp(timestamp: number): string {
if (timestamp === 0) return 'Never';
return new Date(timestamp).toLocaleString();
}
</script>

<div class="p-6">
<div class="mb-6 flex items-center justify-between">
<h1 class="text-2xl font-bold">eVault Control Panel</h1>
<div class="flex gap-2">
<div class="mb-6">
<h1 class="mb-4 text-2xl font-bold text-gray-900">eVault Management</h1>

<!-- Cache Status -->
<div class="mb-4 rounded-lg bg-gray-50 p-4">
<h3 class="mb-2 text-sm font-medium text-gray-700">Cache Status</h3>
<div class="grid grid-cols-3 gap-4 text-sm">
<div>
<span class="text-gray-500">Last Updated:</span>
<span class="ml-2 font-mono"
>{formatTimestamp(cacheStatus?.lastUpdated || 0)}</span
>
</div>
<div>
<span class="text-gray-500">Status:</span>
<span
class="ml-2 {cacheStatus?.isStale
? 'text-orange-600'
: 'text-green-600'} font-medium"
>
{cacheStatus?.isStale ? 'Stale' : 'Fresh'}
</span>
</div>
<div>
<span class="text-gray-500">Items:</span>
<span class="ml-2 font-mono">{cacheStatus?.itemCount || 0}</span>
</div>
</div>
</div>

<!-- Action Buttons -->
<div class="mb-6 flex gap-3">
<button
on:click={loadEVaults}
disabled={loading}
class="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600 disabled:opacity-50"
class="rounded-lg bg-blue-600 px-4 py-2 text-white hover:bg-blue-700 disabled:opacity-50"
>
{loading ? 'Loading...' : 'Refresh'}
</button>

<button
on:click={forceRefresh}
disabled={loading}
class="rounded bg-green-500 px-4 py-2 text-white hover:bg-green-600 disabled:opacity-50"
class="rounded-lg bg-green-600 px-4 py-2 text-white hover:bg-green-700 disabled:opacity-50"
>
Force Refresh
</button>

<button
on:click={clearCache}
class="rounded bg-red-500 px-4 py-2 text-white hover:bg-red-600"
class="rounded-lg bg-red-600 px-4 py-2 text-white hover:bg-red-700"
>
Clear Cache
</button>
</div>
</div>

<!-- Cache Status -->
{#if cacheStatus}
<div class="mb-6 rounded-lg bg-gray-100 p-4">
<h3 class="mb-2 font-semibold">Cache Status</h3>
<div class="grid grid-cols-3 gap-4 text-sm">
<div>
<strong>Last Updated:</strong><br />
{new Date(cacheStatus.lastUpdated).toLocaleString()}
</div>
<div>
<strong>Status:</strong><br />
<span
class="rounded px-2 py-1 text-xs {cacheStatus.isStale
? 'bg-yellow-200 text-yellow-800'
: 'bg-green-200 text-green-800'}"
>
{cacheStatus.isStale ? 'Stale' : 'Fresh'}
</span>
</div>
<div>
<strong>Cached Items:</strong><br />
{cacheStatus.count} eVaults
</div>
</div>
<!-- Error Display -->
{#if error}
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-red-700">
{error}
</div>
{/if}

<!-- Last Refresh Info -->
{#if lastRefresh}
<div class="mb-4 text-sm text-gray-600">
Last refresh: {lastRefresh}
<!-- Loading State -->
{#if loading}
<div class="py-8 text-center">
<div
class="inline-block h-8 w-8 animate-spin rounded-full border-b-2 border-blue-600"
></div>
<p class="mt-2 text-gray-600">Loading eVaults...</p>
</div>
{/if}

<!-- eVault List -->
<div class="grid gap-4">
{#if evaults.length === 0}
<div class="py-8 text-center text-gray-500">
{loading ? 'Loading eVaults...' : 'No eVaults found'}
</div>
{:else}
{#each evaults as evault}
<div class="rounded-lg border p-4 transition-shadow hover:shadow-md">
<h3 class="text-lg font-semibold">{evault.name || 'Unnamed eVault'}</h3>
<div class="mt-2 text-sm text-gray-600">
<strong>Namespace:</strong>
{evault.namespace}<br />
<strong>Pod:</strong>
{evault.podName}<br />
<strong>Status:</strong>
<span
class="rounded px-2 py-1 text-xs {evault.status === 'Running'
? 'bg-green-200 text-green-800'
: 'bg-red-200 text-red-800'}"
{:else if evaults.length === 0}
<!-- Empty State -->
<div class="py-8 text-center">
<p class="text-gray-500">No eVaults found</p>
<p class="mt-1 text-sm text-gray-400">
Try refreshing or check your Kubernetes connection
</p>
</div>
{:else}
<!-- eVault List -->
<div class="overflow-hidden rounded-lg bg-white shadow">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th
class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500"
>
{evault.status}
</span>
</div>
</div>
{/each}
{/if}
</div>
Name
</th>
<th
class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500"
>
Namespace
</th>
<th
class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500"
>
Status
</th>
<th
class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500"
>
Age
</th>
<th
class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500"
>
Service URL
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 bg-white">
{#each evaults as evault}
<tr class="hover:bg-gray-50">
<td
class="whitespace-nowrap px-6 py-4 text-sm font-medium text-gray-900"
>
{evault.name}
</td>
<td class="whitespace-nowrap px-6 py-4 text-sm text-gray-500">
{evault.namespace}
</td>
<td class="whitespace-nowrap px-6 py-4">
<span
class="inline-flex rounded-full px-2 py-1 text-xs font-semibold {evault.status ===
'Running'
? 'bg-green-100 text-green-800'
: evault.status === 'Pending'
? 'bg-yellow-100 text-yellow-800'
: 'bg-red-100 text-red-800'}"
>
{evault.status}
</span>
</td>
<td class="whitespace-nowrap px-6 py-4 text-sm text-gray-500">
{evault.age || 'Unknown'}
</td>
<td class="whitespace-nowrap px-6 py-4 text-sm text-gray-500">
{#if evault.serviceUrl}
<a
href={evault.serviceUrl}
target="_blank"
rel="noopener noreferrer"
class="text-blue-600 underline hover:text-blue-800"
>
{evault.serviceUrl}
</a>
{:else}
<span class="text-gray-400">No external access</span>
{/if}
</td>
</tr>
{/each}
</tbody>
</table>
</div>
{/if}
</div>
Loading
Loading