Some checks failed
- deploy.sh writes .build-info with commit SHA and timestamp after git pull - /health endpoint now returns version, commit, and deployed_at fields - Admin sidebar footer shows version and commit SHA - Hetzner docs updated: runner --config flag, swap, and runner timeout Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
145 lines
6.7 KiB
HTML
145 lines
6.7 KiB
HTML
{# app/templates/admin/partials/sidebar.html #}
|
|
{# Dynamic sidebar that renders from menu discovery API #}
|
|
|
|
{# ============================================================================
|
|
SIDEBAR CONTENT (shared between desktop and mobile)
|
|
============================================================================ #}
|
|
|
|
{% macro sidebar_content() %}
|
|
<div class="py-4 text-gray-500 dark:text-gray-400" x-init="loadMenuConfig()">
|
|
<a class="ml-6 text-lg font-bold text-gray-800 dark:text-gray-200" href="/admin/dashboard">
|
|
Admin Portal
|
|
</a>
|
|
|
|
<!-- Loading State -->
|
|
<div x-show="menuLoading" class="mt-6 px-6">
|
|
<div class="animate-pulse space-y-3">
|
|
<div class="h-4 bg-gray-200 dark:bg-gray-700 rounded w-3/4"></div>
|
|
<div class="h-4 bg-gray-200 dark:bg-gray-700 rounded w-1/2"></div>
|
|
<div class="h-4 bg-gray-200 dark:bg-gray-700 rounded w-2/3"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Menu Content (loaded from API) -->
|
|
<div x-show="!menuLoading && menuData">
|
|
<!-- Dynamic Sections from API -->
|
|
<template x-for="section in (menuData?.sections || [])" :key="section.id">
|
|
<div>
|
|
<!-- Section Divider -->
|
|
<div class="px-6 my-4">
|
|
<hr class="border-gray-200 dark:border-gray-700" />
|
|
</div>
|
|
|
|
<!-- Section Header (collapsible) -->
|
|
<button
|
|
@click="toggleSection(section.id)"
|
|
class="flex items-center justify-between w-full px-6 py-2 text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase tracking-wider hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors"
|
|
>
|
|
<span x-text="section.label"></span>
|
|
<span
|
|
x-html="$icon('chevron-down', 'w-4 h-4 transition-transform duration-200')"
|
|
:class="{ 'rotate-180': openSections[section.id] }"
|
|
></span>
|
|
</button>
|
|
|
|
<!-- Section Items -->
|
|
<ul
|
|
x-show="openSections[section.id]"
|
|
x-transition:enter="transition-all duration-200 ease-out"
|
|
x-transition:enter-start="opacity-0 -translate-y-2"
|
|
x-transition:enter-end="opacity-100 translate-y-0"
|
|
x-transition:leave="transition-all duration-150 ease-in"
|
|
x-transition:leave-start="opacity-100 translate-y-0"
|
|
x-transition:leave-end="opacity-0 -translate-y-2"
|
|
class="mt-1 overflow-hidden"
|
|
>
|
|
<template x-for="item in section.items" :key="item.id">
|
|
<li class="relative px-6 py-3">
|
|
<span
|
|
x-show="currentPage === item.id"
|
|
class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg"
|
|
aria-hidden="true"
|
|
></span>
|
|
<a
|
|
class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
|
:class="currentPage === item.id ? 'text-gray-800 dark:text-gray-100' : ''"
|
|
:href="item.url"
|
|
>
|
|
<span x-html="$icon(item.icon)"></span>
|
|
<span class="ml-4" x-text="item.label"></span>
|
|
</a>
|
|
</li>
|
|
</template>
|
|
</ul>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
|
|
<!-- Fallback: Static menu when API not loaded -->
|
|
<div x-show="!menuLoading && !menuData" class="mt-6">
|
|
<ul>
|
|
<li class="relative px-6 py-3">
|
|
<span x-show="currentPage === 'dashboard'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
|
|
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
|
:class="currentPage === 'dashboard' ? 'text-gray-800 dark:text-gray-100' : ''"
|
|
href="/admin/dashboard">
|
|
<span x-html="$icon('home')"></span>
|
|
<span class="ml-4">Dashboard</span>
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
<div class="px-6 mt-4">
|
|
<p class="text-xs text-gray-400 dark:text-gray-500">
|
|
Menu unavailable. Showing default items only.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endmacro %}
|
|
|
|
{# ============================================================================
|
|
DESKTOP SIDEBAR
|
|
============================================================================ #}
|
|
|
|
<aside class="z-20 hidden w-64 overflow-y-auto bg-white dark:bg-gray-800 md:block flex-shrink-0 flex flex-col">
|
|
<div class="flex-1">
|
|
{{ sidebar_content() }}
|
|
</div>
|
|
<div class="px-6 py-3 border-t border-gray-200 dark:border-gray-700">
|
|
<p class="text-[10px] font-mono text-gray-400 dark:text-gray-600">
|
|
v{{ config.version }}
|
|
{% if config.commit %}
|
|
· <span title="Deployed {{ config.deployed_at or '' }}">{{ config.commit }}</span>
|
|
{% endif %}
|
|
</p>
|
|
</div>
|
|
</aside>
|
|
|
|
{# ============================================================================
|
|
MOBILE SIDEBAR
|
|
============================================================================ #}
|
|
|
|
<!-- Mobile sidebar backdrop -->
|
|
<div x-show="isSideMenuOpen"
|
|
x-transition:enter="transition ease-in-out duration-150"
|
|
x-transition:enter-start="opacity-0"
|
|
x-transition:enter-end="opacity-100"
|
|
x-transition:leave="transition ease-in-out duration-150"
|
|
x-transition:leave-start="opacity-100"
|
|
x-transition:leave-end="opacity-0"
|
|
class="fixed inset-0 z-10 flex items-end bg-black bg-opacity-50 sm:items-center sm:justify-center"></div>
|
|
|
|
<!-- Mobile sidebar panel -->
|
|
<aside class="fixed inset-y-0 z-20 flex-shrink-0 w-64 mt-16 overflow-y-auto bg-white dark:bg-gray-800 md:hidden"
|
|
x-show="isSideMenuOpen"
|
|
x-transition:enter="transition ease-in-out duration-150"
|
|
x-transition:enter-start="opacity-0 transform -translate-x-20"
|
|
x-transition:enter-end="opacity-100"
|
|
x-transition:leave="transition ease-in-out duration-150"
|
|
x-transition:leave-start="opacity-100"
|
|
x-transition:leave-end="opacity-0 transform -translate-x-20"
|
|
@click.away="closeSideMenu"
|
|
@keydown.escape="closeSideMenu">
|
|
{{ sidebar_content() }}
|
|
</aside>
|