v01
This commit is contained in:
@@ -1,43 +1,159 @@
|
||||
<template>
|
||||
<div>
|
||||
<h2>Dashboard</h2>
|
||||
<button @click="logout">Logout</button>
|
||||
<h3>Users</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>ID</th><th>Login</th><th>Created</th><th>IP</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="user in users" :key="user.id">
|
||||
<td>{{ user.id }}</td>
|
||||
<td>{{ user.login }}</td>
|
||||
<td>{{ user.created }}</td>
|
||||
<td>{{ user.ip }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<AppLayout>
|
||||
<!-- Stats Grid -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
|
||||
<div class="card animate-fade-in">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-600">Total Users</p>
|
||||
<p class="text-3xl font-bold text-gray-900 mt-2">{{ stats.totalUsers }}</p>
|
||||
</div>
|
||||
<div class="w-12 h-12 bg-primary-100 rounded-lg flex items-center justify-center">
|
||||
<svg class="w-6 h-6 text-primary-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 flex items-center text-sm">
|
||||
<span class="text-green-600 font-medium">↑ 12%</span>
|
||||
<span class="text-gray-500 ml-2">from last month</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card animate-fade-in" style="animation-delay: 0.1s">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-600">Active Sessions</p>
|
||||
<p class="text-3xl font-bold text-gray-900 mt-2">{{ stats.activeSessions }}</p>
|
||||
</div>
|
||||
<div class="w-12 h-12 bg-green-100 rounded-lg flex items-center justify-center">
|
||||
<svg class="w-6 h-6 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 flex items-center text-sm">
|
||||
<span class="text-green-600 font-medium">↑ 5%</span>
|
||||
<span class="text-gray-500 ml-2">from last hour</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card animate-fade-in" style="animation-delay: 0.2s">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-600">System Health</p>
|
||||
<p class="text-3xl font-bold text-gray-900 mt-2">{{ stats.systemHealth }}%</p>
|
||||
</div>
|
||||
<div class="w-12 h-12 bg-blue-100 rounded-lg flex items-center justify-center">
|
||||
<svg class="w-6 h-6 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<div class="h-2 bg-gray-200 rounded-full">
|
||||
<div class="h-2 bg-blue-600 rounded-full" :style="{ width: `${stats.systemHealth}%` }"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card animate-fade-in" style="animation-delay: 0.3s">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-600">Uptime</p>
|
||||
<p class="text-3xl font-bold text-gray-900 mt-2">{{ stats.uptime }}</p>
|
||||
</div>
|
||||
<div class="w-12 h-12 bg-purple-100 rounded-lg flex items-center justify-center">
|
||||
<svg class="w-6 h-6 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 flex items-center text-sm">
|
||||
<div class="flex items-center text-green-600">
|
||||
<div class="w-2 h-2 bg-green-600 rounded-full mr-2"></div>
|
||||
Operational
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recent Activity -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<div class="card">
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-4">Recent Users</h3>
|
||||
<div class="space-y-3">
|
||||
<div v-for="user in recentUsers" :key="user.id" class="flex items-center justify-between p-3 hover:bg-gray-50 rounded-lg transition-colors">
|
||||
<div class="flex items-center space-x-3">
|
||||
<div class="w-10 h-10 bg-gradient-to-br from-primary-500 to-primary-700 rounded-full flex items-center justify-center text-white font-semibold">
|
||||
{{ user.login[0].toUpperCase() }}
|
||||
</div>
|
||||
<div>
|
||||
<p class="font-medium text-gray-900">{{ user.login }}</p>
|
||||
<p class="text-sm text-gray-500">{{ formatDate(user.created) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<span class="px-2 py-1 text-xs font-medium bg-green-100 text-green-700 rounded-full">New</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-4">System Status</h3>
|
||||
<div class="space-y-4">
|
||||
<div v-for="service in systemServices" :key="service.name" class="flex items-center justify-between">
|
||||
<div class="flex items-center space-x-3">
|
||||
<div :class="['w-2 h-2 rounded-full', service.status === 'healthy' ? 'bg-green-500' : 'bg-red-500']"></div>
|
||||
<span class="text-gray-700">{{ service.name }}</span>
|
||||
</div>
|
||||
<span class="text-sm text-gray-500">{{ service.latency }}ms</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AppLayout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import axios from 'axios'
|
||||
import AppLayout from '../components/Layout/AppLayout.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const users = ref([])
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const res = await axios.get('/api/admin/users')
|
||||
users.value = res.data
|
||||
} catch {
|
||||
router.push('/login')
|
||||
}
|
||||
const stats = ref({
|
||||
totalUsers: 0,
|
||||
activeSessions: 0,
|
||||
systemHealth: 98,
|
||||
uptime: '99.9%'
|
||||
})
|
||||
|
||||
async function logout() {
|
||||
await axios.post('/api/logout')
|
||||
router.push('/login')
|
||||
const recentUsers = ref([])
|
||||
const systemServices = ref([
|
||||
{ name: 'Database', status: 'healthy', latency: 12 },
|
||||
{ name: 'Redis Cache', status: 'healthy', latency: 3 },
|
||||
{ name: 'API Gateway', status: 'healthy', latency: 45 },
|
||||
{ name: 'File Storage', status: 'healthy', latency: 28 }
|
||||
])
|
||||
|
||||
onMounted(async () => {
|
||||
await loadData()
|
||||
})
|
||||
|
||||
async function loadData() {
|
||||
try {
|
||||
const res = await fetch('/api/admin/users')
|
||||
const users = await res.json()
|
||||
stats.value.totalUsers = users.length
|
||||
recentUsers.value = users.slice(-5).reverse()
|
||||
} catch (e) {
|
||||
console.error('Failed to load data', e)
|
||||
}
|
||||
}
|
||||
|
||||
function formatDate(dateStr: string) {
|
||||
return new Date(dateStr).toLocaleDateString('en-US', {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user