First-run creates the superadmin in-app; wizard seeds reference data
No CLI needed for a UI-driven install. - Login page: when the instance has no users yet (/setup/needs-admin), it shows a "create the first admin" form instead of login. Submitting creates the admin (admin role = full access / superadmin) via /setup/create-admin, logs in, and goes to the setup wizard. - Wizard Starter Data step: "Seed core reference data" button (statuses, types, permissions, default settings via /setup/seed-reference) alongside the common vendors, so a UI-first install gets the data the app needs. - Add waitress to requirements.txt (the IIS web.config launches it but it was missing - found while validating the Windows install). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,34 +4,40 @@
|
||||
<img src="/ge-aerospace-logo.svg" alt="GE Aerospace" class="login-logo" />
|
||||
<h1>ShopDB</h1>
|
||||
|
||||
<div v-if="error" class="error-message">
|
||||
{{ error }}
|
||||
</div>
|
||||
<div v-if="error" class="error-message">{{ error }}</div>
|
||||
|
||||
<form @submit.prevent="handleLogin">
|
||||
<!-- First run: no users yet - create the first admin -->
|
||||
<template v-if="mode === 'createadmin'">
|
||||
<p class="first-run-note">First run - create your admin account.</p>
|
||||
<form @submit.prevent="handleCreateAdmin">
|
||||
<div class="form-group">
|
||||
<label for="newusername">Username <span class="hint">(your SSO)</span></label>
|
||||
<input id="newusername" v-model="username" type="text" class="form-control" required autofocus />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="email">Email</label>
|
||||
<input id="email" v-model="email" type="email" class="form-control" required />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="newpassword">Password</label>
|
||||
<input id="newpassword" v-model="password" type="password" class="form-control" required minlength="8" />
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary" :disabled="loading">
|
||||
{{ loading ? 'Creating...' : 'Create admin & continue' }}
|
||||
</button>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<!-- Normal login -->
|
||||
<form v-else @submit.prevent="handleLogin">
|
||||
<div class="form-group">
|
||||
<label for="username">Username</label>
|
||||
<input
|
||||
id="username"
|
||||
v-model="username"
|
||||
type="text"
|
||||
class="form-control"
|
||||
required
|
||||
autofocus
|
||||
/>
|
||||
<input id="username" v-model="username" type="text" class="form-control" required autofocus />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="password">Password</label>
|
||||
<input
|
||||
id="password"
|
||||
v-model="password"
|
||||
type="password"
|
||||
class="form-control"
|
||||
required
|
||||
/>
|
||||
<input id="password" v-model="password" type="password" class="form-control" required />
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary" :disabled="loading">
|
||||
{{ loading ? 'Logging in...' : 'Login' }}
|
||||
</button>
|
||||
@@ -41,30 +47,56 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useAuthStore } from '../stores/auth'
|
||||
import { setupApi } from '../api'
|
||||
|
||||
const router = useRouter()
|
||||
const authStore = useAuthStore()
|
||||
|
||||
const mode = ref('login')
|
||||
const username = ref('')
|
||||
const email = ref('')
|
||||
const password = ref('')
|
||||
const error = ref('')
|
||||
const loading = ref(false)
|
||||
|
||||
onMounted(async () => {
|
||||
// If the instance has no users yet, offer to create the first admin.
|
||||
try {
|
||||
const response = await setupApi.needsAdmin()
|
||||
if (response.data?.data?.needsadmin) mode.value = 'createadmin'
|
||||
} catch (err) { /* fall back to normal login */ }
|
||||
})
|
||||
|
||||
async function handleLogin() {
|
||||
error.value = ''
|
||||
loading.value = true
|
||||
|
||||
const result = await authStore.login(username.value, password.value)
|
||||
|
||||
loading.value = false
|
||||
if (result.success) router.push('/')
|
||||
else error.value = result.message
|
||||
}
|
||||
|
||||
if (result.success) {
|
||||
router.push('/')
|
||||
} else {
|
||||
error.value = result.message
|
||||
async function handleCreateAdmin() {
|
||||
error.value = ''
|
||||
loading.value = true
|
||||
try {
|
||||
await setupApi.createAdmin({ username: username.value, email: email.value, password: password.value })
|
||||
// Log straight in with the new credentials, then on to the setup wizard.
|
||||
const result = await authStore.login(username.value, password.value)
|
||||
if (result.success) router.push('/setup')
|
||||
else { mode.value = 'login'; error.value = 'Admin created - please sign in.' }
|
||||
} catch (err) {
|
||||
error.value = err.response?.data?.data?.error?.message || err.response?.data?.message || 'Could not create admin'
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.first-run-note { color: var(--text-light); font-size: 0.9rem; margin-bottom: 1rem; }
|
||||
.hint { color: var(--text-light); font-weight: normal; font-size: 0.85em; }
|
||||
</style>
|
||||
|
||||
@@ -112,10 +112,21 @@
|
||||
<!-- Starter data -->
|
||||
<div v-else-if="current.key === 'data'">
|
||||
<h2>Starter data</h2>
|
||||
<p class="hint">Add common hardware vendors so you can start adding assets right away. Safe to run - it skips ones you already have.</p>
|
||||
<button class="btn btn-secondary" :disabled="seeding" @click="seedStarter">
|
||||
{{ seeding ? 'Adding...' : 'Add common vendors' }}
|
||||
</button>
|
||||
<p class="hint">Seed the data a new site needs. Both are idempotent - safe to run more than once.</p>
|
||||
<div class="starter-actions">
|
||||
<div>
|
||||
<button class="btn btn-secondary" :disabled="seeding" @click="seedReference">
|
||||
{{ seeding ? 'Working...' : 'Seed core reference data' }}
|
||||
</button>
|
||||
<p class="hint">Statuses, machine/location/relationship types, permissions, default settings. Run this if you installed without the CLI seed.</p>
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn btn-secondary" :disabled="seeding" @click="seedStarter">
|
||||
{{ seeding ? 'Working...' : 'Add common vendors' }}
|
||||
</button>
|
||||
<p class="hint">Dell, HP, Lenovo, and other common hardware vendors.</p>
|
||||
</div>
|
||||
</div>
|
||||
<p v-if="seedResult" class="seed-result">{{ seedResult }}</p>
|
||||
</div>
|
||||
|
||||
@@ -325,6 +336,18 @@ async function togglePlugin(plugin, enabled) {
|
||||
}
|
||||
}
|
||||
|
||||
async function seedReference() {
|
||||
seeding.value = true
|
||||
try {
|
||||
const response = await setupApi.seedReference()
|
||||
seedResult.value = response.data?.message || 'Reference data seeded.'
|
||||
} catch (err) {
|
||||
toast.error(apiError(err, 'Could not seed reference data'))
|
||||
} finally {
|
||||
seeding.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function seedStarter() {
|
||||
seeding.value = true
|
||||
try {
|
||||
@@ -384,6 +407,8 @@ async function finish() {
|
||||
.provision-note p:last-child { margin-bottom: 0; }
|
||||
.provision-tables code, .provision-docs code { background: var(--bg-card); border: 1px solid var(--border); border-radius: 3px; padding: 0 0.3rem; margin-right: 0.3rem; font-size: 0.78rem; }
|
||||
.seed-result { margin-top: 0.75rem; color: var(--success); font-size: 0.88rem; }
|
||||
.starter-actions { display: flex; flex-direction: column; gap: 1.1rem; }
|
||||
.starter-actions .hint { margin: 0.35rem 0 0; }
|
||||
.config-block { margin-top: 1.25rem; padding-top: 1rem; border-top: 1px solid var(--border); }
|
||||
.config-title { margin: 0 0 0.75rem; font-size: 1rem; text-transform: capitalize; }
|
||||
.secret-tag { margin-left: 0.5rem; font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--warning); border: 1px solid var(--warning); border-radius: 4px; padding: 0 0.3rem; }
|
||||
|
||||
Reference in New Issue
Block a user