feat: init pc-monitor project

- Client: Go-based Windows hardware monitoring (CPU, GPU, memory, disk, network, power)
- Server: Go + Gin + SQLite backend with REST API
- Frontend: Vue 3 + Element Plus dashboard
- Docker deployment support
- Windows service installation script
This commit is contained in:
672
2026-05-17 01:29:44 +08:00
commit 0e8c9f7bff
49 changed files with 3291 additions and 0 deletions

99
server/service/alert.go Normal file
View File

@@ -0,0 +1,99 @@
package service
import (
"fmt"
"pc-monitor-server/model"
"pc-monitor-server/repository"
"time"
)
type AlertService struct {
alertRepo *repository.AlertRepository
metricsRepo *repository.MetricsRepository
}
func NewAlertService(alertRepo *repository.AlertRepository, metricsRepo *repository.MetricsRepository) *AlertService {
return &AlertService{alertRepo: alertRepo, metricsRepo: metricsRepo}
}
func (s *AlertService) CreateRule(rule *model.AlertRule) error {
rule.CreatedAt = time.Now()
return s.alertRepo.CreateRule(rule)
}
func (s *AlertService) GetRulesByDevice(deviceID string) ([]model.AlertRule, error) {
return s.alertRepo.GetRulesByDevice(deviceID)
}
func (s *AlertService) DeleteRule(id string) error {
return s.alertRepo.DeleteRule(id)
}
func (s *AlertService) GetActiveAlerts() ([]model.Alert, error) {
return s.alertRepo.GetActiveAlerts()
}
func (s *AlertService) ResolveAlert(id string) error {
return s.alertRepo.ResolveAlert(id)
}
func (s *AlertService) CheckAlerts(deviceID string, metrics *model.Metrics) error {
rules, err := s.alertRepo.GetRulesByDevice(deviceID)
if err != nil {
return err
}
for _, rule := range rules {
value := getMetricValue(metrics, rule.Metric)
if checkThreshold(value, rule.Operator, rule.Threshold) {
alert := &model.Alert{
ID: fmt.Sprintf("%s-%s-%d", deviceID, rule.ID, time.Now().Unix()),
DeviceID: deviceID,
RuleID: rule.ID,
Metric: rule.Metric,
Value: value,
Message: fmt.Sprintf("%s %s %.2f (threshold: %.2f)", rule.Metric, rule.Operator, value, rule.Threshold),
Status: "active",
CreatedAt: time.Now(),
}
s.alertRepo.CreateAlert(alert)
}
}
return nil
}
func getMetricValue(m *model.Metrics, metric string) float64 {
switch metric {
case "cpu_usage":
return m.CPUUsage
case "cpu_temperature":
return m.CPUTemperature
case "memory_usage":
return m.MemoryUsage
case "gpu_usage":
return m.GPUUsage
case "gpu_temperature":
return m.GPUTemperature
case "battery_level":
return float64(m.BatteryLevel)
default:
return 0
}
}
func checkThreshold(value float64, operator string, threshold float64) bool {
switch operator {
case ">":
return value > threshold
case ">=":
return value >= threshold
case "<":
return value < threshold
case "<=":
return value <= threshold
case "==":
return value == threshold
default:
return false
}
}

73
server/service/device.go Normal file
View File

@@ -0,0 +1,73 @@
package service
import (
"crypto/sha256"
"fmt"
"pc-monitor-server/model"
"pc-monitor-server/repository"
"time"
)
type DeviceService struct {
repo *repository.DeviceRepository
}
func NewDeviceService(repo *repository.DeviceRepository) *DeviceService {
return &DeviceService{repo: repo}
}
func (s *DeviceService) Register(hostname, osName, ip string) (*model.Device, error) {
id := generateDeviceID(hostname, ip)
device, err := s.repo.GetByID(id)
if err == nil {
device.LastReportAt = time.Now()
device.Status = "online"
s.repo.Update(device)
return device, nil
}
device = &model.Device{
ID: id,
Hostname: hostname,
OS: osName,
IP: ip,
RegisteredAt: time.Now(),
LastReportAt: time.Now(),
Status: "online",
}
if err := s.repo.Create(device); err != nil {
return nil, err
}
return device, nil
}
func (s *DeviceService) GetAll() ([]model.Device, error) {
return s.repo.GetAll()
}
func (s *DeviceService) GetByID(id string) (*model.Device, error) {
return s.repo.GetByID(id)
}
func (s *DeviceService) Delete(id string) error {
return s.repo.Delete(id)
}
func (s *DeviceService) Heartbeat(id string) error {
device, err := s.repo.GetByID(id)
if err != nil {
return err
}
device.LastReportAt = time.Now()
device.Status = "online"
return s.repo.Update(device)
}
func (s *DeviceService) CheckOffline() {
s.repo.MarkOffline(2 * time.Minute)
}
func generateDeviceID(hostname, ip string) string {
hash := sha256.Sum256([]byte(fmt.Sprintf("%s-%s", hostname, ip)))
return fmt.Sprintf("%x", hash[:8])
}

35
server/service/metrics.go Normal file
View File

@@ -0,0 +1,35 @@
package service
import (
"pc-monitor-server/model"
"pc-monitor-server/repository"
"time"
)
type MetricsService struct {
repo *repository.MetricsRepository
}
func NewMetricsService(repo *repository.MetricsRepository) *MetricsService {
return &MetricsService{repo: repo}
}
func (s *MetricsService) Save(m *model.Metrics) error {
return s.repo.Save(m)
}
func (s *MetricsService) GetLatest(deviceID string) (*model.Metrics, error) {
return s.repo.GetLatest(deviceID)
}
func (s *MetricsService) GetHistory(deviceID string, start, end time.Time, limit int) ([]model.Metrics, error) {
if limit <= 0 {
limit = 1000
}
return s.repo.GetHistory(deviceID, start, end, limit)
}
func (s *MetricsService) Cleanup(retentionDays int) error {
before := time.Now().AddDate(0, 0, -retentionDays)
return s.repo.Cleanup(before)
}