Files
lan-manager/server/handlers/export.go
shirainbown e4b2edb3bf feat: add comprehensive operation logs for all critical actions
- Login/Logout: record user authentication events with source IP
- Login failed: record failed login attempts for security audit
- Export: record data export operations
- Machine status change: record online/offline transitions with reason
- SSH sync: record automatic SSH sync success/failure with error details
- All auto-generated logs use username='system' and empty source_ip
2026-06-19 18:59:32 +08:00

133 lines
4.7 KiB
Go

package handlers
import (
"database/sql"
"encoding/json"
"net/http"
"time"
"github.com/gin-gonic/gin"
"lan-manager/server/db"
"lan-manager/server/middleware"
"lan-manager/server/models"
)
type ExportHandler struct{}
func NewExportHandler() *ExportHandler {
return &ExportHandler{}
}
func (h *ExportHandler) Export(c *gin.Context) {
data := map[string]interface{}{}
machines := []models.Machine{}
if rows, err := db.DB.Query(`SELECT id, hostname, ip, mac, os_type, os_version, notes, ssh_port, ssh_username, ssh_password, is_online, last_ping_at, cpu_info, memory_info, disk_info, uptime, listen_ports, ssh_synced_at, created_at, updated_at FROM machines`); err == nil {
defer rows.Close()
for rows.Next() {
var m models.Machine
var lp, ss *time.Time
if err := rows.Scan(&m.ID, &m.Hostname, &m.IP, &m.MAC, &m.OsType, &m.OsVersion, &m.Notes, &m.SSHPort, &m.SSHUsername, &m.SSHPassword, &m.IsOnline, &lp, &m.CPUInfo, &m.MemoryInfo, &m.DiskInfo, &m.Uptime, &m.ListenPorts, &ss, &m.CreatedAt, &m.UpdatedAt); err == nil {
m.LastPingAt = lp
m.SSHSyncedAt = ss
machines = append(machines, m)
}
}
}
data["machines"] = machines
services := []models.Service{}
if rows, err := db.DB.Query(`SELECT id, machine_id, name, port, protocol, notes, target_machine_id, target_notes FROM services`); err == nil {
defer rows.Close()
for rows.Next() {
var s models.Service
var tm sql.NullInt64
if err := rows.Scan(&s.ID, &s.MachineID, &s.Name, &s.Port, &s.Protocol, &s.Notes, &tm, &s.TargetNotes); err == nil {
if tm.Valid {
tmid := tm.Int64
s.TargetMachineID = &tmid
}
services = append(services, s)
}
}
}
data["services"] = services
relationships := []models.Relationship{}
if rows, err := db.DB.Query(`SELECT id, source_machine_id, target_machine_id, relation_type, source_port, target_port, notes FROM relationships`); err == nil {
defer rows.Close()
for rows.Next() {
var r models.Relationship
if err := rows.Scan(&r.ID, &r.SourceMachineID, &r.TargetMachineID, &r.RelationType, &r.SourcePort, &r.TargetPort, &r.Notes); err == nil {
relationships = append(relationships, r)
}
}
}
data["relationships"] = relationships
b, _ := json.MarshalIndent(data, "", " ")
filename := "lan-manager-backup-" + time.Now().Format("20060102") + ".json"
c.Header("Content-Disposition", "attachment; filename="+filename)
// 记录导出日志
var entityID int64 = 0
middleware.LogOperation("export", "system", &entityID, "json-export", "", "", c.ClientIP(), middleware.CurrentUser(c))
c.Data(http.StatusOK, "application/json", b)
}
func (h *ExportHandler) Import(c *gin.Context) {
file, _, err := c.Request.FormFile("file")
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
defer file.Close()
var data map[string]json.RawMessage
if err := json.NewDecoder(file).Decode(&data); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
mode := c.DefaultPostForm("mode", "append")
if mode == "overwrite" {
_, _ = db.DB.Exec(`DELETE FROM relationships`)
_, _ = db.DB.Exec(`DELETE FROM services`)
_, _ = db.DB.Exec(`DELETE FROM machines`)
}
if raw, ok := data["machines"]; ok {
var list []models.Machine
if err := json.Unmarshal(raw, &list); err == nil {
for _, m := range list {
_, _ = db.DB.Exec(`INSERT OR IGNORE INTO machines (id, hostname, ip, mac, os_type, os_version, notes, ssh_port, ssh_username, ssh_password, is_online, cpu_info, memory_info, disk_info, uptime, listen_ports) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
m.ID, m.Hostname, m.IP, m.MAC, m.OsType, m.OsVersion, m.Notes, m.SSHPort, m.SSHUsername, m.SSHPassword, 0, m.CPUInfo, m.MemoryInfo, m.DiskInfo, m.Uptime, m.ListenPorts)
}
}
}
if raw, ok := data["services"]; ok {
var list []models.Service
if err := json.Unmarshal(raw, &list); err == nil {
for _, s := range list {
_, _ = db.DB.Exec(`INSERT OR IGNORE INTO services (id, machine_id, name, port, protocol, notes, target_machine_id, target_notes) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
s.ID, s.MachineID, s.Name, s.Port, s.Protocol, s.Notes, s.TargetMachineID, s.TargetNotes)
}
}
}
if raw, ok := data["relationships"]; ok {
var list []models.Relationship
if err := json.Unmarshal(raw, &list); err == nil {
for _, r := range list {
_, _ = db.DB.Exec(`INSERT OR IGNORE INTO relationships (id, source_machine_id, target_machine_id, relation_type, source_port, target_port, notes) VALUES (?, ?, ?, ?, ?, ?, ?)`,
r.ID, r.SourceMachineID, r.TargetMachineID, r.RelationType, r.SourcePort, r.TargetPort, r.Notes)
}
}
}
middleware.LogOperation("import", "system", nil, "json-import", "", "", c.ClientIP(), middleware.CurrentUser(c))
c.JSON(http.StatusOK, gin.H{"message": "imported"})
}