Skip to content

Commit

Permalink
feature: Dark theme (#2)
Browse files Browse the repository at this point in the history
* (feature) implement dark theme
  • Loading branch information
x0ddf authored Nov 26, 2024
1 parent 5456dce commit a874ce3
Showing 1 changed file with 151 additions and 29 deletions.
180 changes: 151 additions & 29 deletions pkg/server/templates/index.html
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
<!DOCTYPE html>
<html class="h-full bg-gray-50">
<html class="h-full">
<head>
<title>Kubernetes Service Monitor</title>
<script src="https://unpkg.com/htmx.org"></script>
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
darkMode: 'class',
theme: {
extend: {
colors: {
Expand All @@ -20,9 +21,9 @@
</script>
</head>
<body class="h-full">
<div class="min-h-full">
<div class="min-h-full bg-gray-50 dark:bg-gray-900 transition-colors duration-200">
<!-- Navigation -->
<nav class="bg-k8s-blue">
<nav class="bg-k8s-blue dark:bg-k8s-gray">
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div class="flex h-16 items-center justify-between">
<div class="flex items-center">
Expand All @@ -35,13 +36,29 @@
<h1 class="text-white text-xl font-bold">Kubernetes Service Monitor</h1>
</div>
</div>
<div class="flex items-center justify-between p-4 shadow">
<div class="flex items-center space-x-4">
<select id="context-select" class="border rounded px-2 py-1 text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-white">
<!-- existing context options -->
</select>
<span id="context-mode" class="text-sm text-gray-600 dark:text-gray-300"></span>
</div>

<!-- Add theme toggle button -->
<button
onclick="toggleTheme()"
class="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"
aria-label="Toggle theme">
<span id="theme-icon" class="text-2xl"> ☀️</span>
</button>
</div>
</div>
</div>
</nav>

<!-- Add this after the navigation bar -->
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 py-4">
<div class="bg-white shadow-sm rounded-lg p-4 mb-6">
<div class="bg-white dark:bg-gray-800 shadow-sm rounded-lg p-4 mb-6">
<div class="flex flex-col sm:flex-row gap-4">
<div class="flex-shrink-0">
<div class="flex items-center space-x-4">
Expand Down Expand Up @@ -90,14 +107,9 @@ <h1 class="text-white text-xl font-bold">Kubernetes Service Monitor</h1>
</main>
</div>

<div class="fixed top-4 right-4 z-50" id="context-switcher" style="display: none;">
<div class="bg-white p-4 rounded-lg shadow-lg border border-gray-200">
<h3 class="text-sm font-medium text-gray-900 mb-2">Kubernetes Context</h3>
<select id="context-select"
class="block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm">
</select>
<p class="mt-1 text-xs text-gray-500" id="context-mode"></p>
</div>
<!-- Add this div for notifications container -->
<div id="notification-container" class="fixed bottom-4 right-4 z-50 space-y-2">
<!-- Notifications will be dynamically inserted here -->
</div>

<script>
Expand Down Expand Up @@ -133,11 +145,11 @@ <h3 class="text-sm font-medium text-gray-900 mb-2">Kubernetes Context</h3>
const visibleGroups = groups.filter(group => !hiddenNamespaces.has(group.namespace));

container.innerHTML = visibleGroups.map(group => `
<div class="mb-6 bg-white shadow-sm rounded-lg overflow-hidden border border-gray-200">
<div class="flex items-center justify-between bg-gray-50 px-4 py-3 cursor-pointer hover:bg-gray-100 transition-colors duration-150"
<div class="mb-6 bg-white dark:bg-gray-800 shadow-sm rounded-lg overflow-hidden border border-gray-200 dark:border-gray-700">
<div class="flex items-center justify-between bg-gray-50 dark:bg-gray-800 px-4 py-3 cursor-pointer hover:bg-gray-100 transition-colors duration-150"
onclick="toggleNamespace('${group.namespace}')">
<div class="flex items-center space-x-2">
<h2 class="text-lg font-semibold text-gray-900">
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">
${group.namespace}
</h2>
<span class="px-2.5 py-0.5 rounded-full text-xs font-medium bg-k8s-blue bg-opacity-10 text-k8s-blue">
Expand All @@ -152,22 +164,22 @@ <h2 class="text-lg font-semibold text-gray-900">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
</svg>
</div>
<div id="ns-${group.namespace}" class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 p-4">
<div id="ns-${group.namespace}" class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 p-4 ">
${group.services.map(service => `
<div class="bg-white rounded-lg border border-gray-200 p-4 hover:shadow-md transition-shadow duration-200">
<div class="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4 hover:shadow-md transition-shadow duration-200">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-medium text-gray-900 cursor-pointer" onclick="copyToClipboard('${service.name}','service')">${service.name}</h3>
<h3 class="text-lg font-medium text-gray-900 dark:text-white cursor-pointer" onclick="copyToClipboard('${service.name}','service')">${service.name}</h3>
<span class="px-2.5 py-0.5 rounded-full text-xs font-medium ${!service.lastFailure ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'}">
${!service.lastFailure ? 'Healthy' : 'Failed'}
</span>
</div>
<div class="space-y-2">
<div class="text-sm text-gray-500">Type: <span class="text-gray-900">${service.type}</span></div>
<div class="text-sm text-gray-500">Cluster IP: <span class="text-gray-900">${service.clusterIP}</span></div>
<div class="text-sm text-gray-500">Uptime: <span class="text-gray-900">${service.uptime}</span></div>
<div class="text-sm text-gray-500 dark:text-white">Type: <span class="${spanClass()}">${service.type}</span></div>
<div class="text-sm text-gray-500 dark:text-white">Cluster IP:<span class="${spanClass()}">${service.clusterIP}</span></div>
<div class="text-sm text-gray-500 dark:text-white">Uptime: <span class="${spanClass()}">${service.uptime}</span></div>
<div class="mt-4">
<h4 class="text-sm font-medium text-gray-900 mb-2">Endpoints</h4>
<h4 class="text-sm font-medium text-gray-900 dark:text-white mb-2">Endpoints</h4>
<div class="service-endpoints">
${service.endpoints.map(endpoint => `
<span
Expand Down Expand Up @@ -210,6 +222,9 @@ <h4 class="text-sm font-medium text-gray-900 mb-2">Ports</h4>
// Update namespace toggles if needed
updateNamespaceToggles(groups);
}
function spanClass() {
return 'px-2 py-1 space-x-8 text-xs font-medium rounded-full bg-gray-100 text-gray-600 cursor-pointer hover:bg-gray-200 transition-colors duration-150';
}
function getPortDescription(port) {
const d = [];
d.push(port.protocol);
Expand All @@ -232,7 +247,6 @@ <h4 class="text-sm font-medium text-gray-900 mb-2">Ports</h4>
updateServices(lastGroups);
}
}

function toggleNamespace(namespace) {
const element = document.getElementById(`ns-${namespace}`);
const chevron = document.getElementById(`chevron-${namespace}`);
Expand Down Expand Up @@ -311,7 +325,8 @@ <h4 class="text-sm font-medium text-gray-900 mb-2">Ports</h4>
try {
const response = await fetch('/api/contexts');
if (response.status === 400) {
document.getElementById('context-mode').textContent = 'Running in-cluster';
// document.getElementById('context-mode').textContent = 'In-Cluster';

return;
}

Expand All @@ -333,7 +348,7 @@ <h4 class="text-sm font-medium text-gray-900 mb-2">Ports</h4>
select.appendChild(option);
});

document.getElementById('context-mode').textContent = 'Running on desktop';
// document.getElementById('context-mode').textContent = 'Desktop';
document.getElementById('context-switcher').style.display = 'block';
} catch (err) {
console.error('Failed to load contexts:', err);
Expand All @@ -346,8 +361,8 @@ <h4 class="text-sm font-medium text-gray-900 mb-2">Ports</h4>

// Disable select during switch
e.target.disabled = true;
document.getElementById('context-mode').textContent = 'Switching context...';

// document.getElementById('context-mode').textContent = 'Switching context...';
showNotification("switching context",'info');
const response = await fetch('/api/contexts/switch', {
method: 'POST',
headers: {
Expand All @@ -356,24 +371,131 @@ <h4 class="text-sm font-medium text-gray-900 mb-2">Ports</h4>
body: JSON.stringify({ context: newContext })
});

if (!response.ok) throw new Error('Failed to switch context');
if (!response.ok) {
showNotification(`fail switch context to ${newContext}`,'error');
throw new Error('Failed to switch context');
}

// Store new context in localStorage
localStorage.setItem(CURRENT_CONTEXT_KEY, newContext);

// Reload services
document.getElementById('services-container').innerHTML = '<div class="text-center">Loading services...</div>';
window.location.reload();

showNotification("context updated",'success');
} catch (err) {
console.error('Failed to switch context:', err);
e.target.disabled = false;
loadContexts(); // Reset selection
showNotification('previous context restored','info')
}
});

// Initial load
loadContexts();

// Theme handling
function setTheme(theme) {
if (theme === 'dark') {
document.documentElement.classList.add('dark');
document.getElementById('theme-icon').textContent = ' 🌓 ';
} else {
document.documentElement.classList.remove('dark');
document.getElementById('theme-icon').textContent = ' ☀️';
}
localStorage.setItem('theme', theme);
}

function toggleTheme() {
const isDark = document.documentElement.classList.contains('dark');
setTheme(isDark ? 'light' : 'dark');
}

// Initialize theme
if (localStorage.theme === 'dark' || (!localStorage.theme && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
setTheme('dark');
} else {
setTheme('light');
}

class NotificationManager {
constructor() {
this.container = document.getElementById('notification-container');
}

show(message, type = 'info') {
// Create notification element
const notification = document.createElement('div');
notification.className = `
notification max-w-sm p-4 rounded-lg shadow-lg transform transition-all duration-300
flex items-center justify-between
${this.getTypeStyles(type)}
`;
// Add content
notification.innerHTML = `
<div class="flex items-center space-x-2">
${this.getIcon(type)}
<p class="text-sm font-medium">${message}</p>
</div>
<button class="ml-4 hover:opacity-75" onclick="this.parentElement.remove()">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
</svg>
</button>
`;

// Add to container
this.container.appendChild(notification);

// Animate in
requestAnimationFrame(() => {
notification.style.transform = 'translateX(0)';
notification.style.opacity = '1';
});

// Auto remove after delay
setTimeout(() => {
notification.style.opacity = '0';
notification.style.transform = 'translateX(100%)';
setTimeout(() => notification.remove(), 3000);
}, 5000);
}

getTypeStyles(type) {
const styles = {
success: 'bg-green-500 text-white',
error: 'bg-red-500 text-white',
warning: 'bg-yellow-500 text-white',
info: 'bg-blue-500 text-white'
};
return styles[type] || styles.info;
}

getIcon(type) {
const icons = {
success: `<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
</svg>`,
error: `<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
</svg>`,
warning: `<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
</svg>`,
info: `<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>`
};
return icons[type] || icons.info;
}
}

// Initialize notification manager
const notifications = new NotificationManager();
// Example usage in your existing code:
function showNotification(message, type) {
notifications.show(message, type);
}
</script>
</body>
</html>

0 comments on commit a874ce3

Please sign in to comment.