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

View File

@@ -0,0 +1,88 @@
package repository
import (
"database/sql"
"pc-monitor-server/model"
"time"
)
type AlertRepository struct {
db *sql.DB
}
func NewAlertRepository(db *sql.DB) *AlertRepository {
return &AlertRepository{db: db}
}
func (r *AlertRepository) CreateRule(rule *model.AlertRule) error {
_, err := r.db.Exec(
`INSERT INTO alert_rules (id, device_id, metric, operator, threshold, duration, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?)`,
rule.ID, rule.DeviceID, rule.Metric, rule.Operator, rule.Threshold, rule.Duration, rule.CreatedAt,
)
return err
}
func (r *AlertRepository) GetRulesByDevice(deviceID string) ([]model.AlertRule, error) {
rows, err := r.db.Query(
`SELECT id, device_id, metric, operator, threshold, duration, created_at
FROM alert_rules WHERE device_id=?`, deviceID)
if err != nil {
return nil, err
}
defer rows.Close()
var rules []model.AlertRule
for rows.Next() {
var rule model.AlertRule
if err := rows.Scan(&rule.ID, &rule.DeviceID, &rule.Metric, &rule.Operator,
&rule.Threshold, &rule.Duration, &rule.CreatedAt); err != nil {
return nil, err
}
rules = append(rules, rule)
}
return rules, nil
}
func (r *AlertRepository) DeleteRule(id string) error {
_, err := r.db.Exec("DELETE FROM alert_rules WHERE id=?", id)
return err
}
func (r *AlertRepository) CreateAlert(alert *model.Alert) error {
_, err := r.db.Exec(
`INSERT INTO alerts (id, device_id, rule_id, metric, value, message, status, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
alert.ID, alert.DeviceID, alert.RuleID, alert.Metric,
alert.Value, alert.Message, alert.Status, alert.CreatedAt,
)
return err
}
func (r *AlertRepository) GetActiveAlerts() ([]model.Alert, error) {
rows, err := r.db.Query(
`SELECT id, device_id, rule_id, metric, value, message, status, created_at, resolved_at
FROM alerts WHERE status='active' ORDER BY created_at DESC`)
if err != nil {
return nil, err
}
defer rows.Close()
var alerts []model.Alert
for rows.Next() {
var a model.Alert
if err := rows.Scan(&a.ID, &a.DeviceID, &a.RuleID, &a.Metric,
&a.Value, &a.Message, &a.Status, &a.CreatedAt, &a.ResolvedAt); err != nil {
return nil, err
}
alerts = append(alerts, a)
}
return alerts, nil
}
func (r *AlertRepository) ResolveAlert(id string) error {
now := time.Now()
_, err := r.db.Exec(
`UPDATE alerts SET status='resolved', resolved_at=? WHERE id=?`, now, id)
return err
}

94
server/repository/db.go Normal file
View File

@@ -0,0 +1,94 @@
package repository
import (
"database/sql"
"os"
"path/filepath"
_ "github.com/mattn/go-sqlite3"
)
func NewDB(dbPath string) (*sql.DB, error) {
dir := filepath.Dir(dbPath)
if err := os.MkdirAll(dir, 0755); err != nil {
return nil, err
}
db, err := sql.Open("sqlite3", dbPath+"?_journal_mode=WAL")
if err != nil {
return nil, err
}
if err := initSchema(db); err != nil {
db.Close()
return nil, err
}
return db, nil
}
func initSchema(db *sql.DB) error {
schema := `
CREATE TABLE IF NOT EXISTS devices (
id TEXT PRIMARY KEY,
hostname TEXT NOT NULL,
os TEXT NOT NULL,
ip TEXT NOT NULL,
registered_at DATETIME NOT NULL,
last_report_at DATETIME NOT NULL,
status TEXT NOT NULL DEFAULT 'offline'
);
CREATE TABLE IF NOT EXISTS metrics (
id INTEGER PRIMARY KEY AUTOINCREMENT,
device_id TEXT NOT NULL,
timestamp DATETIME NOT NULL,
cpu_usage REAL,
cpu_temperature REAL,
cpu_core_usage TEXT,
memory_total INTEGER,
memory_used INTEGER,
memory_usage REAL,
gpu_usage REAL,
gpu_temperature REAL,
gpu_memory_total INTEGER,
gpu_memory_used INTEGER,
gpu_name TEXT,
network_interfaces TEXT,
disks TEXT,
power_status TEXT,
battery_level INTEGER,
power_source TEXT,
FOREIGN KEY (device_id) REFERENCES devices(id)
);
CREATE INDEX IF NOT EXISTS idx_metrics_device_time ON metrics(device_id, timestamp);
CREATE TABLE IF NOT EXISTS alert_rules (
id TEXT PRIMARY KEY,
device_id TEXT NOT NULL,
metric TEXT NOT NULL,
operator TEXT NOT NULL,
threshold REAL NOT NULL,
duration INTEGER NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL,
FOREIGN KEY (device_id) REFERENCES devices(id)
);
CREATE TABLE IF NOT EXISTS alerts (
id TEXT PRIMARY KEY,
device_id TEXT NOT NULL,
rule_id TEXT NOT NULL,
metric TEXT NOT NULL,
value REAL NOT NULL,
message TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'active',
created_at DATETIME NOT NULL,
resolved_at DATETIME,
FOREIGN KEY (device_id) REFERENCES devices(id),
FOREIGN KEY (rule_id) REFERENCES alert_rules(id)
);
`
_, err := db.Exec(schema)
return err
}

View File

@@ -0,0 +1,87 @@
package repository
import (
"database/sql"
"pc-monitor-server/model"
"time"
)
type DeviceRepository struct {
db *sql.DB
}
func NewDeviceRepository(db *sql.DB) *DeviceRepository {
return &DeviceRepository{db: db}
}
func (r *DeviceRepository) Create(device *model.Device) error {
_, err := r.db.Exec(
`INSERT INTO devices (id, hostname, os, ip, registered_at, last_report_at, status)
VALUES (?, ?, ?, ?, ?, ?, ?)`,
device.ID, device.Hostname, device.OS, device.IP,
device.RegisteredAt, device.LastReportAt, device.Status,
)
return err
}
func (r *DeviceRepository) Update(device *model.Device) error {
_, err := r.db.Exec(
`UPDATE devices SET hostname=?, os=?, ip=?, last_report_at=?, status=?
WHERE id=?`,
device.Hostname, device.OS, device.IP,
device.LastReportAt, device.Status, device.ID,
)
return err
}
func (r *DeviceRepository) GetByID(id string) (*model.Device, error) {
device := &model.Device{}
err := r.db.QueryRow(
`SELECT id, hostname, os, ip, registered_at, last_report_at, status
FROM devices WHERE id=?`, id,
).Scan(&device.ID, &device.Hostname, &device.OS, &device.IP,
&device.RegisteredAt, &device.LastReportAt, &device.Status)
if err != nil {
return nil, err
}
return device, nil
}
func (r *DeviceRepository) GetAll() ([]model.Device, error) {
rows, err := r.db.Query(
`SELECT id, hostname, os, ip, registered_at, last_report_at, status
FROM devices ORDER BY last_report_at DESC`)
if err != nil {
return nil, err
}
defer rows.Close()
var devices []model.Device
for rows.Next() {
var d model.Device
if err := rows.Scan(&d.ID, &d.Hostname, &d.OS, &d.IP,
&d.RegisteredAt, &d.LastReportAt, &d.Status); err != nil {
return nil, err
}
devices = append(devices, d)
}
return devices, nil
}
func (r *DeviceRepository) Delete(id string) error {
_, err := r.db.Exec("DELETE FROM devices WHERE id=?", id)
return err
}
func (r *DeviceRepository) UpdateStatus(id string, status string) error {
_, err := r.db.Exec("UPDATE devices SET status=? WHERE id=?", status, id)
return err
}
func (r *DeviceRepository) MarkOffline(threshold time.Duration) error {
cutoff := time.Now().Add(-threshold)
_, err := r.db.Exec(
`UPDATE devices SET status='offline'
WHERE last_report_at < ? AND status='online'`, cutoff)
return err
}

View File

@@ -0,0 +1,106 @@
package repository
import (
"database/sql"
"encoding/json"
"pc-monitor-server/model"
"time"
)
type MetricsRepository struct {
db *sql.DB
}
func NewMetricsRepository(db *sql.DB) *MetricsRepository {
return &MetricsRepository{db: db}
}
func (r *MetricsRepository) Save(m *model.Metrics) error {
cpuCoreJSON, _ := json.Marshal(m.CPUCoreUsage)
netJSON, _ := json.Marshal(m.NetworkInterfaces)
diskJSON, _ := json.Marshal(m.Disks)
_, err := r.db.Exec(
`INSERT INTO metrics (
device_id, timestamp, cpu_usage, cpu_temperature, cpu_core_usage,
memory_total, memory_used, memory_usage,
gpu_usage, gpu_temperature, gpu_memory_total, gpu_memory_used, gpu_name,
network_interfaces, disks,
power_status, battery_level, power_source
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
m.DeviceID, m.Timestamp,
m.CPUUsage, m.CPUTemperature, string(cpuCoreJSON),
m.MemoryTotal, m.MemoryUsed, m.MemoryUsage,
m.GPUUsage, m.GPUTemperature, m.GPUMemoryTotal, m.GPUMemoryUsed, m.GPUName,
string(netJSON), string(diskJSON),
m.PowerStatus, m.BatteryLevel, m.PowerSource,
)
return err
}
func (r *MetricsRepository) GetLatest(deviceID string) (*model.Metrics, error) {
m := &model.Metrics{}
var cpuCoreJSON, netJSON, diskJSON string
err := r.db.QueryRow(
`SELECT device_id, timestamp, cpu_usage, cpu_temperature, cpu_core_usage,
memory_total, memory_used, memory_usage,
gpu_usage, gpu_temperature, gpu_memory_total, gpu_memory_used, gpu_name,
network_interfaces, disks,
power_status, battery_level, power_source
FROM metrics WHERE device_id=? ORDER BY timestamp DESC LIMIT 1`, deviceID,
).Scan(&m.DeviceID, &m.Timestamp,
&m.CPUUsage, &m.CPUTemperature, &cpuCoreJSON,
&m.MemoryTotal, &m.MemoryUsed, &m.MemoryUsage,
&m.GPUUsage, &m.GPUTemperature, &m.GPUMemoryTotal, &m.GPUMemoryUsed, &m.GPUName,
&netJSON, &diskJSON,
&m.PowerStatus, &m.BatteryLevel, &m.PowerSource)
if err != nil {
return nil, err
}
json.Unmarshal([]byte(cpuCoreJSON), &m.CPUCoreUsage)
json.Unmarshal([]byte(netJSON), &m.NetworkInterfaces)
json.Unmarshal([]byte(diskJSON), &m.Disks)
return m, nil
}
func (r *MetricsRepository) GetHistory(deviceID string, start, end time.Time, limit int) ([]model.Metrics, error) {
rows, err := r.db.Query(
`SELECT device_id, timestamp, cpu_usage, cpu_temperature, cpu_core_usage,
memory_total, memory_used, memory_usage,
gpu_usage, gpu_temperature, gpu_memory_total, gpu_memory_used, gpu_name,
network_interfaces, disks,
power_status, battery_level, power_source
FROM metrics WHERE device_id=? AND timestamp BETWEEN ? AND ?
ORDER BY timestamp DESC LIMIT ?`, deviceID, start, end, limit)
if err != nil {
return nil, err
}
defer rows.Close()
var metrics []model.Metrics
for rows.Next() {
var m model.Metrics
var cpuCoreJSON, netJSON, diskJSON string
if err := rows.Scan(&m.DeviceID, &m.Timestamp,
&m.CPUUsage, &m.CPUTemperature, &cpuCoreJSON,
&m.MemoryTotal, &m.MemoryUsed, &m.MemoryUsage,
&m.GPUUsage, &m.GPUTemperature, &m.GPUMemoryTotal, &m.GPUMemoryUsed, &m.GPUName,
&netJSON, &diskJSON,
&m.PowerStatus, &m.BatteryLevel, &m.PowerSource); err != nil {
return nil, err
}
json.Unmarshal([]byte(cpuCoreJSON), &m.CPUCoreUsage)
json.Unmarshal([]byte(netJSON), &m.NetworkInterfaces)
json.Unmarshal([]byte(diskJSON), &m.Disks)
metrics = append(metrics, m)
}
return metrics, nil
}
func (r *MetricsRepository) Cleanup(before time.Time) error {
_, err := r.db.Exec("DELETE FROM metrics WHERE timestamp < ?", before)
return err
}