apps/admin/: - Nuxt 4.1.3 + @nuxt/ui 4 + @nuxtjs/supabase, port 3017 staging - 7 pages: index (59 LOC dashboard), login (72 LOC), auth/confirm, plus stubs für domains/users/stats/moderation (14-17 LOC each, content für separate Phase 2 Session) - composables/useAdminAuth.ts: Supabase login + verifyAdminRole hook - middleware/admin-auth.ts: route guard (Phase 3 backend-check ready) - layouts/default.vue, app.vue, README.md - nuxt.config.ts: SSR=true, port 3017, dark-mode preference, Supabase pkce-flow, runtimeConfig.adminSecret für Phase 3 backend-binding Deploy-Infrastructure: - .github/workflows/deploy-admin-staging.yml: build admin auf push to main mit path-filter apps/admin/**, scp tar zu Server, atomic-mv + pm2 restart - scripts/deploy-admin-from-artifact.sh: Server-side deploy (extract, atomic mv, pm2 reload). Kein prisma-migrate (admin hat kein eigenes DB-Schema). - apps/admin/start-admin-staging.sh: pm2 start-script mit Infisical-wrapper, port 3017, mappt Infisical SUPABASE_URL/KEY auf NUXT_PUBLIC_* - ecosystem.config.js: rebreak-admin-staging Eintrag (port 3017, max_memory_restart 400M) - ops/nginx/admin-staging.rebreak.org.conf: HTTP→HTTPS redirect, SSL paths, proxy auf 127.0.0.1:3017, noindex header Pending User-Actions für go-live: 1. DNS-A-Record admin.staging.rebreak.org → 49.13.55.22 2. SSL-cert via certbot (oder bestehender wildcard *.staging.rebreak.org) 3. nginx-config auf Server aktivieren (sudo cp + ln + reload) 4. pm2 initial start: pm2 start ecosystem.config.js --only rebreak-admin-staging 5. Infisical-secret ADMIN_SECRET (server-only, Phase 3 binding) GH-Actions: keine neuen Secrets (nutzt bestehende HETZNER_SSH_KEY/HOST/USER) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
57 lines
1.9 KiB
Vue
57 lines
1.9 KiB
Vue
<template>
|
|
<div class="min-h-screen bg-gray-950 text-gray-100">
|
|
<!-- Topbar -->
|
|
<header class="border-b border-gray-800 bg-gray-900 px-6 py-3 flex items-center justify-between">
|
|
<div class="flex items-center gap-3">
|
|
<span class="text-sm font-semibold tracking-wide text-gray-400 uppercase">rebreak</span>
|
|
<span class="text-gray-600">/</span>
|
|
<span class="text-sm font-medium text-white">Admin</span>
|
|
</div>
|
|
<div class="flex items-center gap-4">
|
|
<span class="text-xs text-gray-500">{{ adminEmail }}</span>
|
|
<UButton
|
|
size="xs"
|
|
color="neutral"
|
|
variant="ghost"
|
|
label="Logout"
|
|
@click="logout"
|
|
/>
|
|
</div>
|
|
</header>
|
|
|
|
<!-- Sidebar + Content -->
|
|
<div class="flex">
|
|
<aside class="w-56 min-h-[calc(100vh-49px)] border-r border-gray-800 bg-gray-900 py-4">
|
|
<nav class="flex flex-col gap-1 px-3">
|
|
<NuxtLink
|
|
v-for="item in nav"
|
|
:key="item.to"
|
|
:to="item.to"
|
|
class="flex items-center gap-2 rounded px-3 py-2 text-sm text-gray-400 hover:bg-gray-800 hover:text-white transition-colors"
|
|
active-class="bg-gray-800 text-white"
|
|
>
|
|
<UIcon :name="item.icon" class="h-4 w-4 shrink-0" />
|
|
{{ item.label }}
|
|
</NuxtLink>
|
|
</nav>
|
|
</aside>
|
|
|
|
<main class="flex-1 p-6">
|
|
<slot />
|
|
</main>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
const { adminEmail, logout } = useAdminAuth()
|
|
|
|
const nav = [
|
|
{ to: "/", label: "Dashboard", icon: "heroicons:home" },
|
|
{ to: "/domains", label: "Domain-Approval", icon: "heroicons:globe-alt" },
|
|
{ to: "/users", label: "User-Management", icon: "heroicons:users" },
|
|
{ to: "/stats", label: "Statistiken", icon: "heroicons:chart-bar" },
|
|
{ to: "/moderation", label: "Moderation", icon: "heroicons:shield-check" },
|
|
]
|
|
</script>
|