Implement Dashboard with system stats (Phase 11 P1)
Some checks failed
Test / test (push) Has been cancelled
Test / build (push) Has been cancelled

Dashboard Features:
- Dashboard.vue: System overview UI
- System stats: CPU, Memory, Disk usage
- Service status: SMB/SFTP/WebDAV/Backup
- Recent activity log

Tauri Commands:
- get_system_stats: CPU/Memory/Disk stats (macOS + Linux)
- get_all_services_status: Service status list
- get_recent_activity: Activity log

Platform Support:
- macOS: top + vm_stat + df commands
- Linux: /proc/stat + /proc/meminfo + df

UI Components:
- CPU usage progress bar (color-coded)
- Memory usage progress bar
- Disk usage progress bar
- Service status table
- Quick actions buttons
- Recent activity table

Router:
- Added /dashboard route

Home.vue:
- Added Dashboard card (first card)

Build:  Tauri + markbase-core
Tests: 495 markbase-core + 201 smb-server
This commit is contained in:
Warren
2026-06-24 06:10:02 +08:00
parent 0f77983483
commit 0efaddaffc
6 changed files with 613 additions and 2 deletions

View File

@@ -1,5 +1,6 @@
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'
import Dashboard from '../views/Dashboard.vue'
import Install from '../views/Install.vue'
import Config from '../views/Config.vue'
import Diagnostic from '../views/Diagnostic.vue'
@@ -16,6 +17,11 @@ const routes = [
name: 'Home',
component: Home
},
{
path: '/dashboard',
name: 'Dashboard',
component: Dashboard
},
{
path: '/install',
name: 'Install',

View File

@@ -0,0 +1,302 @@
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import { invoke } from '@tauri-apps/api/core'
import { ElMessage } from 'element-plus'
import {
Monitor,
Cpu,
Coin,
FolderOpened,
Connection,
Document,
Upload,
Download,
Clock,
} from '@element-plus/icons-vue'
const systemStats = ref({
cpu_usage: 0,
memory_usage: 0,
memory_total: 0,
memory_used: 0,
disk_total: 0,
disk_used: 0,
disk_usage_percent: 0,
})
const serviceStatus = ref([])
const recentActivity = ref([])
const loading = ref(false)
let statsInterval = null
const loadSystemStats = async () => {
try {
const stats = await invoke('get_system_stats')
systemStats.value = stats
} catch (error) {
console.error('Failed to load system stats:', error)
}
}
const loadServiceStatus = async () => {
try {
const status = await invoke('get_all_services_status')
serviceStatus.value = status
} catch (error) {
console.error('Failed to load service status:', error)
}
}
const loadRecentActivity = async () => {
try {
const activity = await invoke('get_recent_activity')
recentActivity.value = activity
} catch (error) {
console.error('Failed to load recent activity:', error)
}
}
const refreshData = async () => {
loading.value = true
await Promise.all([
loadSystemStats(),
loadServiceStatus(),
loadRecentActivity(),
])
loading.value = false
}
const formatBytes = (bytes) => {
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(2)) + ' ' + sizes[i]
}
const getUsageColor = (usage) => {
if (usage < 50) return 'success'
if (usage < 80) return 'warning'
return 'danger'
}
const getServiceColor = (status) => {
if (status === 'running') return 'success'
if (status === 'stopped') return 'danger'
return 'info'
}
onMounted(async () => {
await refreshData()
statsInterval = setInterval(loadSystemStats, 5000)
})
onUnmounted(() => {
if (statsInterval) {
clearInterval(statsInterval)
}
})
</script>
<template>
<div class="dashboard-container">
<!-- System Stats Cards -->
<el-row :gutter="20" class="stats-row">
<el-col :span="8">
<el-card class="stat-card">
<div class="stat-header">
<el-icon :size="24"><Cpu /></el-icon>
<span>CPU Usage</span>
</div>
<div class="stat-body">
<el-progress
:percentage="systemStats.cpu_usage"
:color="getUsageColor(systemStats.cpu_usage)"
:stroke-width="20"
/>
<div class="stat-value">{{ systemStats.cpu_usage.toFixed(1) }}%</div>
</div>
</el-card>
</el-col>
<el-col :span="8">
<el-card class="stat-card">
<div class="stat-header">
<el-icon :size="24"><Coin /></el-icon>
<span>Memory Usage</span>
</div>
<div class="stat-body">
<el-progress
:percentage="systemStats.memory_usage"
:color="getUsageColor(systemStats.memory_usage)"
:stroke-width="20"
/>
<div class="stat-value">
{{ formatBytes(systemStats.memory_used) }} / {{ formatBytes(systemStats.memory_total) }}
</div>
</div>
</el-card>
</el-col>
<el-col :span="8">
<el-card class="stat-card">
<div class="stat-header">
<el-icon :size="24"><FolderOpened /></el-icon>
<span>Disk Usage</span>
</div>
<div class="stat-body">
<el-progress
:percentage="systemStats.disk_usage_percent"
:color="getUsageColor(systemStats.disk_usage_percent)"
:stroke-width="20"
/>
<div class="stat-value">
{{ formatBytes(systemStats.disk_used) }} / {{ formatBytes(systemStats.disk_total) }}
</div>
</div>
</el-card>
</el-col>
</el-row>
<!-- Service Status & Quick Actions -->
<el-row :gutter="20" class="services-row">
<el-col :span="12">
<el-card>
<template #header>
<div class="card-header">
<span><el-icon><Connection /></el-icon> Service Status</span>
<el-button size="small" @click="refreshData" :loading="loading">
Refresh
</el-button>
</div>
</template>
<el-table :data="serviceStatus" style="width: 100%">
<el-table-column prop="name" label="Service" width="180" />
<el-table-column prop="status" label="Status" width="100">
<template #default="{ row }">
<el-tag :type="getServiceColor(row.status)" size="small">
{{ row.status }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="port" label="Port" width="80" />
<el-table-column prop="uptime" label="Uptime" />
</el-table>
</el-card>
</el-col>
<el-col :span="12">
<el-card>
<template #header>
<div class="card-header">
<span><el-icon><Monitor /></el-icon> Quick Actions</span>
</div>
</template>
<div class="quick-actions">
<el-button type="primary" :icon="Upload" class="action-btn">
Upload File
</el-button>
<el-button type="success" :icon="Document" class="action-btn">
Create Backup
</el-button>
<el-button type="warning" :icon="Clock" class="action-btn">
View Backups
</el-button>
<el-button type="info" :icon="Download" class="action-btn">
Download File
</el-button>
</div>
</el-card>
</el-col>
</el-row>
<!-- Recent Activity -->
<el-row :gutter="20" class="activity-row">
<el-col :span="24">
<el-card>
<template #header>
<div class="card-header">
<span><el-icon><Clock /></el-icon> Recent Activity</span>
</div>
</template>
<el-table :data="recentActivity" style="width: 100%">
<el-table-column prop="timestamp" label="Time" width="180" />
<el-table-column prop="activity_type" label="Type" width="120">
<template #default="{ row }">
<el-tag size="small">{{ row.activity_type }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="description" label="Description" />
<el-table-column prop="user" label="User" width="120" />
</el-table>
</el-card>
</el-col>
</el-row>
</div>
</template>
<style scoped>
.dashboard-container {
padding: 20px;
}
.stats-row {
margin-bottom: 20px;
}
.stat-card {
height: 180px;
}
.stat-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 20px;
font-size: 16px;
font-weight: 500;
}
.stat-body {
text-align: center;
}
.stat-value {
margin-top: 10px;
font-size: 14px;
color: #606266;
}
.services-row {
margin-bottom: 20px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.card-header span {
display: flex;
align-items: center;
gap: 8px;
}
.quick-actions {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px;
}
.action-btn {
width: 100%;
}
.activity-row {
margin-bottom: 20px;
}
</style>

View File

@@ -4,7 +4,7 @@ import { useRouter } from 'vue-router'
import { useAppStore } from '../stores/app'
import { invoke } from '@tauri-apps/api/tauri'
import { ElMessage } from 'element-plus'
import { Folder, Document, Upload, Clock, UserFilled, FolderOpened } from '@element-plus/icons-vue'
import { Folder, Document, Upload, Clock, UserFilled, FolderOpened, Monitor } from '@element-plus/icons-vue'
import { open } from '@tauri-apps/api/dialog'
const router = useRouter()
@@ -170,6 +170,14 @@ onMounted(async () => {
</el-col>
<el-col :span="8">
<div class="management-cards">
<el-card class="management-card" @click="navigateTo('/dashboard')">
<div class="card-content">
<el-icon :size="40"><Monitor /></el-icon>
<h3>Dashboard</h3>
<p>System stats overview</p>
</div>
</el-card>
<el-card class="management-card" @click="navigateTo('/install')">
<div class="card-content">
<el-icon :size="40"><Setting /></el-icon>