feat: integrate MarkBase Admin API with Bearer token authentication

- Added useAdminAuth composable for login/logout/token management
- Admin Users tab now requires login before showing users
- loadUsers/loadShares now use direct fetch with Bearer token
- Added login form with username/password fields
- Token stored in localStorage with expiry check
- Default credentials: demo/demo123
This commit is contained in:
2026-10-27 02:17:41 +08:00
parent 4129c174ed
commit 3b638f0416
4 changed files with 169 additions and 14 deletions

View File

@@ -141,14 +141,33 @@
</div>
<div v-if="activeTab === 'users'" class="users-panel">
<table class="admin-table">
<thead><tr><th>{{ t('admin.table.username') }}</th><th>{{ t('admin.table.homeDir') }}</th><th>{{ t('admin.table.status') }}</th></tr></thead>
<tbody>
<tr v-for="u in users" :key="u.username">
<td>{{ u.username }}</td><td>{{ u.homeDir }}</td><td>{{ u.status === 1 ? 'Active' : 'Disabled' }}</td>
</tr>
</tbody>
</table>
<!-- Admin Login -->
<div v-if="!adminAuth.isLoggedIn()" class="admin-login-form">
<h3>Admin Login</h3>
<div class="login-row">
<input class="login-input" v-model="loginUsername" placeholder="Username" @keyup.enter="handleLogin" />
<input class="login-input" v-model="loginPassword" type="password" placeholder="Password" @keyup.enter="handleLogin" />
<button class="login-btn" @click="handleLogin" :disabled="adminAuth.loading.value">{{ adminAuth.loading.value ? 'Logging in...' : 'Login' }}</button>
</div>
<div v-if="adminAuth.error.value" class="login-error">{{ adminAuth.error.value }}</div>
<div class="login-hint">Default: demo / demo123</div>
</div>
<!-- Users Table -->
<template v-if="adminAuth.isLoggedIn()">
<div class="users-header">
<span>Logged in as: {{ adminAuth.username.value }}</span>
<button class="logout-btn" @click="handleLogout">Logout</button>
</div>
<table class="admin-table">
<thead><tr><th>{{ t('admin.table.username') }}</th><th>{{ t('admin.table.homeDir') }}</th><th>{{ t('admin.table.status') }}</th></tr></thead>
<tbody>
<tr v-for="u in users" :key="u.username">
<td>{{ u.username }}</td><td>{{ u.homeDir }}</td><td>{{ u.status === 1 ? 'Active' : 'Disabled' }}</td>
</tr>
</tbody>
</table>
</template>
</div>
<div v-if="activeTab === 'shares'" class="shares-panel">
@@ -604,6 +623,23 @@ import { ref, onMounted, onUnmounted, watch, computed } from 'vue'
import { apiCall } from '../api'
import { loadSystemStats, systemStatsCache, SystemStats } from '../store'
import { getApiBase } from '@/api/config'
import { useAdminAuth } from '@/composables/useAdminAuth'
const adminAuth = useAdminAuth()
const loginUsername = ref('demo')
const loginPassword = ref('demo123')
async function handleLogin() {
const success = await adminAuth.login(loginUsername.value, loginPassword.value)
if (success) {
await loadUsers()
}
}
function handleLogout() {
adminAuth.logout()
users.value = []
}
const activeTab = ref('dashboard')
const stats = ref({ cpu: 0, memory: 0, disk: 0 })
@@ -1025,11 +1061,28 @@ async function loadDashboard() {
}
async function loadUsers() {
try { users.value = await apiCall('list_admin_users', {}) || [] } catch (e) { console.error(e) }
try {
const base = getApiBase()
const resp = await fetch(`${base}/api/v2/admin/users`, {
headers: adminAuth.getAuthHeader()
})
if (!resp.ok) throw new Error(`HTTP ${resp.status}`)
const data = await resp.json()
users.value = data.users || []
} catch (e: any) {
console.error('Failed to load users:', e)
}
}
async function loadShares() {
try { shares.value = await apiCall('list_shares', {}) || [] } catch (e) { console.error(e) }
try {
const base = getApiBase()
const resp = await fetch(`${base}/api/v2/admin/shares`)
if (!resp.ok) throw new Error(`HTTP ${resp.status}`)
shares.value = await resp.json()
} catch (e: any) {
console.error('Failed to load shares:', e)
}
}
async function loadSettings() {
@@ -1383,6 +1436,16 @@ onMounted(async () => {
.admin-table { width: 100%; border-collapse: collapse; }
.admin-table th, .admin-table td { padding: 10px; border: 1px solid var(--border-color); text-align: left; color: var(--text-primary); }
.admin-login-form { background: var(--hover-background); padding: 20px; border-radius: 8px; margin-bottom: 20px; }
.admin-login-form h3 { margin: 0 0 12px; font-size: 16px; }
.login-row { display: flex; gap: 8px; align-items: center; }
.login-input { flex: 1; padding: 8px 12px; border: 1px solid var(--border-color); border-radius: 4px; font-size: 14px; background: var(--card-background); color: var(--text-primary); }
.login-btn { padding: 8px 16px; background: var(--primary-color); color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; }
.login-btn:disabled { opacity: 0.6; cursor: default; }
.login-error { color: #ea4335; margin-top: 8px; font-size: 13px; }
.login-hint { color: var(--text-secondary); margin-top: 8px; font-size: 12px; }
.users-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; }
.logout-btn { padding: 6px 12px; background: #ea4335; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 13px; }
.settings-panel { max-width: 600px; }
.settings-group { margin-bottom: 24px; }
.settings-group h3 { margin: 0 0 4px; font-size: 16px; color: var(--text-primary); }