Full project scaffold: multi-stage Dockerfile (ARM64/AMD64), AsciiDoc↔TipTap bridge, Setup Wizard, CodeMirror source editor, Git-backed storage layer, LDAP+JWT auth skeleton, Tailwind mobile-first layout, and VS Code build/push tasks targeting registry at 192.168.0.19:5000. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
135 lines
4.4 KiB
Vue
135 lines
4.4 KiB
Vue
<script setup lang="ts">
|
|
import { reactive, ref } from 'vue'
|
|
import { useRouter } from 'vue-router'
|
|
import { gql } from '@/lib/gql'
|
|
import { useAppStore } from '@/stores/app'
|
|
|
|
const router = useRouter()
|
|
const app = useAppStore()
|
|
|
|
const step = ref(1)
|
|
const error = ref('')
|
|
const submitting = ref(false)
|
|
|
|
const form = reactive({
|
|
storagePath: '/data/wiki',
|
|
adminUser: 'admin',
|
|
adminPass: '',
|
|
jwtSecret: crypto.randomUUID().replace(/-/g, ''),
|
|
ldapEnabled: false,
|
|
ldap: {
|
|
host: '',
|
|
port: 389,
|
|
baseDN: '',
|
|
bindDN: '',
|
|
bindPassword: '',
|
|
},
|
|
})
|
|
|
|
async function submit() {
|
|
error.value = ''
|
|
submitting.value = true
|
|
try {
|
|
await gql(
|
|
`mutation Setup($i: SetupInput!) { setup(input: $i) }`,
|
|
{
|
|
i: {
|
|
storagePath: form.storagePath,
|
|
adminUser: form.adminUser,
|
|
adminPass: form.adminPass,
|
|
jwtSecret: form.jwtSecret,
|
|
ldap: form.ldapEnabled ? form.ldap : null,
|
|
},
|
|
},
|
|
)
|
|
app.requiresSetup = false
|
|
await app.login(form.adminUser, form.adminPass)
|
|
router.replace('/')
|
|
} catch (e: unknown) {
|
|
error.value = e instanceof Error ? e.message : String(e)
|
|
} finally {
|
|
submitting.value = false
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="min-h-screen flex items-center justify-center bg-surface-900 px-4">
|
|
<div class="w-full max-w-md bg-surface-800 rounded-2xl p-8 shadow-xl">
|
|
<h1 class="text-2xl font-bold mb-1">Welcome to Archivum</h1>
|
|
<p class="text-slate-400 text-sm mb-6">Complete setup to get started.</p>
|
|
|
|
<!-- Step 1: Storage & Admin -->
|
|
<form v-if="step === 1" class="space-y-4" @submit.prevent="step = 2">
|
|
<div>
|
|
<label class="block text-sm mb-1">Storage path</label>
|
|
<input v-model="form.storagePath" required class="input" />
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm mb-1">Admin username</label>
|
|
<input v-model="form.adminUser" required class="input" />
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm mb-1">Admin password</label>
|
|
<input v-model="form.adminPass" type="password" required class="input" />
|
|
</div>
|
|
<button type="submit" class="btn-primary w-full">Next →</button>
|
|
</form>
|
|
|
|
<!-- Step 2: LDAP (optional) -->
|
|
<form v-else-if="step === 2" class="space-y-4" @submit.prevent="submit">
|
|
<label class="flex items-center gap-2 cursor-pointer">
|
|
<input v-model="form.ldapEnabled" type="checkbox" class="rounded" />
|
|
<span class="text-sm">Enable LDAP authentication</span>
|
|
</label>
|
|
|
|
<template v-if="form.ldapEnabled">
|
|
<div class="grid grid-cols-2 gap-3">
|
|
<div>
|
|
<label class="block text-sm mb-1">Host</label>
|
|
<input v-model="form.ldap.host" required class="input" />
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm mb-1">Port</label>
|
|
<input v-model.number="form.ldap.port" type="number" required class="input" />
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm mb-1">Base DN</label>
|
|
<input v-model="form.ldap.baseDN" required class="input" />
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm mb-1">Bind DN</label>
|
|
<input v-model="form.ldap.bindDN" required class="input" />
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm mb-1">Bind password</label>
|
|
<input v-model="form.ldap.bindPassword" type="password" required class="input" />
|
|
</div>
|
|
</template>
|
|
|
|
<p v-if="error" class="text-red-400 text-sm">{{ error }}</p>
|
|
|
|
<div class="flex gap-3">
|
|
<button type="button" class="btn-secondary flex-1" @click="step = 1">← Back</button>
|
|
<button type="submit" class="btn-primary flex-1" :disabled="submitting">
|
|
{{ submitting ? 'Setting up…' : 'Finish setup' }}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.input {
|
|
@apply w-full bg-surface-900 border border-slate-600 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-blue-500;
|
|
}
|
|
.btn-primary {
|
|
@apply py-2 rounded-lg bg-blue-600 hover:bg-blue-500 font-medium text-sm disabled:opacity-50 transition;
|
|
}
|
|
.btn-secondary {
|
|
@apply py-2 rounded-lg bg-surface-900 hover:bg-surface-700 font-medium text-sm transition;
|
|
}
|
|
</style>
|