248 lines
12 KiB
HTML
248 lines
12 KiB
HTML
{{define "base"}}
|
|
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
<title>{{.Title}} • AtlasOS</title>
|
|
|
|
<!-- v1: Tailwind CDN (later: bundle local) -->
|
|
<!-- Try multiple CDN sources for better reliability -->
|
|
<script src="https://cdn.tailwindcss.com" onerror="this.onerror=null;this.src='https://unpkg.com/@tailwindcss/browser@4/dist/tailwind.min.js'"></script>
|
|
<script src="https://unpkg.com/htmx.org@1.9.12/dist/htmx.min.js" onerror="this.onerror=null;this.src='https://cdn.jsdelivr.net/npm/htmx.org@1.9.12/dist/htmx.min.js'"></script>
|
|
</head>
|
|
|
|
<body class="bg-slate-950 text-slate-100">
|
|
<!-- Mobile Overlay -->
|
|
<div id="mobile-overlay" class="fixed inset-0 bg-black/50 z-40 lg:hidden hidden" onclick="toggleSidebar()"></div>
|
|
|
|
<!-- Sidebar Navigation -->
|
|
<aside id="sidebar" class="fixed left-0 top-0 h-full w-64 bg-slate-900 border-r border-slate-800 z-50 transform -translate-x-full lg:translate-x-0 transition-transform duration-300 ease-in-out flex flex-col">
|
|
<!-- Logo/Brand -->
|
|
<div class="p-4 border-b border-slate-800 flex items-center gap-3">
|
|
<div class="h-10 w-10 rounded-lg bg-blue-600 flex items-center justify-center font-bold text-white">A</div>
|
|
<div>
|
|
<div class="font-semibold leading-tight text-white">AtlasOS</div>
|
|
<div class="text-xs text-slate-400 leading-tight">Storage Controller v1</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Navigation Menu -->
|
|
<nav class="flex-1 overflow-y-auto py-4">
|
|
<div class="px-2 space-y-1">
|
|
<a href="/" class="nav-link flex items-center gap-3 px-4 py-3 rounded-lg text-slate-300 hover:bg-slate-800 hover:text-white transition-colors">
|
|
<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="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"></path>
|
|
</svg>
|
|
<span>Dashboard</span>
|
|
</a>
|
|
<a href="/storage" class="nav-link flex items-center gap-3 px-4 py-3 rounded-lg text-slate-300 hover:bg-slate-800 hover:text-white transition-colors">
|
|
<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="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4"></path>
|
|
</svg>
|
|
<span>Storage</span>
|
|
</a>
|
|
<a href="/shares" class="nav-link flex items-center gap-3 px-4 py-3 rounded-lg text-slate-300 hover:bg-slate-800 hover:text-white transition-colors">
|
|
<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="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4"></path>
|
|
</svg>
|
|
<span>Shares</span>
|
|
</a>
|
|
<a href="/iscsi" class="nav-link flex items-center gap-3 px-4 py-3 rounded-lg text-slate-300 hover:bg-slate-800 hover:text-white transition-colors">
|
|
<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="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z"></path>
|
|
</svg>
|
|
<span>iSCSI</span>
|
|
</a>
|
|
<a href="/protection" class="nav-link flex items-center gap-3 px-4 py-3 rounded-lg text-slate-300 hover:bg-slate-800 hover:text-white transition-colors">
|
|
<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="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"></path>
|
|
</svg>
|
|
<span>Data Protection</span>
|
|
</a>
|
|
<a href="/management" class="nav-link flex items-center gap-3 px-4 py-3 rounded-lg text-slate-300 hover:bg-slate-800 hover:text-white transition-colors">
|
|
<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="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"></path>
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
|
|
</svg>
|
|
<span>Management</span>
|
|
</a>
|
|
<a href="#" class="nav-link flex items-center gap-3 px-4 py-3 rounded-lg text-slate-500 opacity-50 cursor-not-allowed">
|
|
<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="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"></path>
|
|
</svg>
|
|
<span>Monitoring</span>
|
|
</a>
|
|
</div>
|
|
</nav>
|
|
|
|
<!-- Auth Status at Bottom -->
|
|
<div class="p-4 border-t border-slate-800" id="auth-status-sidebar">
|
|
<div class="text-sm text-slate-400">Loading...</div>
|
|
</div>
|
|
</aside>
|
|
|
|
<!-- Top Header with Burger Menu -->
|
|
<header class="sticky top-0 z-30 border-b border-slate-800 bg-slate-950/80 backdrop-blur lg:ml-64">
|
|
<div class="px-4 py-3 flex items-center justify-between">
|
|
<!-- Burger Menu Button (Mobile Only) -->
|
|
<button id="burger-btn" onclick="toggleSidebar()" class="lg:hidden p-2 rounded-lg text-slate-300 hover:bg-slate-800 hover:text-white transition-colors">
|
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path id="burger-icon" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
|
|
<path id="close-icon" class="hidden" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
|
</svg>
|
|
</button>
|
|
|
|
<!-- Page Title (Mobile) / Spacer (Desktop) -->
|
|
<div class="lg:hidden flex-1 text-center">
|
|
<div class="font-semibold text-white">AtlasOS</div>
|
|
</div>
|
|
<div class="hidden lg:block flex-1"></div>
|
|
|
|
<!-- Auth Status (Desktop) -->
|
|
<div class="hidden lg:block" id="auth-status-header">
|
|
<div class="text-sm text-slate-400">Loading...</div>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<!-- Main Content -->
|
|
<main class="lg:ml-64 min-h-screen">
|
|
<div class="px-4 py-6 lg:px-6 lg:py-8">
|
|
{{$ct := getContentTemplate .}}
|
|
{{if eq $ct "storage-content"}}
|
|
{{template "storage-content" .}}
|
|
{{else if eq $ct "shares-content"}}
|
|
{{template "shares-content" .}}
|
|
{{else if eq $ct "iscsi-content"}}
|
|
{{template "iscsi-content" .}}
|
|
{{else if eq $ct "protection-content"}}
|
|
{{template "protection-content" .}}
|
|
{{else if eq $ct "management-content"}}
|
|
{{template "management-content" .}}
|
|
{{else if eq $ct "login-content"}}
|
|
{{template "login-content" .}}
|
|
{{else}}
|
|
{{template "content" .}}
|
|
{{end}}
|
|
</main>
|
|
|
|
</div>
|
|
</main>
|
|
|
|
<footer class="lg:ml-64 border-t border-slate-800 bg-slate-950/80 px-4 py-6 text-xs text-slate-500">
|
|
<div class="flex flex-col sm:flex-row items-center justify-between gap-2">
|
|
<span>AtlasOS • {{nowRFC3339}}</span>
|
|
<span>Build: {{index .Build "version"}}</span>
|
|
</div>
|
|
</footer>
|
|
|
|
<script>
|
|
// Toggle sidebar for mobile
|
|
function toggleSidebar() {
|
|
const sidebar = document.getElementById('sidebar');
|
|
const overlay = document.getElementById('mobile-overlay');
|
|
const burgerIcon = document.getElementById('burger-icon');
|
|
const closeIcon = document.getElementById('close-icon');
|
|
|
|
if (sidebar.classList.contains('-translate-x-full')) {
|
|
sidebar.classList.remove('-translate-x-full');
|
|
overlay.classList.remove('hidden');
|
|
burgerIcon.classList.add('hidden');
|
|
closeIcon.classList.remove('hidden');
|
|
document.body.style.overflow = 'hidden';
|
|
} else {
|
|
sidebar.classList.add('-translate-x-full');
|
|
overlay.classList.add('hidden');
|
|
burgerIcon.classList.remove('hidden');
|
|
closeIcon.classList.add('hidden');
|
|
document.body.style.overflow = '';
|
|
}
|
|
}
|
|
|
|
// Close sidebar when clicking nav link on mobile
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const navLinks = document.querySelectorAll('.nav-link');
|
|
navLinks.forEach(link => {
|
|
link.addEventListener('click', function() {
|
|
if (window.innerWidth < 1024) {
|
|
toggleSidebar();
|
|
}
|
|
});
|
|
});
|
|
|
|
// Update active nav link based on current URL
|
|
const currentPath = window.location.pathname;
|
|
navLinks.forEach(link => {
|
|
if (link.getAttribute('href') === currentPath) {
|
|
link.classList.add('bg-slate-800', 'text-white');
|
|
link.classList.remove('text-slate-300');
|
|
}
|
|
});
|
|
});
|
|
|
|
// Update auth status in navigation
|
|
function updateAuthStatus() {
|
|
const authStatusHeader = document.getElementById('auth-status-header');
|
|
const authStatusSidebar = document.getElementById('auth-status-sidebar');
|
|
|
|
const token = localStorage.getItem('atlas_token');
|
|
const userStr = localStorage.getItem('atlas_user');
|
|
|
|
const authHTML = (token && userStr) ? (() => {
|
|
try {
|
|
const user = JSON.parse(userStr);
|
|
return `
|
|
<div class="flex items-center gap-2">
|
|
<span class="text-slate-300 text-sm">${user.username || 'User'}</span>
|
|
<button onclick="handleLogout()" class="px-2 py-1 text-xs bg-slate-700 hover:bg-slate-600 text-white rounded transition-colors">
|
|
Logout
|
|
</button>
|
|
</div>
|
|
`;
|
|
} catch {
|
|
return `
|
|
<a href="/login" class="px-2 py-1 text-xs bg-blue-600 hover:bg-blue-700 text-white rounded transition-colors">Login</a>
|
|
`;
|
|
}
|
|
})() : `
|
|
<a href="/login" class="px-2 py-1 text-xs bg-blue-600 hover:bg-blue-700 text-white rounded transition-colors">Login</a>
|
|
`;
|
|
|
|
if (authStatusHeader) authStatusHeader.innerHTML = authHTML;
|
|
if (authStatusSidebar) authStatusSidebar.innerHTML = authHTML;
|
|
}
|
|
|
|
function handleLogout() {
|
|
localStorage.removeItem('atlas_token');
|
|
localStorage.removeItem('atlas_user');
|
|
window.location.href = '/login';
|
|
}
|
|
|
|
// Update on page load
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', updateAuthStatus);
|
|
} else {
|
|
updateAuthStatus();
|
|
}
|
|
|
|
// Close sidebar on window resize to desktop
|
|
window.addEventListener('resize', function() {
|
|
if (window.innerWidth >= 1024) {
|
|
const sidebar = document.getElementById('sidebar');
|
|
const overlay = document.getElementById('mobile-overlay');
|
|
const burgerIcon = document.getElementById('burger-icon');
|
|
const closeIcon = document.getElementById('close-icon');
|
|
|
|
sidebar.classList.add('-translate-x-full');
|
|
overlay.classList.add('hidden');
|
|
burgerIcon.classList.remove('hidden');
|
|
closeIcon.classList.add('hidden');
|
|
document.body.style.overflow = '';
|
|
}
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|
|
{{end}}
|