Fix iOS Safari auth loops and simplify admin dashboard
Build And Push Image / docker (push) Successful in 3m4s
Details
Build And Push Image / docker (push) Successful in 3m4s
Details
- Add session check throttling in useAuth to prevent iOS Safari authentication loops - Simplify admin dashboard by removing complex system metrics and stats - Remove system-metrics utility and streamline stats API endpoint - Update admin interface to focus on core user and role management
This commit is contained in:
parent
146b3c9400
commit
2843bcf4f5
|
|
@ -150,9 +150,24 @@ export const useAuth = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check authentication status
|
// Session check throttling to prevent iOS Safari loops
|
||||||
|
const lastSessionCheck = ref(0);
|
||||||
|
const SESSION_CHECK_THROTTLE = 5000; // 5 seconds minimum between checks
|
||||||
|
|
||||||
|
// Check authentication status with debouncing
|
||||||
const checkAuth = async () => {
|
const checkAuth = async () => {
|
||||||
|
const now = Date.now();
|
||||||
|
|
||||||
|
// Throttle session checks to prevent iOS Safari loops
|
||||||
|
if (now - lastSessionCheck.value < SESSION_CHECK_THROTTLE) {
|
||||||
|
console.log('🚫 Session check throttled, using cached result');
|
||||||
|
return !!user.value;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
console.log('🔄 Performing throttled session check...');
|
||||||
|
lastSessionCheck.value = now;
|
||||||
|
|
||||||
const response = await $fetch<{
|
const response = await $fetch<{
|
||||||
authenticated: boolean;
|
authenticated: boolean;
|
||||||
user: User | null;
|
user: User | null;
|
||||||
|
|
|
||||||
|
|
@ -1,483 +1,308 @@
|
||||||
<template>
|
<template>
|
||||||
<v-container>
|
<div>
|
||||||
<!-- Welcome Header -->
|
<v-container fluid>
|
||||||
<v-row class="mb-6">
|
<v-row>
|
||||||
<v-col>
|
<v-col cols="12">
|
||||||
<h1 class="text-h3 font-weight-bold" style="color: #a31515;">
|
<h1 class="text-h4 font-weight-bold mb-4">
|
||||||
Welcome Back, {{ firstName }}!
|
<v-icon left>mdi-shield-crown</v-icon>
|
||||||
</h1>
|
Administration
|
||||||
<p class="text-h6 text-medium-emphasis">
|
</h1>
|
||||||
MonacoUSA Administration Portal
|
<p class="text-body-1 mb-6">
|
||||||
</p>
|
Manage users and portal settings for the MonacoUSA Portal.
|
||||||
<v-chip color="error" variant="elevated" class="mt-2">
|
</p>
|
||||||
<v-icon start>mdi-shield-crown</v-icon>
|
</v-col>
|
||||||
Administrator
|
</v-row>
|
||||||
</v-chip>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
|
|
||||||
<!-- System Status -->
|
<!-- Portal Status -->
|
||||||
<v-row class="mb-6">
|
<v-row class="mb-6">
|
||||||
<v-col cols="12">
|
<v-col cols="12" md="6">
|
||||||
<v-card class="pa-4" elevation="2">
|
<v-card elevation="2">
|
||||||
<h3 class="mb-3">
|
<v-card-text>
|
||||||
<v-icon class="mr-2" color="success">mdi-server</v-icon>
|
<div class="d-flex justify-space-between align-center">
|
||||||
System Status
|
<div>
|
||||||
</h3>
|
<p class="text-caption text-medium-emphasis mb-1">Portal Status</p>
|
||||||
<v-row>
|
<p class="text-h5 font-weight-bold text-success">Online</p>
|
||||||
<v-col cols="6" md="3">
|
</div>
|
||||||
<v-chip color="success" variant="elevated">
|
<v-icon color="success" size="40">mdi-check-circle</v-icon>
|
||||||
<v-icon start>mdi-check-circle</v-icon>
|
</div>
|
||||||
System Healthy
|
</v-card-text>
|
||||||
</v-chip>
|
</v-card>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="6" md="3">
|
|
||||||
<v-chip color="info" variant="elevated">
|
|
||||||
<v-icon start>mdi-account-multiple</v-icon>
|
|
||||||
{{ systemStats.totalUsers }} Users
|
|
||||||
</v-chip>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="6" md="3">
|
|
||||||
<v-chip color="warning" variant="elevated">
|
|
||||||
<v-icon start>mdi-database</v-icon>
|
|
||||||
{{ systemStats.diskUsage }} Disk
|
|
||||||
</v-chip>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="6" md="3">
|
|
||||||
<v-chip color="primary" variant="elevated">
|
|
||||||
<v-icon start>mdi-memory</v-icon>
|
|
||||||
{{ systemStats.memoryUsage }} Memory
|
|
||||||
</v-chip>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-card>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
|
|
||||||
<!-- Admin Tools -->
|
<v-col cols="12" md="6">
|
||||||
<v-row class="mb-6">
|
<v-card elevation="2">
|
||||||
<v-col cols="12" md="4">
|
<v-card-text>
|
||||||
<v-card class="pa-4 text-center" elevation="2" hover>
|
<div class="d-flex justify-space-between align-center">
|
||||||
<v-icon size="48" color="error" class="mb-2">mdi-account-cog</v-icon>
|
<div>
|
||||||
<h3 class="mb-2">User Management</h3>
|
<p class="text-caption text-medium-emphasis mb-1">Total Users</p>
|
||||||
<p class="text-body-2 mb-4">Add, edit, and manage user accounts</p>
|
<p class="text-h5 font-weight-bold">{{ userCount }}</p>
|
||||||
<v-btn
|
</div>
|
||||||
color="error"
|
<v-icon color="primary" size="40">mdi-account-multiple</v-icon>
|
||||||
variant="outlined"
|
</div>
|
||||||
style="border-color: #f44336; color: #f44336;"
|
</v-card-text>
|
||||||
@click="navigateToUserManagement"
|
</v-card>
|
||||||
>
|
</v-col>
|
||||||
Manage Users
|
</v-row>
|
||||||
</v-btn>
|
|
||||||
</v-card>
|
|
||||||
</v-col>
|
|
||||||
|
|
||||||
<v-col cols="12" md="4">
|
<!-- User Management -->
|
||||||
<v-card class="pa-4 text-center" elevation="2" hover>
|
<v-row class="mb-6">
|
||||||
<v-icon size="48" color="error" class="mb-2">mdi-file-document-outline</v-icon>
|
<v-col cols="12">
|
||||||
<h3 class="mb-2">Audit Logs</h3>
|
<v-card elevation="2">
|
||||||
<p class="text-body-2 mb-4">View system and user activity logs</p>
|
<v-card-title>
|
||||||
<v-btn
|
<v-icon left>mdi-account-group</v-icon>
|
||||||
color="error"
|
User Management
|
||||||
variant="outlined"
|
</v-card-title>
|
||||||
style="border-color: #f44336; color: #f44336;"
|
<v-card-text>
|
||||||
@click="navigateToAuditLogs"
|
<p class="mb-4">Manage user accounts, roles, and permissions for the MonacoUSA Portal.</p>
|
||||||
>
|
|
||||||
View Logs
|
|
||||||
</v-btn>
|
|
||||||
</v-card>
|
|
||||||
</v-col>
|
|
||||||
|
|
||||||
<v-col cols="12" md="4">
|
<v-row>
|
||||||
<v-card class="pa-4 text-center" elevation="2" hover>
|
<v-col cols="12" md="4">
|
||||||
<v-icon size="48" color="error" class="mb-2">mdi-cog</v-icon>
|
<v-btn
|
||||||
<h3 class="mb-2">System Config</h3>
|
color="primary"
|
||||||
<p class="text-body-2 mb-4">Configure system settings</p>
|
block
|
||||||
<v-btn
|
size="large"
|
||||||
color="error"
|
@click="manageUsers"
|
||||||
variant="outlined"
|
>
|
||||||
style="border-color: #f44336; color: #f44336;"
|
<v-icon start>mdi-account-cog</v-icon>
|
||||||
@click="navigateToSystemConfig"
|
Manage Users
|
||||||
>
|
</v-btn>
|
||||||
Settings
|
</v-col>
|
||||||
</v-btn>
|
|
||||||
</v-card>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
|
|
||||||
<!-- System Metrics -->
|
<v-col cols="12" md="4">
|
||||||
<v-row class="mb-6">
|
<v-btn
|
||||||
<v-col cols="12">
|
color="secondary"
|
||||||
<v-card elevation="2">
|
variant="outlined"
|
||||||
<v-card-title class="pa-4" style="background-color: #f5f5f5;">
|
block
|
||||||
<v-icon class="mr-2" color="primary">mdi-chart-line</v-icon>
|
size="large"
|
||||||
System Metrics
|
@click="viewAuditLogs"
|
||||||
</v-card-title>
|
>
|
||||||
<v-card-text class="pa-4">
|
<v-icon start>mdi-file-document-outline</v-icon>
|
||||||
<v-row>
|
View Audit Logs
|
||||||
<v-col cols="6" md="3" class="text-center">
|
</v-btn>
|
||||||
<div class="text-h4 font-weight-bold" style="color: #a31515;">{{ systemStats.totalUsers }}</div>
|
</v-col>
|
||||||
<div class="text-body-2">Total Users</div>
|
|
||||||
</v-col>
|
<v-col cols="12" md="4">
|
||||||
<v-col cols="6" md="3" class="text-center">
|
<v-btn
|
||||||
<div class="text-h4 font-weight-bold" style="color: #a31515;">{{ systemStats.activeUsers }}</div>
|
color="secondary"
|
||||||
<div class="text-body-2">Active Users</div>
|
variant="outlined"
|
||||||
</v-col>
|
block
|
||||||
<v-col cols="6" md="3" class="text-center">
|
size="large"
|
||||||
<div class="text-h4 font-weight-bold" style="color: #a31515;">{{ systemStats.totalSessions }}</div>
|
@click="portalSettings"
|
||||||
<div class="text-body-2">Active Sessions</div>
|
>
|
||||||
</v-col>
|
<v-icon start>mdi-cog</v-icon>
|
||||||
<v-col cols="6" md="3" class="text-center">
|
Portal Settings
|
||||||
<div class="text-h4 font-weight-bold" style="color: #a31515;">{{ systemStats.uptime }}</div>
|
</v-btn>
|
||||||
<div class="text-body-2">System Uptime</div>
|
</v-col>
|
||||||
</v-col>
|
</v-row>
|
||||||
</v-row>
|
</v-card-text>
|
||||||
</v-card-text>
|
</v-card>
|
||||||
</v-card>
|
</v-col>
|
||||||
</v-col>
|
</v-row>
|
||||||
</v-row>
|
|
||||||
|
<!-- Quick Actions -->
|
||||||
|
<v-row class="mb-6">
|
||||||
|
<v-col cols="12">
|
||||||
|
<v-card elevation="2">
|
||||||
|
<v-card-title>
|
||||||
|
<v-icon left>mdi-lightning-bolt</v-icon>
|
||||||
|
Quick Actions
|
||||||
|
</v-card-title>
|
||||||
|
<v-card-text>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="6" md="3">
|
||||||
|
<v-btn
|
||||||
|
color="success"
|
||||||
|
variant="outlined"
|
||||||
|
block
|
||||||
|
@click="createUser"
|
||||||
|
>
|
||||||
|
<v-icon start>mdi-account-plus</v-icon>
|
||||||
|
Add User
|
||||||
|
</v-btn>
|
||||||
|
</v-col>
|
||||||
|
|
||||||
|
<v-col cols="6" md="3">
|
||||||
|
<v-btn
|
||||||
|
color="info"
|
||||||
|
variant="outlined"
|
||||||
|
block
|
||||||
|
@click="generateReport"
|
||||||
|
>
|
||||||
|
<v-icon start>mdi-file-chart</v-icon>
|
||||||
|
User Report
|
||||||
|
</v-btn>
|
||||||
|
</v-col>
|
||||||
|
|
||||||
|
<v-col cols="6" md="3">
|
||||||
|
<v-btn
|
||||||
|
color="warning"
|
||||||
|
variant="outlined"
|
||||||
|
block
|
||||||
|
@click="manageRoles"
|
||||||
|
>
|
||||||
|
<v-icon start>mdi-shield-account</v-icon>
|
||||||
|
Manage Roles
|
||||||
|
</v-btn>
|
||||||
|
</v-col>
|
||||||
|
|
||||||
|
<v-col cols="6" md="3">
|
||||||
|
<v-btn
|
||||||
|
color="error"
|
||||||
|
variant="outlined"
|
||||||
|
block
|
||||||
|
@click="systemMaintenance"
|
||||||
|
>
|
||||||
|
<v-icon start>mdi-wrench</v-icon>
|
||||||
|
Maintenance
|
||||||
|
</v-btn>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<!-- Recent Activity -->
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12">
|
||||||
|
<v-card elevation="2">
|
||||||
|
<v-card-title>
|
||||||
|
<v-icon left>mdi-history</v-icon>
|
||||||
|
Recent Admin Activity
|
||||||
|
</v-card-title>
|
||||||
|
<v-card-text>
|
||||||
|
<v-list>
|
||||||
|
<v-list-item
|
||||||
|
v-for="activity in recentActivity"
|
||||||
|
:key="activity.id"
|
||||||
|
class="px-0"
|
||||||
|
>
|
||||||
|
<template #prepend>
|
||||||
|
<v-icon :color="activity.color" class="mr-3">{{ activity.icon }}</v-icon>
|
||||||
|
</template>
|
||||||
|
|
||||||
<!-- Recent Admin Activity -->
|
|
||||||
<v-row class="mb-6">
|
|
||||||
<v-col cols="12">
|
|
||||||
<v-card elevation="2">
|
|
||||||
<v-card-title class="pa-4" style="background-color: #f5f5f5;">
|
|
||||||
<v-icon class="mr-2" color="primary">mdi-history</v-icon>
|
|
||||||
Recent Admin Activity
|
|
||||||
</v-card-title>
|
|
||||||
<v-card-text class="pa-4">
|
|
||||||
<v-list>
|
|
||||||
<v-list-item v-for="activity in recentActivity" :key="activity.id">
|
|
||||||
<v-list-item-content>
|
|
||||||
<v-list-item-title>{{ activity.title }}</v-list-item-title>
|
<v-list-item-title>{{ activity.title }}</v-list-item-title>
|
||||||
<v-list-item-subtitle>
|
<v-list-item-subtitle>{{ activity.description }}</v-list-item-subtitle>
|
||||||
{{ activity.description }} - {{ activity.timestamp }}
|
|
||||||
</v-list-item-subtitle>
|
|
||||||
</v-list-item-content>
|
|
||||||
<v-list-item-action>
|
|
||||||
<v-chip :color="activity.type" size="small">{{ activity.status }}</v-chip>
|
|
||||||
</v-list-item-action>
|
|
||||||
</v-list-item>
|
|
||||||
</v-list>
|
|
||||||
</v-card-text>
|
|
||||||
</v-card>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
|
|
||||||
<!-- Security & Monitoring -->
|
<template #append>
|
||||||
<v-row class="mb-6">
|
<small class="text-medium-emphasis">{{ activity.time }}</small>
|
||||||
<v-col cols="12" md="6">
|
</template>
|
||||||
<v-card elevation="2">
|
</v-list-item>
|
||||||
<v-card-title class="pa-4" style="background-color: #f5f5f5;">
|
</v-list>
|
||||||
<v-icon class="mr-2" color="error">mdi-security</v-icon>
|
</v-card-text>
|
||||||
Security Alerts
|
</v-card>
|
||||||
</v-card-title>
|
</v-col>
|
||||||
<v-card-text class="pa-4">
|
</v-row>
|
||||||
<v-list>
|
</v-container>
|
||||||
<v-list-item v-for="alert in securityAlerts" :key="alert.id">
|
</div>
|
||||||
<v-list-item-content>
|
|
||||||
<v-list-item-title>{{ alert.title }}</v-list-item-title>
|
|
||||||
<v-list-item-subtitle>{{ alert.description }}</v-list-item-subtitle>
|
|
||||||
</v-list-item-content>
|
|
||||||
<v-list-item-action>
|
|
||||||
<v-chip :color="alert.severity" size="small">{{ alert.level }}</v-chip>
|
|
||||||
</v-list-item-action>
|
|
||||||
</v-list-item>
|
|
||||||
</v-list>
|
|
||||||
</v-card-text>
|
|
||||||
</v-card>
|
|
||||||
</v-col>
|
|
||||||
|
|
||||||
<v-col cols="12" md="6">
|
|
||||||
<v-card elevation="2">
|
|
||||||
<v-card-title class="pa-4" style="background-color: #f5f5f5;">
|
|
||||||
<v-icon class="mr-2" color="primary">mdi-monitor-dashboard</v-icon>
|
|
||||||
System Health
|
|
||||||
</v-card-title>
|
|
||||||
<v-card-text class="pa-4">
|
|
||||||
<div class="mb-3">
|
|
||||||
<div class="d-flex justify-space-between mb-1">
|
|
||||||
<span>CPU Usage</span>
|
|
||||||
<span>{{ systemHealth.cpu }}%</span>
|
|
||||||
</div>
|
|
||||||
<v-progress-linear
|
|
||||||
:model-value="systemHealth.cpu"
|
|
||||||
color="primary"
|
|
||||||
height="8"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<div class="d-flex justify-space-between mb-1">
|
|
||||||
<span>Memory Usage</span>
|
|
||||||
<span>{{ systemHealth.memory }}%</span>
|
|
||||||
</div>
|
|
||||||
<v-progress-linear
|
|
||||||
:model-value="systemHealth.memory"
|
|
||||||
color="warning"
|
|
||||||
height="8"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<div class="d-flex justify-space-between mb-1">
|
|
||||||
<span>Disk Usage</span>
|
|
||||||
<span>{{ systemHealth.disk }}%</span>
|
|
||||||
</div>
|
|
||||||
<v-progress-linear
|
|
||||||
:model-value="systemHealth.disk"
|
|
||||||
color="success"
|
|
||||||
height="8"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</v-card-text>
|
|
||||||
</v-card>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
|
|
||||||
<!-- Quick Admin Actions -->
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12">
|
|
||||||
<v-card elevation="2">
|
|
||||||
<v-card-title class="pa-4" style="background-color: #f5f5f5;">
|
|
||||||
<v-icon class="mr-2" color="primary">mdi-lightning-bolt</v-icon>
|
|
||||||
Quick Admin Actions
|
|
||||||
</v-card-title>
|
|
||||||
<v-card-text class="pa-4">
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" md="3">
|
|
||||||
<v-btn
|
|
||||||
color="error"
|
|
||||||
variant="outlined"
|
|
||||||
block
|
|
||||||
style="border-color: #f44336; color: #f44336;"
|
|
||||||
@click="createNewUser"
|
|
||||||
>
|
|
||||||
<v-icon start>mdi-account-plus</v-icon>
|
|
||||||
Create User
|
|
||||||
</v-btn>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" md="3">
|
|
||||||
<v-btn
|
|
||||||
color="error"
|
|
||||||
variant="outlined"
|
|
||||||
block
|
|
||||||
style="border-color: #f44336; color: #f44336;"
|
|
||||||
@click="generateSystemReport"
|
|
||||||
>
|
|
||||||
<v-icon start>mdi-file-chart</v-icon>
|
|
||||||
System Report
|
|
||||||
</v-btn>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" md="3">
|
|
||||||
<v-btn
|
|
||||||
color="error"
|
|
||||||
variant="outlined"
|
|
||||||
block
|
|
||||||
style="border-color: #f44336; color: #f44336;"
|
|
||||||
@click="managePermissions"
|
|
||||||
>
|
|
||||||
<v-icon start>mdi-shield-key</v-icon>
|
|
||||||
Permissions
|
|
||||||
</v-btn>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" md="3">
|
|
||||||
<v-btn
|
|
||||||
color="error"
|
|
||||||
variant="outlined"
|
|
||||||
block
|
|
||||||
style="border-color: #f44336; color: #f44336;"
|
|
||||||
@click="systemMaintenance"
|
|
||||||
>
|
|
||||||
<v-icon start>mdi-wrench</v-icon>
|
|
||||||
Maintenance
|
|
||||||
</v-btn>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-card-text>
|
|
||||||
</v-card>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-container>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
layout: 'dashboard',
|
layout: 'dashboard',
|
||||||
middleware: 'auth'
|
middleware: 'auth-admin'
|
||||||
});
|
});
|
||||||
|
|
||||||
const { firstName, isAdmin } = useAuth();
|
const { firstName } = useAuth();
|
||||||
|
|
||||||
// Check admin access on mount
|
|
||||||
onMounted(async () => {
|
|
||||||
if (!isAdmin.value) {
|
|
||||||
throw createError({
|
|
||||||
statusCode: 403,
|
|
||||||
statusMessage: 'Access denied. Administrator privileges required.'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load admin dashboard data
|
|
||||||
await loadAdminStats();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Reactive data
|
// Reactive data
|
||||||
const systemStats = ref({
|
const userCount = ref(0);
|
||||||
totalUsers: 0,
|
const loading = ref(false);
|
||||||
activeUsers: 0,
|
|
||||||
totalSessions: 0,
|
|
||||||
diskUsage: '0%',
|
|
||||||
memoryUsage: '0%',
|
|
||||||
uptime: '0d'
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
const systemHealth = ref({
|
|
||||||
cpu: 45,
|
|
||||||
memory: 62,
|
|
||||||
disk: 38
|
|
||||||
});
|
|
||||||
|
|
||||||
const recentActivity = ref([
|
const recentActivity = ref([
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
title: 'User Created',
|
title: 'User Account Created',
|
||||||
description: 'New user account created for john.doe@example.com',
|
description: 'New user account created for john.doe@monacousa.org',
|
||||||
timestamp: '2 hours ago',
|
time: '2 hours ago',
|
||||||
type: 'success',
|
icon: 'mdi-account-plus',
|
||||||
status: 'Completed'
|
color: 'success'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
title: 'System Backup',
|
title: 'Role Updated',
|
||||||
description: 'Automated system backup completed successfully',
|
description: 'User role updated from User to Board Member',
|
||||||
timestamp: '6 hours ago',
|
time: '4 hours ago',
|
||||||
type: 'info',
|
icon: 'mdi-shield-account',
|
||||||
status: 'Completed'
|
color: 'warning'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 3,
|
id: 3,
|
||||||
title: 'Permission Update',
|
title: 'System Backup',
|
||||||
description: 'User permissions updated for board member',
|
description: 'Automated system backup completed successfully',
|
||||||
timestamp: '1 day ago',
|
time: '1 day ago',
|
||||||
type: 'warning',
|
icon: 'mdi-backup-restore',
|
||||||
status: 'Completed'
|
color: 'info'
|
||||||
}
|
|
||||||
]);
|
|
||||||
|
|
||||||
const securityAlerts = ref([
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
title: 'Failed Login Attempts',
|
|
||||||
description: '3 failed login attempts from IP 192.168.1.100',
|
|
||||||
severity: 'warning',
|
|
||||||
level: 'Medium'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 4,
|
||||||
title: 'System Update Available',
|
title: 'Password Reset',
|
||||||
description: 'Security update available for Keycloak',
|
description: 'Password reset requested for jane.smith@monacousa.org',
|
||||||
severity: 'info',
|
time: '2 days ago',
|
||||||
level: 'Low'
|
icon: 'mdi-key-change',
|
||||||
|
color: 'primary'
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Load admin statistics
|
// Load simplified admin stats (without system metrics)
|
||||||
const loadAdminStats = async () => {
|
const loadStats = async () => {
|
||||||
try {
|
try {
|
||||||
const stats = await $fetch<{
|
loading.value = true;
|
||||||
totalUsers: number;
|
|
||||||
activeUsers: number;
|
|
||||||
totalSessions: number;
|
|
||||||
diskUsage: string;
|
|
||||||
memoryUsage: string;
|
|
||||||
systemMetrics: {
|
|
||||||
cpu: number;
|
|
||||||
memory: number;
|
|
||||||
disk: number;
|
|
||||||
uptime: string;
|
|
||||||
processes: number;
|
|
||||||
cpuCores: number;
|
|
||||||
cpuModel: string;
|
|
||||||
memoryTotal: string;
|
|
||||||
memoryUsed: string;
|
|
||||||
diskTotal: string;
|
|
||||||
diskUsed: string;
|
|
||||||
};
|
|
||||||
}>('/api/admin/stats');
|
|
||||||
|
|
||||||
// Update system stats with real data
|
// Simple user count without complex system metrics
|
||||||
systemStats.value = {
|
const response = await $fetch<{ userCount: number }>('/api/admin/stats');
|
||||||
totalUsers: stats.totalUsers || 0,
|
userCount.value = response.userCount || 0;
|
||||||
activeUsers: stats.activeUsers || 0,
|
|
||||||
totalSessions: stats.totalSessions || 0,
|
|
||||||
diskUsage: stats.diskUsage || '0%',
|
|
||||||
memoryUsage: stats.memoryUsage || '0%',
|
|
||||||
uptime: stats.systemMetrics?.uptime || '0m'
|
|
||||||
};
|
|
||||||
|
|
||||||
// Update system health with real metrics
|
|
||||||
systemHealth.value = {
|
|
||||||
cpu: stats.systemMetrics?.cpu || 0,
|
|
||||||
memory: stats.systemMetrics?.memory || 0,
|
|
||||||
disk: stats.systemMetrics?.disk || 0
|
|
||||||
};
|
|
||||||
|
|
||||||
console.log('✅ Admin stats loaded with real system metrics:', {
|
|
||||||
cpu: `${systemHealth.value.cpu}%`,
|
|
||||||
memory: `${systemHealth.value.memory}%`,
|
|
||||||
disk: `${systemHealth.value.disk}%`,
|
|
||||||
uptime: systemStats.value.uptime
|
|
||||||
});
|
|
||||||
|
|
||||||
|
console.log('✅ Admin stats loaded:', { userCount: userCount.value });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load admin stats:', error);
|
console.error('❌ Failed to load admin stats:', error);
|
||||||
// Use fallback data on error
|
// Use fallback data
|
||||||
systemStats.value = {
|
userCount.value = 25;
|
||||||
totalUsers: 156,
|
} finally {
|
||||||
activeUsers: 45,
|
loading.value = false;
|
||||||
totalSessions: 67,
|
|
||||||
diskUsage: '45%',
|
|
||||||
memoryUsage: '62%',
|
|
||||||
uptime: '5d 12h'
|
|
||||||
};
|
|
||||||
|
|
||||||
systemHealth.value = {
|
|
||||||
cpu: 45,
|
|
||||||
memory: 62,
|
|
||||||
disk: 38
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Navigation methods (placeholder implementations)
|
// Action methods (placeholders for now)
|
||||||
const navigateToUserManagement = () => {
|
const manageUsers = () => {
|
||||||
console.log('Navigate to user management');
|
console.log('Navigate to user management');
|
||||||
|
// TODO: Implement user management navigation
|
||||||
};
|
};
|
||||||
|
|
||||||
const navigateToAuditLogs = () => {
|
const viewAuditLogs = () => {
|
||||||
console.log('Navigate to audit logs');
|
console.log('Navigate to audit logs');
|
||||||
|
// TODO: Implement audit logs navigation
|
||||||
};
|
};
|
||||||
|
|
||||||
const navigateToSystemConfig = () => {
|
const portalSettings = () => {
|
||||||
console.log('Navigate to system config');
|
console.log('Navigate to portal settings');
|
||||||
|
// TODO: Implement portal settings navigation
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const createUser = () => {
|
||||||
const createNewUser = () => {
|
|
||||||
console.log('Create new user');
|
console.log('Create new user');
|
||||||
|
// TODO: Implement create user dialog/form
|
||||||
};
|
};
|
||||||
|
|
||||||
const generateSystemReport = () => {
|
const generateReport = () => {
|
||||||
console.log('Generate system report');
|
console.log('Generate user report');
|
||||||
|
// TODO: Implement report generation
|
||||||
};
|
};
|
||||||
|
|
||||||
const managePermissions = () => {
|
const manageRoles = () => {
|
||||||
console.log('Manage permissions');
|
console.log('Manage user roles');
|
||||||
|
// TODO: Implement role management
|
||||||
};
|
};
|
||||||
|
|
||||||
const systemMaintenance = () => {
|
const systemMaintenance = () => {
|
||||||
console.log('System maintenance');
|
console.log('System maintenance');
|
||||||
|
// TODO: Implement maintenance mode
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Load stats on component mount
|
||||||
|
onMounted(async () => {
|
||||||
|
await loadStats();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
@ -486,32 +311,21 @@ const systemMaintenance = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
.v-card:hover {
|
.v-card:hover {
|
||||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
|
transform: translateY(-2px);
|
||||||
transition: box-shadow 0.2s ease;
|
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15) !important;
|
||||||
|
transition: all 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.v-btn {
|
.v-btn {
|
||||||
text-transform: none !important;
|
text-transform: none !important;
|
||||||
}
|
|
||||||
|
|
||||||
.v-icon {
|
|
||||||
color: #a31515 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
color: #333;
|
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-body-2 {
|
.v-list-item {
|
||||||
color: #666;
|
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
.v-chip {
|
.v-list-item:last-child {
|
||||||
font-weight: 600;
|
border-bottom: none;
|
||||||
}
|
|
||||||
|
|
||||||
.v-progress-linear {
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -131,16 +131,16 @@ const { user, login, loading: authLoading, error: authError, checkAuth } = useAu
|
||||||
// Import mobile utilities for enhanced debugging
|
// Import mobile utilities for enhanced debugging
|
||||||
const { isMobileDevice, debugMobileLogin, runMobileDiagnostics } = await import('~/utils/mobile-utils');
|
const { isMobileDevice, debugMobileLogin, runMobileDiagnostics } = await import('~/utils/mobile-utils');
|
||||||
|
|
||||||
// Check if user is already authenticated - with mobile-specific handling
|
// Check if user is already authenticated - prevent iOS Safari loops
|
||||||
if (user.value) {
|
if (user.value) {
|
||||||
const redirectUrl = '/dashboard';
|
const redirectUrl = '/dashboard';
|
||||||
|
|
||||||
|
// Always use window.location for iOS Safari to prevent loops
|
||||||
if (isMobileDevice()) {
|
if (isMobileDevice()) {
|
||||||
console.log('📱 Mobile browser detected, using delayed redirect');
|
console.log('📱 Mobile browser detected, using window.location redirect');
|
||||||
debugMobileLogin('Already authenticated redirect');
|
debugMobileLogin('Already authenticated redirect');
|
||||||
setTimeout(() => {
|
// Use window.location.replace to avoid back button issues
|
||||||
window.location.href = redirectUrl;
|
window.location.replace(redirectUrl);
|
||||||
}, 100);
|
|
||||||
} else {
|
} else {
|
||||||
await navigateTo(redirectUrl);
|
await navigateTo(redirectUrl);
|
||||||
}
|
}
|
||||||
|
|
@ -259,7 +259,6 @@ onMounted(() => {
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-card:hover {
|
.login-card:hover {
|
||||||
transform: translateY(-4px);
|
|
||||||
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.4) !important;
|
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.4) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
export default defineEventHandler(async (event) => {
|
export default defineEventHandler(async (event) => {
|
||||||
console.log('📊 Admin stats requested at:', new Date().toISOString());
|
console.log('📊 Simple admin stats requested at:', new Date().toISOString());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Check if user is admin (middleware should handle this, but double-check)
|
// Check if user is admin
|
||||||
const sessionManager = createSessionManager();
|
const sessionManager = createSessionManager();
|
||||||
const cookieHeader = getHeader(event, 'cookie');
|
const cookieHeader = getHeader(event, 'cookie');
|
||||||
const session = sessionManager.getSession(cookieHeader);
|
const session = sessionManager.getSession(cookieHeader);
|
||||||
|
|
@ -17,78 +17,17 @@ export default defineEventHandler(async (event) => {
|
||||||
|
|
||||||
console.log('✅ Admin access verified for user:', session.user.email);
|
console.log('✅ Admin access verified for user:', session.user.email);
|
||||||
|
|
||||||
// Get real system metrics
|
// Return simple user-focused stats without system metrics
|
||||||
const { getSystemMetrics, getSystemHealthStatus, formatBytes } = await import('~/server/utils/system-metrics');
|
|
||||||
const systemMetrics = await getSystemMetrics();
|
|
||||||
const systemHealth = getSystemHealthStatus(systemMetrics);
|
|
||||||
|
|
||||||
// Build comprehensive stats with real data
|
|
||||||
const stats = {
|
const stats = {
|
||||||
// Real system metrics
|
// Simple user count (mock data for now - would come from Keycloak API)
|
||||||
totalUsers: 156, // TODO: Get from Keycloak API
|
userCount: 25,
|
||||||
activeUsers: 45, // TODO: Get from session store
|
|
||||||
totalSessions: 67, // TODO: Get from session store
|
|
||||||
systemHealth,
|
|
||||||
lastBackup: new Date().toISOString(),
|
|
||||||
diskUsage: `${systemMetrics.disk.usagePercent}%`,
|
|
||||||
memoryUsage: `${systemMetrics.memory.usagePercent}%`,
|
|
||||||
|
|
||||||
// Enhanced system metrics with real data
|
// Basic portal health without system metrics
|
||||||
systemMetrics: {
|
portalStatus: 'online',
|
||||||
cpu: systemMetrics.cpu.usage,
|
lastUpdate: new Date().toISOString()
|
||||||
memory: systemMetrics.memory.usagePercent,
|
|
||||||
disk: systemMetrics.disk.usagePercent,
|
|
||||||
uptime: systemMetrics.uptime,
|
|
||||||
processes: systemMetrics.processes.total,
|
|
||||||
cpuCores: systemMetrics.cpu.cores,
|
|
||||||
cpuModel: systemMetrics.cpu.model,
|
|
||||||
memoryTotal: formatBytes(systemMetrics.memory.total),
|
|
||||||
memoryUsed: formatBytes(systemMetrics.memory.used),
|
|
||||||
diskTotal: formatBytes(systemMetrics.disk.total),
|
|
||||||
diskUsed: formatBytes(systemMetrics.disk.used)
|
|
||||||
},
|
|
||||||
|
|
||||||
// Mock data for features not yet implemented
|
|
||||||
recentActivity: [
|
|
||||||
{
|
|
||||||
action: 'User login',
|
|
||||||
user: 'john@example.com',
|
|
||||||
timestamp: new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString(),
|
|
||||||
type: 'info'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
action: 'Password reset',
|
|
||||||
user: 'jane@example.com',
|
|
||||||
timestamp: new Date(Date.now() - 4 * 60 * 60 * 1000).toISOString(),
|
|
||||||
type: 'warning'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
action: 'User created',
|
|
||||||
user: 'admin@monacousa.org',
|
|
||||||
timestamp: new Date(Date.now() - 6 * 60 * 60 * 1000).toISOString(),
|
|
||||||
type: 'success'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
securityAlerts: [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
title: 'Failed Login Attempts',
|
|
||||||
description: '3 failed login attempts detected',
|
|
||||||
severity: 'medium',
|
|
||||||
timestamp: new Date(Date.now() - 1 * 60 * 60 * 1000).toISOString()
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
title: 'System Update Available',
|
|
||||||
description: 'Security update available for Keycloak',
|
|
||||||
severity: 'low',
|
|
||||||
timestamp: new Date(Date.now() - 12 * 60 * 60 * 1000).toISOString()
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log('✅ Admin stats retrieved successfully with real system metrics');
|
console.log('✅ Simple admin stats retrieved successfully');
|
||||||
return stats;
|
return stats;
|
||||||
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
|
@ -100,7 +39,7 @@ export default defineEventHandler(async (event) => {
|
||||||
|
|
||||||
throw createError({
|
throw createError({
|
||||||
statusCode: 500,
|
statusCode: 500,
|
||||||
statusMessage: 'Failed to retrieve system statistics'
|
statusMessage: 'Failed to retrieve admin statistics'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,190 +0,0 @@
|
||||||
import si from 'systeminformation';
|
|
||||||
|
|
||||||
interface SystemMetrics {
|
|
||||||
cpu: {
|
|
||||||
usage: number;
|
|
||||||
cores: number;
|
|
||||||
model: string;
|
|
||||||
};
|
|
||||||
memory: {
|
|
||||||
total: number;
|
|
||||||
used: number;
|
|
||||||
free: number;
|
|
||||||
usagePercent: number;
|
|
||||||
};
|
|
||||||
disk: {
|
|
||||||
total: number;
|
|
||||||
used: number;
|
|
||||||
free: number;
|
|
||||||
usagePercent: number;
|
|
||||||
};
|
|
||||||
uptime: string;
|
|
||||||
processes: {
|
|
||||||
total: number;
|
|
||||||
running: number;
|
|
||||||
sleeping: number;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cache system metrics to avoid excessive system calls
|
|
||||||
let metricsCache: SystemMetrics | null = null;
|
|
||||||
let lastCacheTime = 0;
|
|
||||||
const CACHE_DURATION = 30000; // 30 seconds
|
|
||||||
|
|
||||||
export async function getSystemMetrics(): Promise<SystemMetrics> {
|
|
||||||
const now = Date.now();
|
|
||||||
|
|
||||||
// Return cached data if still valid
|
|
||||||
if (metricsCache && (now - lastCacheTime) < CACHE_DURATION) {
|
|
||||||
console.log('📊 Returning cached system metrics');
|
|
||||||
return metricsCache;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
console.log('🔄 Fetching fresh system metrics...');
|
|
||||||
|
|
||||||
// Get all metrics in parallel with individual error handling
|
|
||||||
const results = await Promise.allSettled([
|
|
||||||
si.cpu(),
|
|
||||||
si.mem(),
|
|
||||||
si.fsSize(),
|
|
||||||
si.currentLoad(),
|
|
||||||
si.processes(),
|
|
||||||
si.time()
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Extract data with fallbacks for failed promises
|
|
||||||
const cpuData = results[0].status === 'fulfilled' ? results[0].value : { cores: 0, brand: 'Unknown' };
|
|
||||||
const memData = results[1].status === 'fulfilled' ? results[1].value : { total: 0, used: 0, free: 0 };
|
|
||||||
const fsData = results[2].status === 'fulfilled' ? results[2].value : [];
|
|
||||||
const currentLoad = results[3].status === 'fulfilled' ? results[3].value : { currentLoad: 0 };
|
|
||||||
const processData = results[4].status === 'fulfilled' ? results[4].value : { all: 0, running: 0, sleeping: 0 };
|
|
||||||
const uptimeData = results[5].status === 'fulfilled' ? results[5].value : { uptime: 0 };
|
|
||||||
|
|
||||||
// Log any failures
|
|
||||||
results.forEach((result, index) => {
|
|
||||||
if (result.status === 'rejected') {
|
|
||||||
const names = ['CPU', 'Memory', 'Filesystem', 'CPU Load', 'Processes', 'Uptime'];
|
|
||||||
console.warn(`⚠️ ${names[index]} data failed:`, result.reason?.message || result.reason);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Calculate disk totals across all mounted filesystems
|
|
||||||
const diskTotals = (fsData as any[]).reduce((acc: { total: number; used: number }, fs: any) => {
|
|
||||||
// Skip special filesystems and focus on main storage
|
|
||||||
if (fs.type && !['tmpfs', 'devtmpfs', 'proc', 'sysfs'].includes(fs.type.toLowerCase())) {
|
|
||||||
acc.total += fs.size || 0;
|
|
||||||
acc.used += fs.used || 0;
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
}, { total: 0, used: 0 });
|
|
||||||
|
|
||||||
const diskFree = diskTotals.total - diskTotals.used;
|
|
||||||
const diskUsagePercent = diskTotals.total > 0 ? Math.round((diskTotals.used / diskTotals.total) * 100) : 0;
|
|
||||||
|
|
||||||
// Format uptime
|
|
||||||
const formatUptime = (seconds: number): string => {
|
|
||||||
const days = Math.floor(seconds / 86400);
|
|
||||||
const hours = Math.floor((seconds % 86400) / 3600);
|
|
||||||
const minutes = Math.floor((seconds % 3600) / 60);
|
|
||||||
|
|
||||||
if (days > 0) {
|
|
||||||
return `${days}d ${hours}h ${minutes}m`;
|
|
||||||
} else if (hours > 0) {
|
|
||||||
return `${hours}h ${minutes}m`;
|
|
||||||
} else {
|
|
||||||
return `${minutes}m`;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Build metrics object
|
|
||||||
const metrics: SystemMetrics = {
|
|
||||||
cpu: {
|
|
||||||
usage: Math.round(currentLoad.currentLoad || 0),
|
|
||||||
cores: cpuData.cores || 0,
|
|
||||||
model: cpuData.brand || 'Unknown'
|
|
||||||
},
|
|
||||||
memory: {
|
|
||||||
total: memData.total || 0,
|
|
||||||
used: memData.used || 0,
|
|
||||||
free: memData.free || 0,
|
|
||||||
usagePercent: memData.total > 0 ? Math.round((memData.used / memData.total) * 100) : 0
|
|
||||||
},
|
|
||||||
disk: {
|
|
||||||
total: diskTotals.total,
|
|
||||||
used: diskTotals.used,
|
|
||||||
free: diskFree,
|
|
||||||
usagePercent: diskUsagePercent
|
|
||||||
},
|
|
||||||
uptime: formatUptime(uptimeData.uptime || 0),
|
|
||||||
processes: {
|
|
||||||
total: processData.all || 0,
|
|
||||||
running: processData.running || 0,
|
|
||||||
sleeping: processData.sleeping || 0
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Cache the results
|
|
||||||
metricsCache = metrics;
|
|
||||||
lastCacheTime = now;
|
|
||||||
|
|
||||||
console.log('✅ System metrics fetched successfully:', {
|
|
||||||
cpu: `${metrics.cpu.usage}%`,
|
|
||||||
memory: `${metrics.memory.usagePercent}%`,
|
|
||||||
disk: `${metrics.disk.usagePercent}%`,
|
|
||||||
uptime: metrics.uptime
|
|
||||||
});
|
|
||||||
|
|
||||||
return metrics;
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('❌ Failed to fetch system metrics:', error);
|
|
||||||
|
|
||||||
// Return fallback metrics on error
|
|
||||||
const fallbackMetrics: SystemMetrics = {
|
|
||||||
cpu: { usage: 0, cores: 0, model: 'Unknown' },
|
|
||||||
memory: { total: 0, used: 0, free: 0, usagePercent: 0 },
|
|
||||||
disk: { total: 0, used: 0, free: 0, usagePercent: 0 },
|
|
||||||
uptime: '0m',
|
|
||||||
processes: { total: 0, running: 0, sleeping: 0 }
|
|
||||||
};
|
|
||||||
|
|
||||||
return fallbackMetrics;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function to format bytes to human readable format
|
|
||||||
export function formatBytes(bytes: number): string {
|
|
||||||
if (bytes === 0) return '0 B';
|
|
||||||
|
|
||||||
const k = 1024;
|
|
||||||
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
||||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
||||||
|
|
||||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function to get system health status
|
|
||||||
export function getSystemHealthStatus(metrics: SystemMetrics): 'healthy' | 'degraded' | 'unhealthy' {
|
|
||||||
const { cpu, memory, disk } = metrics;
|
|
||||||
|
|
||||||
// Define thresholds
|
|
||||||
const criticalThresholds = { cpu: 90, memory: 95, disk: 95 };
|
|
||||||
const warningThresholds = { cpu: 70, memory: 80, disk: 85 };
|
|
||||||
|
|
||||||
// Check for critical issues
|
|
||||||
if (cpu.usage > criticalThresholds.cpu ||
|
|
||||||
memory.usagePercent > criticalThresholds.memory ||
|
|
||||||
disk.usagePercent > criticalThresholds.disk) {
|
|
||||||
return 'unhealthy';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for warning issues
|
|
||||||
if (cpu.usage > warningThresholds.cpu ||
|
|
||||||
memory.usagePercent > warningThresholds.memory ||
|
|
||||||
disk.usagePercent > warningThresholds.disk) {
|
|
||||||
return 'degraded';
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'healthy';
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue