Files
dezky/apps/portal/pages/index.vue
T
Ronni Baslund 4bf6a85517 fix(stalwart): wire recovery admin + point portal tile at admin UI
- docker-compose: add STALWART_RECOVERY_ADMIN env so the env-file password
  works as a permanent recovery login. Without this, Stalwart prints a
  one-time bootstrap password to the logs and discards it after first setup
- portal: mail tile now links to /admin/ (the real Stalwart admin SPA),
  not /login (which is the OAuth client authorization UI for IMAP/SMTP
  clients like Thunderbird — confusing and unrelated)

The persistent admin (admin@dezky.local) was created via Stalwart's setup
wizard at /admin/init and lives in the stalwart_data volume. Recovery admin
in env is the "I lost the wizard credentials" escape hatch.
2026-05-23 22:51:25 +02:00

187 lines
3.9 KiB
Vue

<script setup lang="ts">
// Post-login landing. Auth middleware (nuxt-oidc-auth) gates access — anonymous
// visitors get bounced to /login by the customLoginPage config in nuxt.config.ts.
const { user, logout } = useOidcAuth()
</script>
<template>
<div class="page">
<header class="bar">
<div class="brand">
<span class="brand-tile">
<NodeMark :size="22" />
</span>
<span class="brand-name">dezky</span>
</div>
<div class="me">
<span class="email">{{ user?.userInfo?.email || user?.userName }}</span>
<button class="logout" @click="logout()">sign out</button>
</div>
</header>
<main class="stage">
<section class="hero">
<p class="eyebrow">Workspace · welcome</p>
<h1>Hi, {{ user?.userInfo?.name || user?.userName }}.</h1>
<p class="tagline">Sovereign workspace platform · all your services in one place.</p>
</section>
<section class="grid">
<a href="https://files.dezky.local" target="_blank" class="tile">
<span class="tile-name">Files</span>
<span class="tile-meta">OCIS · S3-backed storage</span>
</a>
<a href="https://mail.dezky.local/admin/" target="_blank" class="tile">
<span class="tile-name">Mail</span>
<span class="tile-meta">Stalwart · JMAP/IMAP/SMTP</span>
</a>
<a href="https://office.dezky.local" target="_blank" class="tile">
<span class="tile-name">Office</span>
<span class="tile-meta">Collabora · document editing</span>
</a>
<a href="https://auth.dezky.local" target="_blank" class="tile">
<span class="tile-name">Identity</span>
<span class="tile-meta">Authentik · SSO &amp; access</span>
</a>
</section>
</main>
</div>
</template>
<style scoped>
.page {
min-height: 100vh;
display: flex;
flex-direction: column;
}
.bar {
padding: 16px 24px;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid var(--border);
}
.brand {
display: flex;
align-items: center;
gap: 10px;
}
.brand-tile {
width: 30px;
height: 30px;
border-radius: 7px;
background: #0a0a0a;
display: inline-flex;
align-items: center;
justify-content: center;
}
.brand-name {
font-family: var(--font-mono);
font-size: 12px;
font-weight: 600;
}
.me {
display: flex;
align-items: center;
gap: 14px;
}
.email {
font-family: var(--font-mono);
font-size: 12px;
color: var(--text-dim);
}
.logout {
background: transparent;
border: 1px solid var(--border);
border-radius: 6px;
padding: 6px 12px;
font-family: var(--font-mono);
font-size: 11px;
color: var(--text);
}
.logout:hover {
background: rgba(10, 10, 10, 0.04);
}
.stage {
flex: 1;
padding: 48px 24px;
max-width: 960px;
width: 100%;
margin: 0 auto;
}
.hero {
margin-bottom: 48px;
}
.eyebrow {
font-family: var(--font-mono);
font-size: 10px;
letter-spacing: 0.18em;
text-transform: uppercase;
color: var(--text-mute);
margin: 0 0 12px 0;
}
.hero h1 {
margin: 0;
font-family: var(--font-display);
font-weight: 600;
font-size: 40px;
letter-spacing: -0.025em;
line-height: 1.05;
}
.tagline {
margin: 12px 0 0 0;
color: var(--text-dim);
font-size: 15px;
line-height: 1.5;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 12px;
}
.tile {
background: var(--elevated);
border: 1px solid var(--border);
border-radius: 10px;
padding: 20px;
text-decoration: none;
color: inherit;
display: flex;
flex-direction: column;
gap: 6px;
transition: border-color 120ms ease, transform 120ms ease;
}
.tile:hover {
border-color: var(--border-hi);
transform: translateY(-1px);
}
.tile-name {
font-family: var(--font-display);
font-weight: 600;
font-size: 17px;
}
.tile-meta {
font-family: var(--font-mono);
font-size: 11px;
color: var(--text-mute);
}
</style>