Drafting admin app (frontend)
This commit is contained in:
parent
02ac4adc77
commit
e0717f005a
13
frontend/admin/index.html
Normal file
13
frontend/admin/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/src/assets/icon.webp" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Passkey Admin</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="admin-app"></div>
|
||||
<script type="module" src="/src/admin/main.js"></script>
|
||||
</body>
|
||||
</html>
|
70
frontend/src/admin/AdminApp.vue
Normal file
70
frontend/src/admin/AdminApp.vue
Normal file
@ -0,0 +1,70 @@
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
|
||||
const info = ref(null)
|
||||
const loading = ref(true)
|
||||
const error = ref(null)
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const res = await fetch('/auth/user-info', { method: 'POST' })
|
||||
const data = await res.json()
|
||||
if (data.detail) throw new Error(data.detail)
|
||||
info.value = data
|
||||
} catch (e) {
|
||||
error.value = e.message
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container">
|
||||
<h1>Passkey Admin</h1>
|
||||
<p class="subtitle">Manage organizations, roles, and permissions</p>
|
||||
|
||||
<div v-if="loading">Loading…</div>
|
||||
<div v-else-if="error" class="error">{{ error }}</div>
|
||||
<div v-else>
|
||||
<div v-if="!info?.authenticated">
|
||||
<p>You must be authenticated.</p>
|
||||
</div>
|
||||
<div v-else-if="!(info?.is_global_admin || info?.is_org_admin)">
|
||||
<p>Insufficient permissions.</p>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div class="card">
|
||||
<h2>User</h2>
|
||||
<div>{{ info.user.user_name }} ({{ info.user.user_uuid }})</div>
|
||||
<div>Role: {{ info.role?.display_name }}</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Organization</h2>
|
||||
<div>{{ info.org?.display_name }} ({{ info.org?.uuid }})</div>
|
||||
<div>Role permissions: {{ info.role?.permissions?.join(', ') }}</div>
|
||||
<div>Org grantable: {{ info.org?.permissions?.join(', ') }}</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Permissions</h2>
|
||||
<div>Effective: {{ info.permissions?.join(', ') }}</div>
|
||||
<div>Global admin: {{ info.is_global_admin ? 'yes' : 'no' }}</div>
|
||||
<div>Org admin: {{ info.is_org_admin ? 'yes' : 'no' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.container { max-width: 960px; margin: 2rem auto; padding: 0 1rem; }
|
||||
.subtitle { color: #888 }
|
||||
.card { margin: 1rem 0; padding: 1rem; border: 1px solid #eee; border-radius: 8px; }
|
||||
.error { color: #a00 }
|
||||
</style>
|
6
frontend/src/admin/main.js
Normal file
6
frontend/src/admin/main.js
Normal file
@ -0,0 +1,6 @@
|
||||
import '../assets/style.css'
|
||||
|
||||
import { createApp } from 'vue'
|
||||
import AdminApp from './AdminApp.vue'
|
||||
|
||||
createApp(AdminApp).mount('#admin-app')
|
@ -1,6 +1,7 @@
|
||||
import { fileURLToPath, URL } from 'node:url'
|
||||
|
||||
import { defineConfig } from 'vite'
|
||||
import { resolve } from 'node:path'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
|
||||
// https://vite.dev/config/
|
||||
@ -13,6 +14,7 @@ export default defineConfig(({ command, mode }) => ({
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||
},
|
||||
},
|
||||
// Use absolute paths at dev, deploy under /auth/
|
||||
base: command === 'build' ? '/auth/' : '/',
|
||||
server: {
|
||||
port: 4403,
|
||||
@ -27,6 +29,15 @@ export default defineConfig(({ command, mode }) => ({
|
||||
build: {
|
||||
outDir: '../passkey/frontend-build',
|
||||
emptyOutDir: true,
|
||||
assetsDir: 'assets'
|
||||
assetsDir: 'assets',
|
||||
rollupOptions: {
|
||||
input: {
|
||||
index: resolve(__dirname, 'index.html'),
|
||||
admin: resolve(__dirname, 'admin/index.html')
|
||||
},
|
||||
output: {
|
||||
// Ensure HTML files land as /auth/index.html and /auth/admin.html -> we will serve /auth/admin mapping in backend
|
||||
}
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
@ -70,6 +70,19 @@ async def redirect_to_index():
|
||||
return FileResponse(STATIC_DIR / "index.html")
|
||||
|
||||
|
||||
@app.get("/auth/admin")
|
||||
async def serve_admin():
|
||||
"""Serve the admin app entry point."""
|
||||
# Vite MPA builds admin as admin.html in the same outDir
|
||||
admin_html = STATIC_DIR / "admin.html"
|
||||
# If configured to emit admin/index.html, support that too
|
||||
if not admin_html.exists():
|
||||
alt = STATIC_DIR / "admin" / "index.html"
|
||||
if alt.exists():
|
||||
return FileResponse(alt)
|
||||
return FileResponse(admin_html)
|
||||
|
||||
|
||||
# Register API routes
|
||||
register_api_routes(app)
|
||||
register_reset_routes(app)
|
||||
|
Loading…
x
Reference in New Issue
Block a user