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:
88
server/repository/alert.go
Normal file
88
server/repository/alert.go
Normal 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
94
server/repository/db.go
Normal 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
|
||||
}
|
||||
87
server/repository/device.go
Normal file
87
server/repository/device.go
Normal 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
|
||||
}
|
||||
106
server/repository/metrics.go
Normal file
106
server/repository/metrics.go
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user